render: migrate rendering pipeline from OpenGL to GLES 2.0
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I55267367c8001ffc5ceac2c64015a7686a6a6964
This commit is contained in:
parent
f032d3723d
commit
f4275cb0f8
1 changed files with 87 additions and 62 deletions
149
src/render.c
149
src/render.c
|
|
@ -187,7 +187,6 @@ static void calculate_texture_coords(chroma_scale_mode_t scale_mode,
|
|||
|
||||
// Vertex shader for simple texture rendering
|
||||
static const char *vertex_shader_source =
|
||||
"#version 120\n"
|
||||
"attribute vec2 position;\n"
|
||||
"attribute vec2 texcoord;\n"
|
||||
"varying vec2 v_texcoord;\n"
|
||||
|
|
@ -198,7 +197,7 @@ static const char *vertex_shader_source =
|
|||
|
||||
// Fragment shader for simple texture rendering
|
||||
static const char *fragment_shader_source =
|
||||
"#version 120\n"
|
||||
"precision mediump float;\n"
|
||||
"varying vec2 v_texcoord;\n"
|
||||
"uniform sampler2D texture;\n"
|
||||
"void main() {\n"
|
||||
|
|
@ -214,7 +213,7 @@ static const float vertices[] = {
|
|||
-1.0f, 1.0f, 0.0f, 0.0f, // top left
|
||||
};
|
||||
|
||||
static const unsigned int indices[] = {
|
||||
static const unsigned short indices[] = {
|
||||
0, 1, 2, // first triangle
|
||||
2, 3, 0 // second triangle
|
||||
};
|
||||
|
|
@ -284,17 +283,29 @@ static GLuint create_shader_program(void) {
|
|||
}
|
||||
|
||||
// Initialize OpenGL resources for output
|
||||
static int init_gl_resources(chroma_output_t *output) {
|
||||
static int init_gl_resources(chroma_state_t *state, chroma_output_t *output) {
|
||||
if (!output || output->gl_resources_initialized) {
|
||||
return CHROMA_OK;
|
||||
}
|
||||
|
||||
// Create shader prog
|
||||
output->shader_program = create_shader_program();
|
||||
if (!output->shader_program) {
|
||||
chroma_log("ERROR", "Failed to create shader program for output %u",
|
||||
// Use shared shader program from state if available
|
||||
if (state && state->shader_program != 0) {
|
||||
output->shader_program = state->shader_program;
|
||||
chroma_log("DEBUG", "Using shared shader program for output %u",
|
||||
output->id);
|
||||
return CHROMA_ERROR_EGL;
|
||||
} else {
|
||||
// Create shader program
|
||||
output->shader_program = create_shader_program();
|
||||
if (!output->shader_program) {
|
||||
chroma_log("ERROR", "Failed to create shader program for output %u",
|
||||
output->id);
|
||||
return CHROMA_ERROR_EGL;
|
||||
}
|
||||
// Store in state for sharing
|
||||
if (state) {
|
||||
state->shader_program = output->shader_program;
|
||||
chroma_log("DEBUG", "Created shared shader program");
|
||||
}
|
||||
}
|
||||
|
||||
// Create and setup VBO/EBO
|
||||
|
|
@ -308,6 +319,10 @@ static int init_gl_resources(chroma_output_t *output) {
|
|||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices,
|
||||
GL_STATIC_DRAW);
|
||||
|
||||
// Unbind buffers
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
||||
|
||||
output->texture_id = 0; // will be created when image is assigned
|
||||
output->vbo_dirty = true; // VBO needs initial update
|
||||
output->gl_resources_initialized = true;
|
||||
|
|
@ -370,9 +385,24 @@ static int update_texture_from_image(chroma_output_t *output,
|
|||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter);
|
||||
|
||||
// Upload texture data (always RGBA now)
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image->width, image->height, 0,
|
||||
GL_RGBA, GL_UNSIGNED_BYTE, image->data);
|
||||
// Upload texture data with appropriate format based on channels
|
||||
GLint internal_format;
|
||||
GLenum format;
|
||||
if (image->channels == 4) {
|
||||
internal_format = GL_RGBA;
|
||||
format = GL_RGBA;
|
||||
} else if (image->channels == 3) {
|
||||
internal_format = GL_RGB;
|
||||
format = GL_RGB;
|
||||
} else if (image->channels == 2) {
|
||||
internal_format = GL_LUMINANCE_ALPHA;
|
||||
format = GL_LUMINANCE_ALPHA;
|
||||
} else {
|
||||
internal_format = GL_LUMINANCE;
|
||||
format = GL_LUMINANCE;
|
||||
}
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, internal_format, image->width, image->height,
|
||||
0, format, GL_UNSIGNED_BYTE, image->data);
|
||||
|
||||
// Generate mipmaps for trilinear filtering if they're needed
|
||||
if (needs_mipmaps) {
|
||||
|
|
@ -385,38 +415,24 @@ static int update_texture_from_image(chroma_output_t *output,
|
|||
// Mark this output as having uploaded its texture
|
||||
output->texture_uploaded = true;
|
||||
|
||||
// Free system RAM copy only when ALL outputs using this image have uploaded
|
||||
// to GPU
|
||||
if (image->data) {
|
||||
// Count total outputs using this image and how many have uploaded
|
||||
int total_using = 0;
|
||||
int uploaded_count = 0;
|
||||
|
||||
chroma_state_t *state = output->state;
|
||||
for (int i = 0; i < state->output_count; i++) {
|
||||
if (state->outputs[i].active && state->outputs[i].image == image) {
|
||||
total_using++;
|
||||
if (state->outputs[i].texture_uploaded) {
|
||||
uploaded_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only free image data when ALL outputs using it have uploaded
|
||||
if (total_using > 0 && uploaded_count >= total_using) {
|
||||
size_t freed_bytes = (size_t)image->width * (size_t)image->height *
|
||||
(size_t)image->channels;
|
||||
stbi_image_free(image->data);
|
||||
image->data = NULL;
|
||||
chroma_log("INFO",
|
||||
"Freed %.2f MB of image data after all %d outputs uploaded to "
|
||||
"GPU: %s",
|
||||
(double)freed_bytes / (1024.0 * 1024.0), total_using,
|
||||
image->path);
|
||||
chroma_log_resource_deallocation("image_data", freed_bytes,
|
||||
"post-gpu-upload");
|
||||
chroma_log_memory_stats("post-gpu-upload");
|
||||
}
|
||||
// Decrement reference count and free system RAM when all outputs
|
||||
// have uploaded their textures
|
||||
image->ref_count--;
|
||||
if (image->ref_count <= 0 && image->data) {
|
||||
size_t freed_bytes =
|
||||
(size_t)image->width * (size_t)image->height * (size_t)image->channels;
|
||||
stbi_image_free(image->data);
|
||||
image->data = NULL;
|
||||
chroma_log("INFO", "Freed %.2f MB of image data after GPU upload: %s",
|
||||
(double)freed_bytes / (1024.0 * 1024.0), image->path);
|
||||
chroma_log_resource_deallocation("image_data", freed_bytes,
|
||||
"post-gpu-upload");
|
||||
chroma_log_memory_stats("post-gpu-upload");
|
||||
} else {
|
||||
chroma_log("DEBUG",
|
||||
"Keeping image data for %s (ref_count: %d, waiting for %d more "
|
||||
"outputs)",
|
||||
image->path, image->ref_count, image->ref_count);
|
||||
}
|
||||
|
||||
chroma_log("DEBUG", "Updated texture for output %u (%dx%d)", output->id,
|
||||
|
|
@ -425,7 +441,8 @@ static int update_texture_from_image(chroma_output_t *output,
|
|||
}
|
||||
|
||||
// Cleanup OpenGL resources for output
|
||||
static void cleanup_gl_resources(chroma_output_t *output) {
|
||||
static void cleanup_gl_resources(chroma_state_t *state,
|
||||
chroma_output_t *output) {
|
||||
if (!output || !output->gl_resources_initialized) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -436,8 +453,12 @@ static void cleanup_gl_resources(chroma_output_t *output) {
|
|||
output->texture_id = 0;
|
||||
}
|
||||
|
||||
// Don't delete shared shader program here, it's managed by state
|
||||
if (output->shader_program != 0) {
|
||||
glDeleteProgram(output->shader_program);
|
||||
// Only delete if this output owns the program (not shared)
|
||||
if (state && state->shader_program != output->shader_program) {
|
||||
glDeleteProgram(output->shader_program);
|
||||
}
|
||||
output->shader_program = 0;
|
||||
}
|
||||
|
||||
|
|
@ -466,9 +487,9 @@ static int choose_egl_config(EGLDisplay display, EGLConfig *config) {
|
|||
EGL_BLUE_SIZE,
|
||||
8,
|
||||
EGL_ALPHA_SIZE,
|
||||
8,
|
||||
0,
|
||||
EGL_RENDERABLE_TYPE,
|
||||
EGL_OPENGL_BIT,
|
||||
EGL_OPENGL_ES2_BIT,
|
||||
EGL_NONE};
|
||||
|
||||
EGLint num_configs;
|
||||
|
|
@ -508,9 +529,9 @@ int chroma_egl_init(chroma_state_t *state) {
|
|||
chroma_log("INFO", "EGL initialized: version %d.%d", major, minor);
|
||||
chroma_log_memory_stats("post-egl-init");
|
||||
|
||||
// Bind OpenGL API
|
||||
if (!eglBindAPI(EGL_OPENGL_API)) {
|
||||
chroma_log("ERROR", "Failed to bind OpenGL API: 0x%04x", eglGetError());
|
||||
// Bind OpenGL ES API for better efficiency on embedded/GPU drivers
|
||||
if (!eglBindAPI(EGL_OPENGL_ES_API)) {
|
||||
chroma_log("ERROR", "Failed to bind OpenGL ES API: 0x%04x", eglGetError());
|
||||
chroma_egl_cleanup(state);
|
||||
return CHROMA_ERROR_EGL;
|
||||
}
|
||||
|
|
@ -521,9 +542,8 @@ int chroma_egl_init(chroma_state_t *state) {
|
|||
return CHROMA_ERROR_EGL;
|
||||
}
|
||||
|
||||
// Create EGL context
|
||||
EGLint context_attributes[] = {EGL_CONTEXT_MAJOR_VERSION, 2,
|
||||
EGL_CONTEXT_MINOR_VERSION, 1, EGL_NONE};
|
||||
// Create EGL context for GLES 2.0
|
||||
EGLint context_attributes[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};
|
||||
|
||||
state->egl_context = eglCreateContext(state->egl_display, state->egl_config,
|
||||
EGL_NO_CONTEXT, context_attributes);
|
||||
|
|
@ -543,6 +563,12 @@ void chroma_egl_cleanup(chroma_state_t *state) {
|
|||
return;
|
||||
}
|
||||
|
||||
// Clean up shared shader program while context is still current
|
||||
if (state->shader_program != 0) {
|
||||
glDeleteProgram(state->shader_program);
|
||||
state->shader_program = 0;
|
||||
}
|
||||
|
||||
if (state->egl_display != EGL_NO_DISPLAY) {
|
||||
eglMakeCurrent(state->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
|
||||
EGL_NO_CONTEXT);
|
||||
|
|
@ -644,8 +670,8 @@ void chroma_surface_destroy(chroma_output_t *output) {
|
|||
return;
|
||||
}
|
||||
|
||||
// Clean up OpenGL resources first
|
||||
cleanup_gl_resources(output);
|
||||
// Clean up OpenGL resources first. We use output's back-reference to state
|
||||
cleanup_gl_resources(output->state, output);
|
||||
|
||||
if (output->egl_surface != EGL_NO_SURFACE) {
|
||||
eglDestroySurface(eglGetCurrentDisplay(), output->egl_surface);
|
||||
|
|
@ -667,6 +693,8 @@ void chroma_surface_destroy(chroma_output_t *output) {
|
|||
output->surface = NULL;
|
||||
}
|
||||
|
||||
output->configured = false;
|
||||
|
||||
// Log surface destruction
|
||||
size_t surface_size = (size_t)output->width * (size_t)output->height * 4;
|
||||
chroma_log_resource_deallocation("egl_surface", surface_size,
|
||||
|
|
@ -689,7 +717,7 @@ int chroma_render_wallpaper(chroma_state_t *state, chroma_output_t *output) {
|
|||
return CHROMA_ERROR_EGL;
|
||||
}
|
||||
|
||||
if (init_gl_resources(output) != CHROMA_OK) {
|
||||
if (init_gl_resources(state, output) != CHROMA_OK) {
|
||||
return CHROMA_ERROR_EGL;
|
||||
}
|
||||
|
||||
|
|
@ -762,7 +790,7 @@ int chroma_render_wallpaper(chroma_state_t *state, chroma_output_t *output) {
|
|||
4 * sizeof(float), (void *)(2 * sizeof(float)));
|
||||
|
||||
// Draw
|
||||
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
|
||||
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0);
|
||||
|
||||
// Unbind resources
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
|
|
@ -770,16 +798,13 @@ int chroma_render_wallpaper(chroma_state_t *state, chroma_output_t *output) {
|
|||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glUseProgram(0);
|
||||
|
||||
// Swap buffers
|
||||
// Swap buffers; this implicitly commits the surface for EGL on Wayland
|
||||
if (!eglSwapBuffers(state->egl_display, output->egl_surface)) {
|
||||
chroma_log("ERROR", "Failed to swap buffers for output %u: 0x%04x",
|
||||
output->id, eglGetError());
|
||||
return CHROMA_ERROR_EGL;
|
||||
}
|
||||
|
||||
// Commit surface
|
||||
wl_surface_commit(output->surface);
|
||||
|
||||
chroma_log("DEBUG", "Rendered wallpaper to output %u (cached resources)",
|
||||
output->id);
|
||||
return CHROMA_OK;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue