diff --git a/rts/Rml/Backends/RmlUi_Renderer_GL3_Spring.cpp b/rts/Rml/Backends/RmlUi_Renderer_GL3_Spring.cpp index f8e904a040..86c2335f34 100644 --- a/rts/Rml/Backends/RmlUi_Renderer_GL3_Spring.cpp +++ b/rts/Rml/Backends/RmlUi_Renderer_GL3_Spring.cpp @@ -657,6 +657,10 @@ bool RenderInterface_GL3_Spring::GenerateTexture(Rml::TextureHandle& texture_han void RenderInterface_GL3_Spring::ReleaseTexture(Rml::TextureHandle texture_handle) { + // Something was using a texture loaded/managed outside of Rml. Do nothing. + if (texture_handle == TextureEnableWithoutBinding) + return; + glDeleteTextures(1, (GLuint*)&texture_handle); } diff --git a/rts/Rml/Backends/RmlUi_Renderer_GL3_Spring.h b/rts/Rml/Backends/RmlUi_Renderer_GL3_Spring.h index 8fd34bc6ef..8351348676 100644 --- a/rts/Rml/Backends/RmlUi_Renderer_GL3_Spring.h +++ b/rts/Rml/Backends/RmlUi_Renderer_GL3_Spring.h @@ -76,6 +76,9 @@ class RenderInterface_GL3_Spring : public Rml::RenderInterface { void SetTransform(const Rml::Matrix4f* transform) override; // Can be passed to RenderGeometry() to enable texture rendering without changing the bound texture. + // Can used as the output of a TextureCallback to signal that this texture is externally managed. + // In that case, you will need to keep a reference to the texture + // you want and bind it before rendering the geometry. static const Rml::TextureHandle TextureEnableWithoutBinding = Rml::TextureHandle(-1); private: diff --git a/rts/Rml/Elements/ElementLuaTexture.cpp b/rts/Rml/Elements/ElementLuaTexture.cpp index e96bc97ae6..c4c6f1c636 100644 --- a/rts/Rml/Elements/ElementLuaTexture.cpp +++ b/rts/Rml/Elements/ElementLuaTexture.cpp @@ -5,7 +5,8 @@ // /* - * This source file is based on the ElementLuaTexture.cpp file of RmlUi, the HTML/CSS Interface Middleware + * This source file is based on the ElementLuaTexture.cpp file of RmlUi, the HTML/CSS Interface + * Middleware * * For the latest information, see http://github.com/mikke89/RmlUi * @@ -36,259 +37,256 @@ #include "Lua/LuaOpenGLUtils.h" #include "Rml/Backends/RmlUi_Backend.h" +#include "Rml/Backends/RmlUi_Renderer_GL3_Spring.h" +#include "Rendering/GL/myGL.h" + #include "RmlUi/Core/ComputedValues.h" #include "RmlUi/Core/ElementUtilities.h" #include "RmlUi/Core/GeometryUtilities.h" #include "RmlUi/Core/PropertyIdSet.h" - -#include +#include "RmlUi/Source/Core/TextureResource.h" namespace RmlGui { - ElementLuaTexture::ElementLuaTexture(const Rml::String& tag) : - Element(tag), dimensions(-1, -1), rect_source(RectSource::None) - { - dimensions_scale = 1.0f; - geometry_dirty = false; - texture_dirty = true; - } - - ElementLuaTexture::~ElementLuaTexture() = default; - - bool ElementLuaTexture::GetIntrinsicDimensions(Rml::Vector2f& _dimensions, float& _ratio) - { - // Check if we need to reload the texture. - if (texture_dirty) - LoadTexture(); - - // Calculate the x dimension. - if (HasAttribute("width")) - dimensions.x = GetAttribute("width", -1); - else if (rect_source == RectSource::None) - dimensions.x = (float)texture.GetDimensions().x; - else - dimensions.x = rect.Width(); - - // Calculate the y dimension. - if (HasAttribute("height")) - dimensions.y = GetAttribute("height", -1); - else if (rect_source == RectSource::None) - dimensions.y = (float)texture.GetDimensions().y; - else - dimensions.y = rect.Height(); - - dimensions *= dimensions_scale; - - // Return the calculated dimensions. If this changes the size of the element, it will result in - // a call to 'onresize' below which will regenerate the geometry. - _dimensions = dimensions; - _ratio = dimensions.x / dimensions.y; - - return true; - } - - void ElementLuaTexture::OnRender() - { - // Regenerate the geometry if required (this will be set if 'rect' changes but does not result in a resize). - if (geometry_dirty) - GenerateGeometry(); - - // Render the geometry beginning at this element's content region. - geometry.Render(GetAbsoluteOffset(Rml::BoxArea::Content).Round()); - } - - void ElementLuaTexture::OnAttributeChange(const Rml::ElementAttributes& changed_attributes) - { - // Call through to the base element's OnAttributeChange(). - Element::OnAttributeChange(changed_attributes); - - bool dirty_layout = false; - - // Check for a changed 'src' attribute. If this changes, the old texture handle is released, - // forcing a reload when the layout is regenerated. - if (changed_attributes.find("src") != changed_attributes.end()) - { - texture_dirty = true; - dirty_layout = true; - } - - // Check for a changed 'width' attribute. If this changes, a layout is forced which will - // recalculate the dimensions. - if (changed_attributes.find("width") != changed_attributes.end() - || changed_attributes.find("height") !=changed_attributes.end()) - { - dirty_layout = true; - } - - // Check for a change to the 'rect' attribute. If this changes, the coordinates are - // recomputed and a layout forced. If a sprite is set to source, then that will override any attribute. - if (changed_attributes.find("rect") != changed_attributes.end()) - { - UpdateRect(); - - // Rectangle has changed; this will most likely result in a size change, so we need to force a layout. - dirty_layout = true; - } - - if (dirty_layout) - DirtyLayout(); - } - - void ElementLuaTexture::OnPropertyChange(const Rml::PropertyIdSet& changed_properties) - { - Element::OnPropertyChange(changed_properties); - - if (changed_properties.Contains(Rml::PropertyId::ImageColor) || changed_properties.Contains(Rml::PropertyId::Opacity)) - { - GenerateGeometry(); - } - } - - void ElementLuaTexture::OnChildAdd(Element* child) - { - // Load the texture once we have attached to the document so that it can immediately be found during the call to `Rml::GetTextureSourceList`. The - // texture won't actually be loaded from the backend before it is shown. However, only do this if we have an active context so that the dp-ratio - // can be retrieved. If there is no context now the texture loading will be deferred until the next layout update. - if (child == this && texture_dirty && GetContext()) - { - LoadTexture(); - } - } - - void ElementLuaTexture::OnResize() - { - GenerateGeometry(); - } - - void ElementLuaTexture::OnDpRatioChange() - { - texture_dirty = true; - DirtyLayout(); - } - - void ElementLuaTexture::GenerateGeometry() - { - // Release the old geometry before specifying the new vertices. - geometry.Release(true); - - Rml::Vector& vertices = geometry.GetVertices(); - Rml::Vector& indices = geometry.GetIndices(); - - vertices.resize(4); - indices.resize(6); - - // Generate the texture coordinates. - Rml::Vector2f texcoords[2]; - if (rect_source != RectSource::None) - { - const auto texture_dimensions = Rml::Vector2f(Rml::Math::Max(texture.GetDimensions(), Rml::Vector2i(1))); - texcoords[0] = rect.TopLeft() / texture_dimensions; - texcoords[1] = rect.BottomRight() / texture_dimensions; - } - else - { - texcoords[0] = Rml::Vector2f(0, 0); - texcoords[1] = Rml::Vector2f(1, 1); - } - - const Rml::ComputedValues& computed = GetComputedValues(); - - float opacity = computed.opacity(); - Rml::Colourb quad_colour = computed.image_color(); - quad_colour.alpha = (Rml::byte)(opacity * (float)quad_colour.alpha); - - Rml::Vector2f quad_size = GetBox().GetSize(Rml::BoxArea::Content).Round(); - - Rml::GeometryUtilities::GenerateQuad( - &vertices[0], &indices[0], - Rml::Vector2f(0, 0), quad_size, quad_colour, - texcoords[0], texcoords[1] - ); - - geometry_dirty = false; - } - - bool ElementLuaTexture::LoadTexture() - { - texture_dirty = false; - geometry_dirty = true; - dimensions_scale = 1.0f; - - const float dp_ratio = Rml::ElementUtilities::GetDensityIndependentPixelRatio(this); - - const auto source_name = GetAttribute("src", ""); - if (source_name.empty()) - { - texture = Rml::Texture(); - rect_source = RectSource::None; - return false; - } - - const Rml::TextureCallback callback = []( - const auto _, const Rml::String& name, Rml::TextureHandle& out_handle, Rml::Vector2i& out_dimensions - ) -> bool - { - LuaMatTexture texUnit; - if (!LuaOpenGLUtils::ParseTextureImage(RmlGui::GetLuaState(), texUnit, name)) - return false; - - out_handle = texUnit.GetTextureID(); - texturesToNotDelete.insert(out_handle); - - const auto [width, height, _z] = texUnit.GetSize(); - out_dimensions.x = width; - out_dimensions.y = height; - - return true; - }; - - - texture.Set(source_name, callback); - - dimensions_scale = dp_ratio; - - // Set the texture onto our geometry object. - geometry.SetTexture(&texture); - - return true; - } - - void ElementLuaTexture::UpdateRect() - { - bool valid_rect = false; - - if (const auto rect_string = GetAttribute("rect", ""); !rect_string.empty()) - { - Rml::StringList coords_list; - Rml::StringUtilities::ExpandString(coords_list, rect_string, ' '); - - if (coords_list.size() != 4) - { - Rml::Log::Message( - Rml::Log::LT_WARNING, - "Element '%s' has an invalid 'rect' attribute; rect requires 4 space-separated values, found %zu.", - GetAddress().c_str(), coords_list.size() - ); - } - else - { - const Rml::Vector2f position = { - Rml::FromString(coords_list[0], 0.f), Rml::FromString(coords_list[1], 0.f) - }; - const Rml::Vector2f size = {Rml::FromString(coords_list[2], 0.f), Rml::FromString(coords_list[3], 0.f)}; - rect = Rml::Rectanglef::FromPositionSize(position, size); - - // We have new, valid coordinates; force the geometry to be regenerated. - valid_rect = true; - geometry_dirty = true; - rect_source = RectSource::Attribute; - } - } - - if (!valid_rect) - { - rect = {}; - rect_source = RectSource::None; - } - } -} // namespace RmlGui +ElementLuaTexture::ElementLuaTexture(const Rml::String& tag) + : Element(tag) + , dimensions(-1, -1) + , rect_source(RectSource::None) +{ + dimensions_scale = 1.0f; + geometry_dirty = false; + texture_dirty = true; + luaTextureHandle = 0; +} + +ElementLuaTexture::~ElementLuaTexture() = default; + +bool ElementLuaTexture::GetIntrinsicDimensions(Rml::Vector2f& _dimensions, float& _ratio) +{ + // Check if we need to reload the texture. + if (texture_dirty) + LoadTexture(); + + // Calculate the x dimension. + if (HasAttribute("width")) + dimensions.x = GetAttribute("width", -1); + else if (rect_source == RectSource::None) + dimensions.x = (float)texture.GetDimensions().x; + else + dimensions.x = rect.Width(); + + // Calculate the y dimension. + if (HasAttribute("height")) + dimensions.y = GetAttribute("height", -1); + else if (rect_source == RectSource::None) + dimensions.y = (float)texture.GetDimensions().y; + else + dimensions.y = rect.Height(); + + dimensions *= dimensions_scale; + + // Return the calculated dimensions. If this changes the size of the element, it will result in + // a call to 'onresize' below which will regenerate the geometry. + _dimensions = dimensions; + _ratio = dimensions.x / dimensions.y; + + return true; +} + +void ElementLuaTexture::OnRender() +{ + glBindTexture(GL_TEXTURE_2D, luaTextureHandle); + // Regenerate the geometry if required (this will be set if 'rect' changes but does not result + // in a resize). + if (geometry_dirty) + GenerateGeometry(); + + // Render the geometry beginning at this element's content region. + geometry.Render(GetAbsoluteOffset(Rml::BoxArea::Content).Round()); +} + +void ElementLuaTexture::OnAttributeChange(const Rml::ElementAttributes& changed_attributes) +{ + // Call through to the base element's OnAttributeChange(). + Element::OnAttributeChange(changed_attributes); + + bool dirty_layout = false; + + // Check for a changed 'src' attribute. If this changes, the old texture handle is released, + // forcing a reload when the layout is regenerated. + if (changed_attributes.find("src") != changed_attributes.end()) { + texture_dirty = true; + dirty_layout = true; + } + + // Check for a changed 'width' attribute. If this changes, a layout is forced which will + // recalculate the dimensions. + if (changed_attributes.find("width") != changed_attributes.end() || + changed_attributes.find("height") != changed_attributes.end()) { + dirty_layout = true; + } + + // Check for a change to the 'rect' attribute. If this changes, the coordinates are + // recomputed and a layout forced. If a sprite is set to source, then that will override any + // attribute. + if (changed_attributes.find("rect") != changed_attributes.end()) { + UpdateRect(); + + // Rectangle has changed; this will most likely result in a size change, so we need to force + // a layout. + dirty_layout = true; + } + + if (dirty_layout) + DirtyLayout(); +} + +void ElementLuaTexture::OnPropertyChange(const Rml::PropertyIdSet& changed_properties) +{ + Element::OnPropertyChange(changed_properties); + + if (changed_properties.Contains(Rml::PropertyId::ImageColor) || + changed_properties.Contains(Rml::PropertyId::Opacity)) { + GenerateGeometry(); + } +} + +void ElementLuaTexture::OnChildAdd(Element* child) +{ + // Load the texture once we have attached to the document so that it can immediately be found + // during the call to `Rml::GetTextureSourceList`. The texture won't actually be loaded from the + // backend before it is shown. However, only do this if we have an active context so that the + // dp-ratio can be retrieved. If there is no context now the texture loading will be deferred + // until the next layout update. + if (child == this && texture_dirty && GetContext()) { + LoadTexture(); + } +} + +void ElementLuaTexture::OnResize() +{ + GenerateGeometry(); +} + +void ElementLuaTexture::OnDpRatioChange() +{ + texture_dirty = true; + DirtyLayout(); +} + +void ElementLuaTexture::GenerateGeometry() +{ + // Release the old geometry before specifying the new vertices. + geometry.Release(true); + + Rml::Vector& vertices = geometry.GetVertices(); + Rml::Vector& indices = geometry.GetIndices(); + + vertices.resize(4); + indices.resize(6); + + // Generate the texture coordinates. + Rml::Vector2f texcoords[2]; + if (rect_source != RectSource::None) { + const auto texture_dimensions = + Rml::Vector2f(Rml::Math::Max(texture.GetDimensions(), Rml::Vector2i(1))); + texcoords[0] = rect.TopLeft() / texture_dimensions; + texcoords[1] = rect.BottomRight() / texture_dimensions; + } else { + texcoords[0] = Rml::Vector2f(0, 0); + texcoords[1] = Rml::Vector2f(1, 1); + } + + const Rml::ComputedValues& computed = GetComputedValues(); + + float opacity = computed.opacity(); + Rml::Colourb quad_colour = computed.image_color(); + quad_colour.alpha = (Rml::byte)(opacity * (float)quad_colour.alpha); + + Rml::Vector2f quad_size = GetBox().GetSize(Rml::BoxArea::Content).Round(); + + Rml::GeometryUtilities::GenerateQuad(&vertices[0], &indices[0], Rml::Vector2f(0, 0), quad_size, + quad_colour, texcoords[0], texcoords[1]); + + geometry_dirty = false; +} + +bool ElementLuaTexture::LoadTexture() +{ + texture_dirty = false; + geometry_dirty = true; + dimensions_scale = 1.0f; + + const float dp_ratio = Rml::ElementUtilities::GetDensityIndependentPixelRatio(this); + + const auto source_name = GetAttribute("src", ""); + if (source_name.empty()) { + texture = Rml::Texture(); + rect_source = RectSource::None; + return false; + } + + const Rml::TextureCallback callback = [this](const auto _renderInterface, const Rml::String& name, + Rml::TextureHandle& out_handle, + Rml::Vector2i& out_dimensions) mutable -> bool { + LuaMatTexture texUnit; + if (!LuaOpenGLUtils::ParseTextureImage(RmlGui::GetLuaState(), texUnit, name)) { + luaTextureHandle = 0; + return false; + } + + // Don't give Rml handles to external textures + out_handle = RenderInterface_GL3_Spring::TextureEnableWithoutBinding; + luaTextureHandle = texUnit.GetTextureID(); + + const auto [width, height, _z] = texUnit.GetSize(); + out_dimensions.x = width; + out_dimensions.y = height; + + return true; + }; + + texture.Set(source_name, callback); + + dimensions_scale = dp_ratio; + + // Set the texture onto our geometry object. + geometry.SetTexture(&texture); + + return true; +} + +void ElementLuaTexture::UpdateRect() +{ + bool valid_rect = false; + + if (const auto rect_string = GetAttribute("rect", ""); !rect_string.empty()) { + Rml::StringList coords_list; + Rml::StringUtilities::ExpandString(coords_list, rect_string, ' '); + + if (coords_list.size() != 4) { + Rml::Log::Message(Rml::Log::LT_WARNING, + "Element '%s' has an invalid 'rect' attribute; rect requires 4 " + "space-separated values, found %zu.", + GetAddress().c_str(), coords_list.size()); + } else { + const Rml::Vector2f position = {Rml::FromString(coords_list[0], 0.f), + Rml::FromString(coords_list[1], 0.f)}; + const Rml::Vector2f size = {Rml::FromString(coords_list[2], 0.f), + Rml::FromString(coords_list[3], 0.f)}; + rect = Rml::Rectanglef::FromPositionSize(position, size); + + // We have new, valid coordinates; force the geometry to be regenerated. + valid_rect = true; + geometry_dirty = true; + rect_source = RectSource::Attribute; + } + } + + if (!valid_rect) { + rect = {}; + rect_source = RectSource::None; + } +} + +} // namespace RmlGui diff --git a/rts/Rml/Elements/ElementLuaTexture.h b/rts/Rml/Elements/ElementLuaTexture.h index 8fcbd9444b..119bb62d39 100644 --- a/rts/Rml/Elements/ElementLuaTexture.h +++ b/rts/Rml/Elements/ElementLuaTexture.h @@ -64,8 +64,6 @@ namespace RmlGui /// Returns the element's inherent size. bool GetIntrinsicDimensions(Rml::Vector2f& dimensions, float& ratio) override; - static inline std::unordered_set texturesToNotDelete{}; - protected: /// Renders the image. void OnRender() override; @@ -98,8 +96,13 @@ namespace RmlGui void UpdateRect(); // The texture this element is rendering from. + // The Texture Handle of this should always be + // RenderInterface_GL3_Spring::TextureEnableWithoutBinding (aka Rml::TextureHandle(-1)) Rml::Texture texture; + // Handle to the externally provided texture to be used + Rml::TextureHandle luaTextureHandle; + // True if we need to refetch the texture's source from the element's attributes. bool texture_dirty;