Closed germandiagogomez closed 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?
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());
}
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.
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.
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.
Doing some cleanup of old issues, and closing this due to lack of activity. Please let us know if you did figure this one out in the end.
@mikke89 it was something bad ony side that I could fix though I do not recall what. So yes, please close.
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
Images 1, 2 and 3 show correctly in MacOS but not in Windows. The Windows version looks like this:
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
For the online players connected screen:
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):
Could this be interferring in some way if the rml model variables are refreshed very quickly? In MacOS it does not happen though.