diff options
Diffstat (limited to 'platform_opengl.c')
| -rw-r--r-- | platform_opengl.c | 541 |
1 files changed, 541 insertions, 0 deletions
diff --git a/platform_opengl.c b/platform_opengl.c new file mode 100644 index 0000000..abe197a --- /dev/null +++ b/platform_opengl.c @@ -0,0 +1,541 @@ +/* + * This is now a 7-pass shader + * + * 1. Phosphor persistence at source resolution (346×270) + * 2. Upscale and warp to viewport resolution without CRT effects → clean, sharp pixels + * 3. Extract bloom from upscaled clean pixels (white pixels, not RGB subpixels) + * 4. Blur the bloom vertically + * 5. Blur the bloom horizontally + * 6. CRT shader on original source with all the phosphor masks and scanlines + * 7. Composite warped bloom on top of the CRT effect + * + */ + +#include "shader.c" +#include "shader.h" + +INCBIN_SHADER_NOHEADER(vertex_shader, "#version 330", "shaders/gl_vertex.glsl"); +INCBIN_SHADER(fragment_shader, "#version 330", "shader.h", "shaders/gl_crt_fragment.glsl"); +INCBIN_SHADER_NOHEADER(phosphor_persistence_fragment, "#version 330", "shaders/gl_phosphor_persistence_fragment.glsl"); +INCBIN_SHADER_NOHEADER(upscale_warp_fragment, "#version 330", "shaders/gl_upscale_warp_fragment.glsl"); +INCBIN_SHADER_NOHEADER(bloom_extract_fragment, "#version 330", "shaders/gl_bloom_extract_fragment.glsl"); +INCBIN_SHADER_NOHEADER(bloom_blur_fragment, "#version 330", "shaders/gl_bloom_blur_fragment.glsl"); +INCBIN_SHADER_NOHEADER(bloom_composite_fragment, "#version 330", "shaders/gl_bloom_composite_fragment.glsl"); + +// [=]===^=[ cleanup_render_targets ]=============================================================^===[=] +static void cleanup_render_targets(void) { + // Don't delete state.texture or persistence textures here - they're managed by change_resolution() + glDeleteTextures(1, &state.crt_output_texture); + glDeleteTextures(1, &state.bloom_texture); + glDeleteTextures(1, &state.bloom_temp_texture); + glDeleteTextures(1, &state.bloom_warped_texture); + glDeleteTextures(1, &state.upscaled_source_texture); + glDeleteFramebuffers(1, &state.crt_fbo); + glDeleteFramebuffers(1, &state.bloom_fbo); + glDeleteFramebuffers(1, &state.bloom_temp_fbo); + glDeleteFramebuffers(1, &state.bloom_warp_fbo); + glDeleteFramebuffers(1, &state.upscaled_source_fbo); +} + +// [=]===^=[ setup_render_target ]================================================================^===[=] +static void setup_render_targets(void) { + cleanup_render_targets(); + + // Ensure source texture exists (might not if framebuffer_callback called before change_resolution) + if(state.texture == 0 && state.render_width > 0 && state.render_height > 0) { + GLfloat border_color[] = {0.0f, 0.0f, 0.0f, 1.0f}; + glGenTextures(1, &state.texture); + glBindTexture(GL_TEXTURE_2D, state.texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB8_ALPHA8, state.render_width, state.render_height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, NULL); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border_color); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + } + + // Ensure persistence textures exist (created in change_resolution, not recreated on viewport change) + if(state.persistence_texture == 0 && state.render_width > 0 && state.render_height > 0) { + glGenTextures(1, &state.persistence_texture); + glBindTexture(GL_TEXTURE_2D, state.persistence_texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB8_ALPHA8, state.render_width, state.render_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + glGenTextures(1, &state.persistence_output_texture); + glBindTexture(GL_TEXTURE_2D, state.persistence_output_texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB8_ALPHA8, state.render_width, state.render_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + glGenFramebuffers(1, &state.persistence_fbo); + glBindFramebuffer(GL_FRAMEBUFFER, state.persistence_fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, state.persistence_output_texture, 0); + if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + DEBUG_PRINT("Persistence FBO not complete!\n"); + } + glBindFramebuffer(GL_FRAMEBUFFER, 0); + } + + // CRT output texture (full viewport resolution) + glGenTextures(1, &state.crt_output_texture); + glBindTexture(GL_TEXTURE_2D, state.crt_output_texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB8_ALPHA8, state.viewport.w, state.viewport.h, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + // Bloom textures (half resolution for wider blur spread) + state.bloom_width = state.viewport.w / 1; + state.bloom_height = state.viewport.h / 1; + + glGenTextures(1, &state.bloom_texture); + glBindTexture(GL_TEXTURE_2D, state.bloom_texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, state.bloom_width, state.bloom_height, 0, GL_RGBA, GL_FLOAT, NULL); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + glGenTextures(1, &state.bloom_temp_texture); + glBindTexture(GL_TEXTURE_2D, state.bloom_temp_texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, state.bloom_width, state.bloom_height, 0, GL_RGBA, GL_FLOAT, NULL); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + GLfloat black_border[] = {0.0f, 0.0f, 0.0f, 0.0f}; + glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, black_border); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + glGenTextures(1, &state.bloom_warped_texture); + glBindTexture(GL_TEXTURE_2D, state.bloom_warped_texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, state.bloom_width, state.bloom_height, 0, GL_RGBA, GL_FLOAT, NULL); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + glBindTexture(GL_TEXTURE_2D, 0); + + // Create framebuffers + glGenFramebuffers(1, &state.crt_fbo); + glBindFramebuffer(GL_FRAMEBUFFER, state.crt_fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, state.crt_output_texture, 0); + if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + DEBUG_PRINT("CRT FBO not complete!\n"); + } + + glGenFramebuffers(1, &state.bloom_fbo); + glBindFramebuffer(GL_FRAMEBUFFER, state.bloom_fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, state.bloom_texture, 0); + if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + DEBUG_PRINT("Bloom FBO not complete!\n"); + } + + glGenFramebuffers(1, &state.bloom_temp_fbo); + glBindFramebuffer(GL_FRAMEBUFFER, state.bloom_temp_fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, state.bloom_temp_texture, 0); + if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + DEBUG_PRINT("Bloom temp FBO not complete!\n"); + } + + glGenFramebuffers(1, &state.bloom_warp_fbo); + glBindFramebuffer(GL_FRAMEBUFFER, state.bloom_warp_fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, state.bloom_warped_texture, 0); + if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + DEBUG_PRINT("Bloom warp FBO not complete!\n"); + } + + // Upscaled source FBO (viewport resolution, for bloom extraction) + glGenTextures(1, &state.upscaled_source_texture); + glBindTexture(GL_TEXTURE_2D, state.upscaled_source_texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB8_ALPHA8, state.viewport.w, state.viewport.h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + glGenFramebuffers(1, &state.upscaled_source_fbo); + glBindFramebuffer(GL_FRAMEBUFFER, state.upscaled_source_fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, state.upscaled_source_texture, 0); + if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + DEBUG_PRINT("Upscaled source FBO not complete!\n"); + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); +} + +// [=]===^=[ change_resolution ]=================================================================^===[=] +static void change_resolution(uint32_t new_width, uint32_t new_height) { + state.render_width = new_width; + state.render_height = new_height; + + // Recreate source texture + GLfloat border_color[] = {0.0f, 0.0f, 0.0f, 1.0f}; + glDeleteTextures(1, &state.texture); + glGenTextures(1, &state.texture); + glBindTexture(GL_TEXTURE_2D, state.texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB8_ALPHA8, state.render_width, state.render_height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, NULL); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border_color); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + // Recreate persistence textures (tied to source resolution) + glDeleteTextures(1, &state.persistence_texture); + glDeleteTextures(1, &state.persistence_output_texture); + glDeleteFramebuffers(1, &state.persistence_fbo); + + glGenTextures(1, &state.persistence_texture); + glBindTexture(GL_TEXTURE_2D, state.persistence_texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB8_ALPHA8, state.render_width, state.render_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + glGenTextures(1, &state.persistence_output_texture); + glBindTexture(GL_TEXTURE_2D, state.persistence_output_texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB8_ALPHA8, state.render_width, state.render_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + glGenFramebuffers(1, &state.persistence_fbo); + glBindFramebuffer(GL_FRAMEBUFFER, state.persistence_fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, state.persistence_output_texture, 0); + if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + DEBUG_PRINT("Persistence FBO not complete!\n"); + } + + glBindTexture(GL_TEXTURE_2D, 0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); +} + +// [=]===^=[ compile_shader ]==============================================================^===[=] +static GLuint compile_shader(GLenum shader_type, const char *shader_source) { + GLuint shader = glCreateShader(shader_type); + glShaderSource(shader, 1, &shader_source, 0); + glCompileShader(shader); + + GLint success; + GLchar info_log[512]; + glGetShaderiv(shader, GL_COMPILE_STATUS, &success); + if(!success) { + glGetShaderInfoLog(shader, sizeof(info_log), 0, info_log); + DEBUG_PRINT("%s shader compilation failed:\n%s\n", (shader_type == GL_VERTEX_SHADER) ? "Vertex" : "Fragment", info_log); + } + return shader; +} + +// [=]===^=[ opengl_setup ]================================================================^===[=] +static void opengl_setup(void) { + gl_loader(); + glEnable(GL_FRAMEBUFFER_SRGB); + glDisable(GL_DEPTH_TEST); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_CULL_FACE); + + // --- CRT Shader Setup --- + GLuint main_vertex_shader = compile_shader(GL_VERTEX_SHADER, vertex_shader_data); + GLuint main_fragment_shader = compile_shader(GL_FRAGMENT_SHADER, fragment_shader_data); + state.shader_program = glCreateProgram(); + glAttachShader(state.shader_program, main_vertex_shader); + glAttachShader(state.shader_program, main_fragment_shader); + glBindAttribLocation(state.shader_program, 0, "position"); + glBindAttribLocation(state.shader_program, 1, "texture_coord"); + glLinkProgram(state.shader_program); + glDeleteShader(main_vertex_shader); + glDeleteShader(main_fragment_shader); + + // Initialize CRTS shader parameters + state.contrast = 1.0f; + state.saturation = 0.0f; + state.brightness = 1.0f; + CrtsTone(state.tone_data, state.contrast, state.saturation, INPUT_THIN, INPUT_MASK); + + glUseProgram(state.shader_program); + state.uniform_resolution = glGetUniformLocation(state.shader_program, "resolution"); + state.uniform_src_image_size = glGetUniformLocation(state.shader_program, "src_image_size"); + state.uniform_brightness = glGetUniformLocation(state.shader_program, "brightness"); + state.uniform_tone = glGetUniformLocation(state.shader_program, "tone_data"); + state.uniform_crt_emulation = glGetUniformLocation(state.shader_program, "crt_emulation"); + state.uniform_apply_mask = glGetUniformLocation(state.shader_program, "apply_mask"); + state.uniform_sampler_location = glGetUniformLocation(state.shader_program, "iChannel0"); + + // --- Phosphor Persistence Shader Setup --- + GLuint persistence_vertex = compile_shader(GL_VERTEX_SHADER, vertex_shader_data); + GLuint persistence_fragment = compile_shader(GL_FRAGMENT_SHADER, phosphor_persistence_fragment_data); + state.persistence_program = glCreateProgram(); + glAttachShader(state.persistence_program, persistence_vertex); + glAttachShader(state.persistence_program, persistence_fragment); + glBindAttribLocation(state.persistence_program, 0, "position"); + glBindAttribLocation(state.persistence_program, 1, "texture_coord"); + glLinkProgram(state.persistence_program); + glDeleteShader(persistence_vertex); + glDeleteShader(persistence_fragment); + + // --- Upscale + Warp Shader Setup (for bloom path) --- + GLuint upscale_warp_vertex = compile_shader(GL_VERTEX_SHADER, vertex_shader_data); + GLuint upscale_warp_fragment = compile_shader(GL_FRAGMENT_SHADER, upscale_warp_fragment_data); + state.upscale_warp_program = glCreateProgram(); + glAttachShader(state.upscale_warp_program, upscale_warp_vertex); + glAttachShader(state.upscale_warp_program, upscale_warp_fragment); + glBindAttribLocation(state.upscale_warp_program, 0, "position"); + glBindAttribLocation(state.upscale_warp_program, 1, "texture_coord"); + glLinkProgram(state.upscale_warp_program); + glDeleteShader(upscale_warp_vertex); + glDeleteShader(upscale_warp_fragment); + + // --- Bloom Extract Shader Setup --- + GLuint bloom_extract_vertex = compile_shader(GL_VERTEX_SHADER, vertex_shader_data); + GLuint bloom_extract_fragment = compile_shader(GL_FRAGMENT_SHADER, bloom_extract_fragment_data); + state.bloom_extract_program = glCreateProgram(); + glAttachShader(state.bloom_extract_program, bloom_extract_vertex); + glAttachShader(state.bloom_extract_program, bloom_extract_fragment); + glBindAttribLocation(state.bloom_extract_program, 0, "position"); + glBindAttribLocation(state.bloom_extract_program, 1, "texture_coord"); + glLinkProgram(state.bloom_extract_program); + glDeleteShader(bloom_extract_vertex); + glDeleteShader(bloom_extract_fragment); + + glUseProgram(state.bloom_extract_program); + state.bloom_uniform_threshold = glGetUniformLocation(state.bloom_extract_program, "threshold"); + state.bloom_uniform_sampler = glGetUniformLocation(state.bloom_extract_program, "source"); + + // --- Bloom Blur Shader Setup --- + GLuint bloom_blur_vertex = compile_shader(GL_VERTEX_SHADER, vertex_shader_data); + GLuint bloom_blur_fragment = compile_shader(GL_FRAGMENT_SHADER, bloom_blur_fragment_data); + state.bloom_blur_program = glCreateProgram(); + glAttachShader(state.bloom_blur_program, bloom_blur_vertex); + glAttachShader(state.bloom_blur_program, bloom_blur_fragment); + glBindAttribLocation(state.bloom_blur_program, 0, "position"); + glBindAttribLocation(state.bloom_blur_program, 1, "texture_coord"); + glLinkProgram(state.bloom_blur_program); + glDeleteShader(bloom_blur_vertex); + glDeleteShader(bloom_blur_fragment); + + glUseProgram(state.bloom_blur_program); + state.blur_uniform_horizontal = glGetUniformLocation(state.bloom_blur_program, "horizontal"); + state.blur_uniform_sampler = glGetUniformLocation(state.bloom_blur_program, "source"); + + // --- Bloom Composite Shader Setup --- + GLuint bloom_composite_vertex = compile_shader(GL_VERTEX_SHADER, vertex_shader_data); + GLuint bloom_composite_fragment = compile_shader(GL_FRAGMENT_SHADER, bloom_composite_fragment_data); + state.bloom_composite_program = glCreateProgram(); + glAttachShader(state.bloom_composite_program, bloom_composite_vertex); + glAttachShader(state.bloom_composite_program, bloom_composite_fragment); + glBindAttribLocation(state.bloom_composite_program, 0, "position"); + glBindAttribLocation(state.bloom_composite_program, 1, "texture_coord"); + glLinkProgram(state.bloom_composite_program); + glDeleteShader(bloom_composite_vertex); + glDeleteShader(bloom_composite_fragment); + + glUseProgram(state.bloom_composite_program); + state.composite_uniform_bloom_strength = glGetUniformLocation(state.bloom_composite_program, "bloom_strength"); + state.composite_uniform_crt_sampler = glGetUniformLocation(state.bloom_composite_program, "crt_texture"); + state.composite_uniform_bloom_sampler = glGetUniformLocation(state.bloom_composite_program, "bloom_texture"); + + // Initialize bloom parameters + state.bloom_threshold = 0.8f; // Lower threshold = more pixels bloom + state.bloom_strength = 0.5f; // Higher strength = brighter bloom + state.persistence_decay = 0.02f; // 0.5 = subtle, 0.7 = more noticeable + + glGenVertexArrays(1, &state.vao); + glGenBuffers(1, &state.vbo); + glGenBuffers(1, &state.ebo); + glBindVertexArray(state.vao); + const float vertices[] = { + -1.0f, -1.0f, 0.0f, 0.0f, + 1.0f, -1.0f, 1.0f, 0.0f, + 1.0f, 1.0f, 1.0f, 1.0f, + -1.0f, 1.0f, 0.0f, 1.0f + }; + static const unsigned int indices[] = { 0, 1, 2, 2, 3, 0 }; + glBindBuffer(GL_ARRAY_BUFFER, state.vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, state.ebo); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float))); + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + glBindVertexArray(0); +} + +// [=]===^=[ render_frame ]=================================================================^===[=] +__attribute__((always_inline)) +static inline void render_frame(void) { + // Check if viewport changed and we need to recreate render targets + if(state.viewport_changed) { + setup_render_targets(); + state.viewport_changed = 0; + } + + // Upload new frame to texture + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, state.texture); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, state.render_width, state.render_height, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, display_buffer); + + // Apply phosphor persistence (subtle trails on bright objects) + // Render: state.texture (current upload) + persistence_texture (previous) -> persistence_output_texture + glBindFramebuffer(GL_FRAMEBUFFER, state.persistence_fbo); + glViewport(0, 0, state.render_width, state.render_height); + + glUseProgram(state.persistence_program); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, state.texture); // Current frame just uploaded + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, state.persistence_texture); // Previous frame + glUniform1i(glGetUniformLocation(state.persistence_program, "current_frame"), 0); + glUniform1i(glGetUniformLocation(state.persistence_program, "previous_frame"), 1); + glUniform1f(glGetUniformLocation(state.persistence_program, "decay"), state.persistence_decay); + + glBindVertexArray(state.vao); + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + + // Copy output back to persistence_texture for next frame + glBindTexture(GL_TEXTURE_2D, state.persistence_texture); + glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, state.render_width, state.render_height); + + // Use persistence output as source for rest of rendering pipeline + GLuint source_texture = state.persistence_output_texture; + + if(state.toggle_bloom) { + // ========== PASS 1: Upscale + Warp source (matches CRT geometry) ========== + glBindFramebuffer(GL_FRAMEBUFFER, state.upscaled_source_fbo); + glViewport(0, 0, state.viewport.w, state.viewport.h); + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + + glUseProgram(state.upscale_warp_program); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, source_texture); + // Use linear filtering for upscale+warp to reduce aliasing in bloom + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glUniform1i(glGetUniformLocation(state.upscale_warp_program, "source"), 0); + glUniform2f(glGetUniformLocation(state.upscale_warp_program, "resolution"), (float)state.viewport.w, (float)state.viewport.h); + glUniform2f(glGetUniformLocation(state.upscale_warp_program, "src_image_size"), (float)state.render_width, (float)state.render_height); + + glBindVertexArray(state.vao); + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + + // Restore nearest filtering for CRT shader + glBindTexture(GL_TEXTURE_2D, source_texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + // ========== PASS 2: Extract bright pixels from upscaled source ========== + glBindFramebuffer(GL_FRAMEBUFFER, state.bloom_temp_fbo); + glViewport(0, 0, state.bloom_width, state.bloom_height); + glClear(GL_COLOR_BUFFER_BIT); + + glUseProgram(state.bloom_extract_program); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, state.upscaled_source_texture); + glUniform1f(state.bloom_uniform_threshold, state.bloom_threshold); + glUniform1i(state.bloom_uniform_sampler, 0); + + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + + // ========== PASS 3: Horizontal blur ========== + glBindFramebuffer(GL_FRAMEBUFFER, state.bloom_fbo); + glViewport(0, 0, state.bloom_width, state.bloom_height); + glClear(GL_COLOR_BUFFER_BIT); + + glUseProgram(state.bloom_blur_program); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, state.bloom_temp_texture); + glUniform1i(state.blur_uniform_horizontal, 1); + glUniform1i(state.blur_uniform_sampler, 0); + + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + + // ========== PASS 4: Vertical blur ========== + glBindFramebuffer(GL_FRAMEBUFFER, state.bloom_temp_fbo); + glViewport(0, 0, state.bloom_width, state.bloom_height); + glClear(GL_COLOR_BUFFER_BIT); + + glUseProgram(state.bloom_blur_program); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, state.bloom_texture); + glUniform1i(state.blur_uniform_horizontal, 0); + glUniform1i(state.blur_uniform_sampler, 0); + + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + + // ========== PASS 5: CRT Shader to offscreen FBO ========== + glBindFramebuffer(GL_FRAMEBUFFER, state.crt_fbo); + glViewport(0, 0, state.viewport.w, state.viewport.h); + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + + glUseProgram(state.shader_program); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, source_texture); + glUniform2f(state.uniform_src_image_size, (float)state.render_width, (float)state.render_height); + glUniform2f(state.uniform_resolution, (float)state.viewport.w, (float)state.viewport.h); + glUniform1f(state.uniform_brightness, state.brightness); + glUniform4f(state.uniform_tone, state.tone_data[0], state.tone_data[1], state.tone_data[2], state.tone_data[3]); + glUniform1i(state.uniform_crt_emulation, state.toggle_crt_emulation); + glUniform1i(state.uniform_apply_mask, 0); // Don't apply mask - composite will do it + glUniform1i(state.uniform_sampler_location, 0); + + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + + // ========== PASS 6: Composite CRT + Bloom to screen ========== + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glViewport(state.viewport.x, state.viewport.y, state.viewport.w, state.viewport.h); + glClear(GL_COLOR_BUFFER_BIT); + + glUseProgram(state.bloom_composite_program); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, state.crt_output_texture); + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, state.bloom_temp_texture); + glUniform1f(state.composite_uniform_bloom_strength, state.bloom_strength); + glUniform1i(state.composite_uniform_crt_sampler, 0); + glUniform1i(state.composite_uniform_bloom_sampler, 1); + glUniform2f(glGetUniformLocation(state.bloom_composite_program, "bloom_size"), (float)state.bloom_width, (float)state.bloom_height); + glUniform2f(glGetUniformLocation(state.bloom_composite_program, "src_image_size"), (float)state.render_width, (float)state.render_height); + glUniform2f(glGetUniformLocation(state.bloom_composite_program, "viewport_size"), (float)state.viewport.w, (float)state.viewport.h); + + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + + glBindVertexArray(0); + glUseProgram(0); + + } else { + // ========== Single pass: CRT Shader directly to screen ========== + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glViewport(state.viewport.x, state.viewport.y, state.viewport.w, state.viewport.h); + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + + glUseProgram(state.shader_program); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, source_texture); + glUniform2f(state.uniform_src_image_size, (float)state.render_width, (float)state.render_height); + glUniform2f(state.uniform_resolution, (float)state.viewport.w, (float)state.viewport.h); + glUniform1f(state.uniform_brightness, state.brightness); + glUniform4f(state.uniform_tone, state.tone_data[0], state.tone_data[1], state.tone_data[2], state.tone_data[3]); + glUniform1i(state.uniform_crt_emulation, state.toggle_crt_emulation); + glUniform1i(state.uniform_apply_mask, 1); // Apply mask in CRT shader when bloom disabled + glUniform1i(state.uniform_sampler_location, 0); + + glBindVertexArray(state.vao); + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + + glBindVertexArray(0); + glUseProgram(0); + } +}
\ No newline at end of file |
