mikke89 / RmlUi

RmlUi - The HTML/CSS User Interface library evolved
https://mikke89.github.io/RmlUiDoc/
MIT License
2.57k stars 295 forks source link

Tga file loading does not work correctly, but not in all circumstances (and only in Windows!). #612

Open germandiagogomez opened 2 months ago

germandiagogomez commented 2 months ago

Dear RmlUi project people,

I am having some trouble with Tga file loading. Let me explain the problem in a semi-structured way.

The problem

  1. A tga file is loaded in a game screen and this tga file is showing up correctly. (menuscreen)
menuscreen
  1. 4 players connect in total and the rest of the 3 players are shown in the place below, marked in the images as 1, 2 and 3 in red.
onlinermarked

Images 1, 2 and 3 show correctly in MacOS but not in Windows. The Windows version looks like this:

defect

As you can see, the images in 1 and 2 show a thin line of the image and the rest in white color.

What I checked so far

The tga image loaded in the menu screen and the images loaded in the players connect screen are the same bit by bit. I checked with a hex editor and a diff viewer that this is the case.

Some rml code

For the menuscreen, which always works correctly, I have code like this in rml

<div class="playerbox transition-border-color"><img id="avatar-img"
data-attr-src="menuModel.avatarPath"/></div>

For the online players connected screen:

<div class="other-players-boxes">
                <div class="other-playerbox">
                    <img data-attr-src="onlinerooms.playersAvatars[0]"
                         data-class-selectable="onlinerooms.playersNames[0] != '' && onlinerooms.connectionCodeCreator"/>
                    <p>{{onlinerooms.playersNames[0]}}</p>
                </div>
                <div class="other-playerbox">
                    <img data-attr-src="onlinerooms.playersAvatars[1]"
                         data-class-selectable="onlinerooms.playersNames[1] != '' && onlinerooms.connectionCodeCreator"/>
                    <p>{{onlinerooms.playersNames[1]}}</p>
                </div>
                <div class="other-playerbox">
                    <img data-attr-src="onlinerooms.playersAvatars[2]"
                         data-class-selectable="onlinerooms.playersNames[2] != '' && onlinerooms.connectionCodeCreator"/>
                    <p>{{onlinerooms.playersNames[2]}}</p>
                </div>
</div>

What I suspect (but I do not know!)

The images for the players are set in a loop (Wren language but I guess it is understandable):


for (userId in notif.joined) {
    _gameView.setProperty("playersAvatars[%(i)]", "/%(userId).tga")
    i = i + 1
}

Could this be interferring in some way if the rml model variables are refreshed very quickly? In MacOS it does not happen though.

mikke89 commented 2 months ago

Hey, I appreciate the detailed post. It sounds like it could be related to #239.

First of all, which version/commit of the library are you using?

And which backends are you using for each of the platforms?

germandiagogomez commented 2 months ago

Hello @mikke89,

I am using RmlUi 5.1. The backend is OpenGL3 with SDL.

What strikes me the most is that the same image can be loaded correctly in one place (menuscreen) and not in the other (connect players screen).

I convert those tga files from png files actually, using SDL for loading, done by myself in another piece of code. But as I said, the converted tga images show correctly unconditionally in the menu screen and then not correctly in the next screen at all.

Please find the Windows capture in the top post of this thread that I edited. Following you have my renderer code:

#include "RmlUi_Renderer_GL3.h"
#include "Guinyote/Exception.hpp"
#include "GuinyoteConfig.hpp"

#include <glbinding/gl33core/gl.h>

#include <fmt/format.h>

#include <RmlUi/Core/Core.h>
#include <RmlUi/Core/FileInterface.h>
#include <RmlUi/Core/Log.h>
#include <RmlUi/Core/Platform.h>
#include <string.h>

#include <map>

using namespace gl;

constexpr char const *const kgl_Version = [] {
  if constexpr (GuinyoteConfig::kGuinyotePlatform ==
                GuinyoteConfig::Platform::Emscripten)
    return "#version 300 es";
  return "#version 330 core";
}();

static const std::string shader_main_vertex = fmt::format(R"--vertexshader({}
uniform vec2 _translate;
uniform mat4 _transform;

layout (location = 0) in vec2 inPosition;
layout (location = 1) in vec4 inColor0;
layout (location = 2) in vec2 inTexCoord0;

out vec2 fragTexCoord;
out vec4 fragColor;

void main() {{
    fragTexCoord = inTexCoord0;
    fragColor = inColor0;

    vec2 translatedPos = inPosition + _translate.xy;
    vec4 outPos = _transform * vec4(translatedPos, 0, 1);

    gl_Position = outPos;
}})--vertexshader",
                                                          kgl_Version);

static const auto shader_main_fragment_texture =
    fmt::format(R"--fragmentshader({}
precision mediump float;

uniform sampler2D _tex;
in vec2 fragTexCoord;
in vec4 fragColor;

out vec4 finalColor;

void main() {{
    vec4 texColor = texture(_tex, fragTexCoord);
    finalColor = fragColor * texColor;
}}
)--fragmentshader",
                kgl_Version);

static const auto shader_main_fragment_color = fmt::format(R"--fragmentshader({}
precision mediump float;

in vec2 fragTexCoord;
in vec4 fragColor;

out vec4 finalColor;

void main() {{
    finalColor = fragColor;
}}
)--fragmentshader",
                                                           kgl_Version);

namespace Gfx {

struct CompiledGeometryData {
  Rml::TextureHandle texture;
  GLuint vao;
  GLuint vbo;
  GLuint ibo;
  GLsizei draw_count;
};

struct ProgramData {
  GLuint id;
  std::unordered_map<std::string, GLint> uniforms;
};

struct ShadersData {
  ProgramData program_color;
  ProgramData program_texture;
  GLuint shader_main_vertex;
  GLuint shader_main_fragment_color;
  GLuint shader_main_fragment_texture;
};

static void CheckGLError(const char *operation_name) {
#ifdef RMLUI_DEBUG
  GLenum error_code = glGetError();
  if (error_code != GL_NO_ERROR) {
    static const Rml::Pair<GLenum, const char *> error_names[] = {
        {GL_INVALID_ENUM, "GL_INVALID_ENUM"},
        {GL_INVALID_VALUE, "GL_INVALID_VALUE"},
        {GL_INVALID_OPERATION, "GL_INVALID_OPERATION"},
        {GL_OUT_OF_MEMORY, "GL_OUT_OF_MEMORY"}};
    const char *error_str = "''";
    for (auto &err : error_names) {
      if (err.first == error_code) {
        error_str = err.second;
        break;
      }
    }
    Rml::Log::Message(Rml::Log::LT_ERROR,
                      "OpenGL error during %s. Error code 0x%x (%s).",
                      operation_name, error_code, error_str);
  }
#endif
  (void)operation_name;
}

// Create the shader, 'shader_type' is either GL_VERTEX_SHADER or
// GL_FRAGMENT_SHADER.
static GLuint CreateShader(GLenum shader_type, const char *code_string) {
  // FIXME: when coming from MenuScreen -> PlayMatchScreen, glCreateShader fails
  // with GL_INVALID_ENUM
  GLuint id = glCreateShader(shader_type);
  CheckGLError("A");
  glShaderSource(id, 1, (const GLchar **)&code_string, NULL);
  glCompileShader(id);
  GLint status = 0;
  glGetShaderiv(id, GL_COMPILE_STATUS, &status);
  if (status == 0) {
    GLint info_log_length = 0;
    glGetShaderiv(id, GL_INFO_LOG_LENGTH, &info_log_length);
    char *info_log_string = new char[info_log_length + 1];
    glGetShaderInfoLog(id, info_log_length, NULL, info_log_string);

    Rml::Log::Message(Rml::Log::LT_ERROR,
                      "Compile failure in OpenGL shader: %s", info_log_string);
    delete[] info_log_string;
    glDeleteShader(id);
    return 0;
  }
  CheckGLError("CreateShader");
  return id;
}

static bool CreateProgram(GLuint vertex_shader, GLuint fragment_shader,
                          ProgramData &out_program) {
  GLuint id = glCreateProgram();
  RMLUI_ASSERT(id);

  // BindAttribLocations(id);

  glAttachShader(id, vertex_shader);
  glAttachShader(id, fragment_shader);

  glLinkProgram(id);

  glDetachShader(id, vertex_shader);
  glDetachShader(id, fragment_shader);

  GLint status = 0;
  glGetProgramiv(id, GL_LINK_STATUS, &status);
  if (status == 0) {
    GLint info_log_length = 0;
    glGetProgramiv(id, GL_INFO_LOG_LENGTH, &info_log_length);
    char *info_log_string = new char[info_log_length + 1];
    glGetProgramInfoLog(id, info_log_length, NULL, info_log_string);

    Rml::Log::Message(Rml::Log::LT_ERROR, "OpenGL program linking failure: %s",
                      info_log_string);
    delete[] info_log_string;
    glDeleteProgram(id);
    return false;
  }
  out_program = {};
  out_program.id = id;
  CheckGLError("CreateProgram");

  return true;
}

static bool CreateShaders(ShadersData &out_shaders) {
  out_shaders = {};
  GLuint &main_vertex = out_shaders.shader_main_vertex;
  GLuint &main_fragment_color = out_shaders.shader_main_fragment_color;
  GLuint &main_fragment_texture = out_shaders.shader_main_fragment_texture;

  main_vertex = CreateShader(GL_VERTEX_SHADER, shader_main_vertex.c_str());
  if (!main_vertex) {
    Rml::Log::Message(Rml::Log::LT_ERROR,
                      "Could not create OpenGL shader: 'shader_main_vertex'.");
    return false;
  }
  main_fragment_color =
      CreateShader(GL_FRAGMENT_SHADER, shader_main_fragment_color.c_str());
  if (!main_fragment_color) {
    Rml::Log::Message(
        Rml::Log::LT_ERROR,
        "Could not create OpenGL shader: 'shader_main_fragment_color'.");
    return false;
  }
  main_fragment_texture =
      CreateShader(GL_FRAGMENT_SHADER, shader_main_fragment_texture.c_str());
  if (!main_fragment_texture) {
    Rml::Log::Message(
        Rml::Log::LT_ERROR,
        "Could not create OpenGL shader: 'shader_main_fragment_texture'.");
    return false;
  }

  if (!CreateProgram(main_vertex, main_fragment_color,
                     out_shaders.program_color)) {
    Rml::Log::Message(Rml::Log::LT_ERROR,
                      "Could not create OpenGL program: 'program_color'.");
    return false;
  }
  if (!CreateProgram(main_vertex, main_fragment_texture,
                     out_shaders.program_texture)) {
    Rml::Log::Message(Rml::Log::LT_ERROR,
                      "Could not create OpenGL program: 'program_texture'.");
    return false;
  }

  return true;
}

static void DestroyShaders(ShadersData &shaders) {
  glDeleteProgram(shaders.program_color.id);
  glDeleteProgram(shaders.program_texture.id);

  glDeleteShader(shaders.shader_main_vertex);
  glDeleteShader(shaders.shader_main_fragment_color);
  glDeleteShader(shaders.shader_main_fragment_texture);

  shaders = {};
}

} // namespace Gfx

RenderInterface_GL3::RenderInterface_GL3() {
  shaders = Rml::MakeUnique<Gfx::ShadersData>();
  if (!Gfx::CreateShaders(*shaders)) {
    GUINYOTE_EXCEPTION_THROW(Guinyote::GuinyoteException,
                             "Cannot create shaders");
  }
}

RenderInterface_GL3::~RenderInterface_GL3() {
  if (shaders)
    Gfx::DestroyShaders(*shaders);
}

void RenderInterface_GL3::SetViewport(int width, int height) {
  viewport_width = width;
  viewport_height = height;
}

void RenderInterface_GL3::BeginFrame() {
  RMLUI_ASSERT(viewport_width >= 0 && viewport_height >= 0);
  glEnable(GL_BLEND);
  glBlendEquation(GL_FUNC_ADD);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

  glDisable(GL_DEPTH_TEST);
  glEnable(GL_STENCIL_TEST);
  glStencilFunc(GL_ALWAYS, 1, GLuint(-1));
  glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);

  glDisable(GL_CULL_FACE);
  projection = Rml::Matrix4f::ProjectOrtho(
      0, (float)viewport_width, (float)viewport_height, 0, -10000, 10000);
  SetTransform(nullptr);
}

void RenderInterface_GL3::EndFrame() {}

void RenderInterface_GL3::RenderGeometry(Rml::Vertex *vertices,
                                         int num_vertices, int *indices,
                                         int num_indices,
                                         const Rml::TextureHandle texture,
                                         const Rml::Vector2f &translation) {
  Rml::CompiledGeometryHandle geometry =
      CompileGeometry(vertices, num_vertices, indices, num_indices, texture);

  if (geometry) {
    RenderCompiledGeometry(geometry, translation);
    ReleaseCompiledGeometry(geometry);
  }
}

Rml::CompiledGeometryHandle
RenderInterface_GL3::CompileGeometry(Rml::Vertex *vertices, int num_vertices,
                                     int *indices, int num_indices,
                                     Rml::TextureHandle texture) {
  enum Attributes { Position = 0, Color = 1, TexCoord = 2 };

  constexpr GLenum draw_usage = GL_STATIC_DRAW;

  GLuint vao = 0;
  GLuint vbo = 0;
  GLuint ibo = 0;

  glGenVertexArrays(1, &vao);
  glGenBuffers(1, &vbo);
  glGenBuffers(1, &ibo);
  glBindVertexArray(vao);

  glBindBuffer(GL_ARRAY_BUFFER, vbo);
  glBufferData(GL_ARRAY_BUFFER, sizeof(Rml::Vertex) * num_vertices,
               (const void *)vertices, draw_usage);

  glEnableVertexAttribArray(Position);
  glVertexAttribPointer(Position, 2, GL_FLOAT, GL_FALSE, sizeof(Rml::Vertex),
                        (const GLvoid *)(offsetof(Rml::Vertex, position)));

  glEnableVertexAttribArray(Color);
  glVertexAttribPointer(Color, 4, GL_UNSIGNED_BYTE, GL_TRUE,
                        sizeof(Rml::Vertex),
                        (const GLvoid *)(offsetof(Rml::Vertex, colour)));

  glEnableVertexAttribArray(TexCoord);
  glVertexAttribPointer(TexCoord, 2, GL_FLOAT, GL_FALSE, sizeof(Rml::Vertex),
                        (const GLvoid *)(offsetof(Rml::Vertex, tex_coord)));

  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
  glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(int) * num_indices,
               (const void *)indices, draw_usage);
  glBindVertexArray(0);

  Gfx::CheckGLError("CompileGeometry");

  Gfx::CompiledGeometryData *geometry = new Gfx::CompiledGeometryData;
  geometry->texture = texture;
  geometry->vao = vao;
  geometry->vbo = vbo;
  geometry->ibo = ibo;
  geometry->draw_count = num_indices;

  return (Rml::CompiledGeometryHandle)geometry;
}

void RenderInterface_GL3::RenderCompiledGeometry(
    Rml::CompiledGeometryHandle handle, const Rml::Vector2f &translation) {
  Gfx::CompiledGeometryData *geometry = (Gfx::CompiledGeometryData *)handle;
  if (geometry->texture) {
    glUseProgram(shaders->program_texture.id);
    static constexpr std::array uniforms = {"_translate", "_transform", "_tex"};
    for (std::size_t i = 0; i < uniforms.size(); ++i) {
      auto theUniformLoc =
          glGetUniformLocation(shaders->program_texture.id, uniforms[i]);
      if (theUniformLoc != -1)
        shaders->program_texture.uniforms[uniforms[i]] = theUniformLoc;
    }

    if (geometry->texture != TextureEnableWithoutBinding) {
      glBindTexture(GL_TEXTURE_2D, (GLuint)geometry->texture);
      glUniform1i(shaders->program_texture.uniforms.at("_tex"), 0);
    }
    SubmitTransformUniform(ProgramId::Texture,
                           shaders->program_texture.uniforms.at("_transform"));
    glUniform2fv(shaders->program_texture.uniforms.at("_translate"), 1,
                 &translation.x);
  } else {
    glUseProgram(shaders->program_color.id);
    static constexpr std::array uniforms = {"_translate", "_transform", "_tex"};
    for (std::size_t i = 0; i < uniforms.size(); ++i) {
      auto theUniformLoc =
          glGetUniformLocation(shaders->program_color.id, uniforms[i]);
      if (theUniformLoc != -1)
        shaders->program_color.uniforms[uniforms[i]] = theUniformLoc;
    }
    // glBindTexture(GL_TEXTURE_2D, 0);

    SubmitTransformUniform(ProgramId::Color,
                           shaders->program_color.uniforms.at("_transform"));
    glUniform2fv(shaders->program_color.uniforms.at("_translate"), 1,
                 &translation.x);
  }

  glBindVertexArray(geometry->vao);
  glDrawElements(GL_TRIANGLES, geometry->draw_count, GL_UNSIGNED_INT,
                 (const GLvoid *)0);
  glBindVertexArray(0);

  Gfx::CheckGLError("RenderCompiledGeometry");
}

void RenderInterface_GL3::ReleaseCompiledGeometry(
    Rml::CompiledGeometryHandle handle) {
  Gfx::CompiledGeometryData *geometry = (Gfx::CompiledGeometryData *)handle;

  glDeleteVertexArrays(1, &geometry->vao);
  glDeleteBuffers(1, &geometry->vbo);
  glDeleteBuffers(1, &geometry->ibo);

  delete geometry;
}

void RenderInterface_GL3::EnableScissorRegion(bool enable) {
  ScissoringState new_state = ScissoringState::Disable;

  if (enable)
    new_state = (transform_active ? ScissoringState::Stencil
                                  : ScissoringState::Scissor);

  if (new_state != scissoring_state) {
    // Disable old
    if (scissoring_state == ScissoringState::Scissor)
      glDisable(GL_SCISSOR_TEST);
    else if (scissoring_state == ScissoringState::Stencil)
      glStencilFunc(GL_ALWAYS, 1, GLuint(-1));

    // Enable new
    if (new_state == ScissoringState::Scissor)
      glEnable(GL_SCISSOR_TEST);
    else if (new_state == ScissoringState::Stencil)
      glStencilFunc(GL_EQUAL, 1, GLuint(-1));

    scissoring_state = new_state;
  }
}

void RenderInterface_GL3::SetScissorRegion(int x, int y, int width,
                                           int height) {
  if (transform_active) {
    const float left = float(x);
    const float right = float(x + width);
    const float top = float(y);
    const float bottom = float(y + height);

    Rml::Vertex vertices[4];
    vertices[0].position = {left, top};
    vertices[1].position = {right, top};
    vertices[2].position = {right, bottom};
    vertices[3].position = {left, bottom};

    int indices[6] = {0, 2, 1, 0, 3, 2};

    glClear(GL_STENCIL_BUFFER_BIT);
    glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
    glStencilFunc(GL_ALWAYS, 1, GLuint(-1));
    glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);

    RenderGeometry(vertices, 4, indices, 6, 0, Rml::Vector2f(0, 0));

    glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
    glStencilFunc(GL_EQUAL, 1, GLuint(-1));
    glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
  } else {
    glScissor(x, viewport_height - (y + height), width, height);
  }
}

// Set to byte packing, or the compiler will expand our struct, which means it
// won't read correctly from file
#pragma pack(1)
struct TGAHeader {
  char idLength;
  char colourMapType;
  char dataType;
  short int colourMapOrigin;
  short int colourMapLength;
  char colourMapDepth;
  short int xOrigin;
  short int yOrigin;
  short int width;
  short int height;
  char bitsPerPixel;
  char imageDescriptor;
};
// Restore packing
#pragma pack()

bool RenderInterface_GL3::LoadTexture(Rml::TextureHandle &texture_handle,
                                      Rml::Vector2i &texture_dimensions,
                                      const Rml::String &source) {
  Rml::FileInterface *file_interface = Rml::GetFileInterface();
  Rml::FileHandle file_handle = file_interface->Open(source);
  if (!file_handle) {
    return false;
  }

  file_interface->Seek(file_handle, 0, SEEK_END);
  size_t buffer_size = file_interface->Tell(file_handle);
  file_interface->Seek(file_handle, 0, SEEK_SET);

  if (buffer_size <= sizeof(TGAHeader)) {
    Rml::Log::Message(Rml::Log::LT_ERROR,
                      "Texture file size is smaller than TGAHeader, file is "
                      "not a valid TGA image.");
    file_interface->Close(file_handle);
    return false;
  }

  using Rml::byte;
  byte *buffer = new byte[buffer_size];
  file_interface->Read(buffer, buffer_size, file_handle);
  file_interface->Close(file_handle);

  TGAHeader header;
  memcpy(&header, buffer, sizeof(TGAHeader));

  int color_mode = header.bitsPerPixel / 8;
  int image_size =
      header.width * header.height * 4; // We always make 32bit textures

  if (header.dataType != 2) {
    Rml::Log::Message(Rml::Log::LT_ERROR,
                      "Only 24/32bit uncompressed TGAs are supported.");
    delete[] buffer;
    return false;
  }

  // Ensure we have at least 3 colors
  if (color_mode < 3) {
    Rml::Log::Message(Rml::Log::LT_ERROR,
                      "Only 24 and 32bit textures are supported.");
    delete[] buffer;
    return false;
  }

  const byte *image_src = buffer + sizeof(TGAHeader);
  byte *image_dest = new byte[image_size];

  // Targa is BGR, swap to RGB and flip Y axis
  for (long y = 0; y < header.height; y++) {
    long read_index = y * header.width * color_mode;
    long write_index = ((header.imageDescriptor & 32) != 0)
                           ? read_index
                           : (header.height - y - 1) * header.width * 4;
    for (long x = 0; x < header.width; x++) {
      image_dest[write_index] = image_src[read_index + 2];
      image_dest[write_index + 1] = image_src[read_index + 1];
      image_dest[write_index + 2] = image_src[read_index];
      if (color_mode == 4) {
        const int alpha = image_src[read_index + 3];
#ifdef RMLUI_SRGB_PREMULTIPLIED_ALPHA
        image_dest[write_index + 0] =
            (image_dest[write_index + 0] * alpha) / 255;
        image_dest[write_index + 1] =
            (image_dest[write_index + 1] * alpha) / 255;
        image_dest[write_index + 2] =
            (image_dest[write_index + 2] * alpha) / 255;
#endif
        image_dest[write_index + 3] = (byte)alpha;
      } else {
        image_dest[write_index + 3] = 255;
      }

      write_index += 4;
      read_index += color_mode;
    }
  }

  texture_dimensions.x = header.width;
  texture_dimensions.y = header.height;

  bool success =
      GenerateTexture(texture_handle, image_dest, texture_dimensions);

  delete[] image_dest;
  delete[] buffer;

  return success;
}

bool RenderInterface_GL3::GenerateTexture(
    Rml::TextureHandle &texture_handle, const Rml::byte *source,
    const Rml::Vector2i &source_dimensions) {
  GLuint texture_id = 0;
  glGenTextures(1, &texture_id);
  if (texture_id == 0) {
    Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to generate texture.");
    return false;
  }

  glBindTexture(GL_TEXTURE_2D, texture_id);

  GLenum internal_format = GL_RGBA8;
  glTexImage2D(GL_TEXTURE_2D, 0, internal_format, source_dimensions.x,
               source_dimensions.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, source);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

  texture_handle = (Rml::TextureHandle)texture_id;

  return true;
}

void RenderInterface_GL3::ReleaseTexture(Rml::TextureHandle texture_handle) {
  glDeleteTextures(1, (GLuint *)&texture_handle);
}

void RenderInterface_GL3::SetTransform(const Rml::Matrix4f *new_transform) {
  transform_active = (new_transform != nullptr);
  transform =
      projection * (new_transform ? *new_transform : Rml::Matrix4f::Identity());
  transform_dirty_state = ProgramId::All;
}

void RenderInterface_GL3::SubmitTransformUniform(ProgramId program_id,
                                                 int uniform_location) {
  if ((int)program_id)
    glUniformMatrix4fv(uniform_location, 1, false, transform.data());
}
mikke89 commented 2 months ago

Hm, if it's working in one place and not in others, then to me this indicates some sort state that is not being correctly set/reset. I know SDL also sets some OpenGL state, so be on the lookout for that.

I suggest that you make a capture in RenderDoc. Then it should be easy to see if any OpenGL state is different from when the image renders correctly and when it doesn't.

germandiagogomez commented 2 months ago

Hm, if it's working in one place and not in others, then to me this indicates some sort state that is not being correctly set/reset. I know SDL also sets some OpenGL state, so be on the lookout for that.

Then why it would work in MacOS but not in Windows? The state would be erroneous on both I assume? Maybe I am assuming too much and there is implementation divergence here.

I suggest that you make a capture in RenderDoc. Then it should be easy to see if any OpenGL state is different from when the image renders correctly and when it doesn't.

Yes, I used it a couple of times before. Probably it is a good idea. Will give a try to this when it reaches priority again. That is within mid next week.

mikke89 commented 1 month ago

I frankly don't have any good explanation for the difference on macOS vs Windows, but RenderDoc should give you a better understanding in any case.