From 7c4ca1dc6e5fb61664c6a3fea75b9fd5014534a2 Mon Sep 17 00:00:00 2001 From: Allen Webster Date: Wed, 26 Apr 2023 19:32:42 -0700 Subject: [PATCH] precise srgb->linear conversion experiment; color gradients experiment --- ...standalone_srgb_antialiasing_experiments.c | 515 +++++++++++++++++- 1 file changed, 497 insertions(+), 18 deletions(-) diff --git a/src/standalone_srgb_antialiasing_experiments.c b/src/standalone_srgb_antialiasing_experiments.c index 82dc2c3..7e3d8af 100644 --- a/src/standalone_srgb_antialiasing_experiments.c +++ b/src/standalone_srgb_antialiasing_experiments.c @@ -1,5 +1,21 @@ #include +//////////////////////////////// +//- Experiment Configuration + +// When this is 0 colors in vertex attributes and uniforms +// that are converted (sRGB -> Linear) are converted using +// the approximate (sRGB -> Linear) (f(x)=x^2.2); When this +// is 1 those colors are converted using an accurate +// (sRGB -> Linear) conversion. +// This has no effect on colors sampled from sRGB textures +// which are not converted by shader code, but by the OpenGL +// implementation itself. By default this is zero to show the +// difference between the approximate and accurate +// conversions in test #3. +#define USE_ACCURATE_CONVERSIONS_IN_SHADER 0 + + //////////////////////////////// //- Mini-Base @@ -53,6 +69,8 @@ static void wgl_helper_end_render(void); typedef struct Vertex{ F32 px; F32 py; + F32 uvx; + F32 uvy; F32 cr; F32 cg; F32 cb; @@ -62,7 +80,9 @@ typedef struct Vertex{ static void opengl_render_init(void); -static void opengl_draw_test_geometry(Vertex *v, U64 count); +static void opengl_draw_basic_geometry(Vertex *v, U64 count); +static void opengl_draw_srgb_in_geometry(Vertex *v, U64 count); +static void opengl_draw_texture_geometry(Vertex *v, U64 count, U32 texture); //////////////////////////////// @@ -88,10 +108,30 @@ typedef unsigned char GLboolean; #define GL_TRIANGLES 0x0004 +#define GL_TEXTURE_2D 0x0DE1 + +#define GL_UNSIGNED_BYTE 0x1401 #define GL_FLOAT 0x1406 +#define GL_DEPTH_COMPONENT 0x1902 +#define GL_RED 0x1903 +#define GL_RGB 0x1907 +#define GL_RGBA 0x1908 + +#define GL_NEAREST 0x2600 +#define GL_LINEAR 0x2601 + +#define GL_TEXTURE_MAG_FILTER 0x2800 +#define GL_TEXTURE_MIN_FILTER 0x2801 +#define GL_TEXTURE_WRAP_S 0x2802 +#define GL_TEXTURE_WRAP_T 0x2803 + #define GL_MULTISAMPLE 0x809D +#define GL_CLAMP_TO_EDGE 0x812F + +#define GL_TEXTURE0 0x84C0 + #define GL_STREAM_DRAW 0x88E0 #define GL_ARRAY_BUFFER 0x8892 @@ -104,6 +144,17 @@ typedef unsigned char GLboolean; #define GL_INFO_LOG_LENGTH 0x8B84 #define GL_SRGB 0x8C40 +#define GL_SRGB8 0x8C41 + +#define GL_READ_FRAMEBUFFER 0x8CA8 +#define GL_DRAW_FRAMEBUFFER 0x8CA9 + +#define GL_COLOR_ATTACHMENT0 0x8CE0 + +#define GL_FRAMEBUFFER 0x8D40 +#define GL_FRAMEBUFFER_SRGB 0x8DB9 + +#define GL_TEXTURE_2D_MULTISAMPLE 0x9100 //- gl func x-list @@ -132,6 +183,7 @@ X(glGenVertexArrays, void, (GLsizei n, GLuint *arrays)) \ X(glDeleteVertexArrays, void, (GLsizei n, const GLuint *arrays)) \ X(glBindVertexArray, void, (GLuint array)) \ /* textures */ \ +X(glGenTextures, void, (GLsizei n, GLuint *textures)) \ X(glActiveTexture, void, (GLenum texture)) \ X(glDeleteTextures, void, (GLsizei n, const GLuint *textures)) \ X(glBindTexture, void, (GLenum target, GLuint texture)) \ @@ -213,7 +265,7 @@ static GLuint opengl_helper_make_program(GLuint *shaders, U32 count); //////////////////////////////// //- OpenGL Render -static char test_vshader[] = +static char basic_vshader[] = "#version 330\n" "uniform vec2 u_view_xform;\n" "layout (location = 0) in vec2 v_p;\n" @@ -226,52 +278,210 @@ static char test_vshader[] = "}\n" ; -static char test_fshader[] = +static char basic_fshader[] = "#version 330\n" "in vec4 f_c;\n" "out vec4 out_color;\n" "void main(){\n" "out_color = f_c;\n" "}\n" -""; +; -GLuint test_program = 0; -GLint test_u_view_xform = -1; +static GLuint basic_program = 0; +static GLint basic_u_view_xform = -1; + + + +static char srgb_in_vshader[] = +"#version 330\n" +"\n" + +#if USE_ACCURATE_CONVERSIONS_IN_SHADER +"float lin_from_srgb(float x){\n" +"float r = 0;\n" +"if (x <= 0.04045) r = x/12.92;\n" +"else r = pow(((x + 0.055)/1.055), 2.4);\n" +"return(r);\n" +"}\n" +"\n" +"float srgb_from_lin(float x){\n" +"float r = 0;\n" +"if (x <= 0.0031308) r = x*12.92;\n" +"else r = pow(x, 1/2.4)*1.055 - 0.055;\n" +"return(r);\n" +"}\n" +#endif + +"\n" +"uniform vec2 u_view_xform;\n" +"layout (location = 0) in vec2 v_p;\n" +"layout (location = 1) in vec4 v_c;\n" +"out vec4 f_c;\n" +"void main(){\n" +"vec2 norm_pos = v_p*u_view_xform + vec2(-1.0, -1.0);\n" + +#if USE_ACCURATE_CONVERSIONS_IN_SHADER +"float lin_r = lin_from_srgb(v_c.r);\n" +"float lin_g = lin_from_srgb(v_c.g);\n" +"float lin_b = lin_from_srgb(v_c.b);\n" +#else +"float lin_r = pow(v_c.r, 2.2);\n" +"float lin_g = pow(v_c.g, 2.2);\n" +"float lin_b = pow(v_c.b, 2.2);\n" +#endif + + +"gl_Position = vec4(norm_pos, 0.0, 1.0);\n" +"f_c = vec4(lin_r, lin_g, lin_b, v_c.a);\n" +"}\n" +; + +static char srgb_in_fshader[] = +"#version 330\n" +"in vec4 f_c;\n" +"out vec4 out_color;\n" +"void main(){\n" +"out_color = f_c;\n" +"}\n" +; + +static GLuint srgb_in_program = 0; +static GLint srgb_in_u_view_xform = -1; + + + +static char texture_vshader[] = +"#version 330\n" +"uniform vec2 u_view_xform;\n" +"layout (location = 0) in vec2 v_p;\n" +"layout (location = 1) in vec2 v_uv;\n" +"out vec2 f_uv;\n" +"void main(){\n" +"vec2 norm_pos = v_p*u_view_xform + vec2(-1.0, -1.0);\n" +"gl_Position = vec4(norm_pos, 0.0, 1.0);\n" +"f_uv = v_uv;\n" +"}\n" +; + +static char texture_fshader[] = +"#version 330\n" +"uniform sampler2D u_texture;\n" +"in vec2 f_uv;\n" +"out vec4 out_color;\n" +"void main(){\n" +"out_color = vec4(texture(u_texture, f_uv).rgb, 1);\n" +"}\n" +; + +static GLuint texture_program = 0; +static GLint texture_u_view_xform = -1; +static GLint texture_u_texture = -1; + + + +static GLuint canvas_texture = 0; +static GLuint canvas_framebuffer = 0; + +static GLuint test3_texture = 0; static void opengl_render_init(void){ - //- setup test shader program + //- setup basic shader program { GLuint shaders[2] = {0}; - shaders[0] = opengl_helper_make_shader(test_vshader, GL_VERTEX_SHADER); - shaders[1] = opengl_helper_make_shader(test_fshader, GL_FRAGMENT_SHADER); - test_program = opengl_helper_make_program(shaders, 2); - test_u_view_xform = glGetUniformLocation(test_program, "u_view_xform"); + shaders[0] = opengl_helper_make_shader(basic_vshader, GL_VERTEX_SHADER); + shaders[1] = opengl_helper_make_shader(basic_fshader, GL_FRAGMENT_SHADER); + basic_program = opengl_helper_make_program(shaders, 2); + basic_u_view_xform = glGetUniformLocation(basic_program, "u_view_xform"); } + //- setup "srgb in" shader program + { + GLuint shaders[2] = {0}; + shaders[0] = opengl_helper_make_shader(srgb_in_vshader, GL_VERTEX_SHADER); + shaders[1] = opengl_helper_make_shader(srgb_in_fshader, GL_FRAGMENT_SHADER); + srgb_in_program = opengl_helper_make_program(shaders, 2); + srgb_in_u_view_xform = glGetUniformLocation(basic_program, "u_view_xform"); + } + + //- setup texture shader program + { + GLuint shaders[2] = {0}; + shaders[0] = opengl_helper_make_shader(texture_vshader, GL_VERTEX_SHADER); + shaders[1] = opengl_helper_make_shader(texture_fshader, GL_FRAGMENT_SHADER); + texture_program = opengl_helper_make_program(shaders, 2); + texture_u_view_xform = glGetUniformLocation(texture_program, "u_view_xform"); + texture_u_texture = glGetUniformLocation(texture_program, "u_texture"); + } + + //- vertex array object { GLuint vao = 0; glGenVertexArrays(1, &vao); glBindVertexArray(vao); } + //- vertex array buffer { GLuint vertex_buffer = 0; glGenBuffers(1, &vertex_buffer); glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer); } + //- canvas texture + { + glGenTextures(1, &canvas_texture); + glBindTexture(GL_TEXTURE_2D, canvas_texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 2048, 2048, 0, + GL_RGB, GL_FLOAT, 0); + + 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); + } + + //- canvas framebuffer + { + glGenFramebuffers(1, &canvas_framebuffer); + glBindFramebuffer(GL_FRAMEBUFFER, canvas_framebuffer); + glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + canvas_texture, 0); + } + + //- test3 texture + { + U8 test3_img[16*3] = {0}; + for (U32 i = 0; i < 16; i += 1){ + U32 i3 = 3*i; + for (U32 j = 0; j < 3; j += 1){ + test3_img[i3 + j] = 0x08 + 0x10*i; + } + } + + glGenTextures(1, &test3_texture); + glBindTexture(GL_TEXTURE_2D, test3_texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB8, 16, 1, 0, + GL_RGB, GL_UNSIGNED_BYTE, test3_img); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } + + //- check for setup errors if (glGetError() != 0){ error_message("error in opengl renderer initialization"); } } static void -opengl_draw_test_geometry(Vertex *v, U64 count){ +opengl_draw_basic_geometry(Vertex *v, U64 count){ glBufferData(GL_ARRAY_BUFFER, sizeof(*v)*count, v, GL_STREAM_DRAW); - glUseProgram(test_program); - glUniform2f(test_u_view_xform, 2.f/graphics_w, 2.f/graphics_h); + glUseProgram(basic_program); + glUniform2f(basic_u_view_xform, 2.f/graphics_w, 2.f/graphics_h); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 2, GL_FLOAT, 0, @@ -284,6 +494,46 @@ opengl_draw_test_geometry(Vertex *v, U64 count){ glDrawArrays(GL_TRIANGLES, 0, count); } +static void +opengl_draw_srgb_in_geometry(Vertex *v, U64 count){ + glBufferData(GL_ARRAY_BUFFER, sizeof(*v)*count, v, GL_STREAM_DRAW); + + glUseProgram(srgb_in_program); + glUniform2f(srgb_in_u_view_xform, 2.f/graphics_w, 2.f/graphics_h); + + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 2, GL_FLOAT, 0, + sizeof(Vertex), PtrOffsetOf(Vertex, px)); + + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 4, GL_FLOAT, 0, + sizeof(Vertex), PtrOffsetOf(Vertex, cr)); + + glDrawArrays(GL_TRIANGLES, 0, count); +} + +static void +opengl_draw_texture_geometry(Vertex *v, U64 count, U32 texture){ + glBufferData(GL_ARRAY_BUFFER, sizeof(*v)*count, v, GL_STREAM_DRAW); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, texture); + + glUseProgram(texture_program); + glUniform2f(texture_u_view_xform, 2.f/graphics_w, 2.f/graphics_h); + glUniform1i(texture_u_texture, 0); + + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 2, GL_FLOAT, 0, + sizeof(Vertex), PtrOffsetOf(Vertex, px)); + + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 2, GL_FLOAT, 0, + sizeof(Vertex), PtrOffsetOf(Vertex, uvx)); + + glDrawArrays(GL_TRIANGLES, 0, count); +} + //////////////////////////////// @@ -315,14 +565,24 @@ WinMain(HINSTANCE hInstance, } // screen layout - F32 margin = 10.f; F32 center_x = graphics_w*0.5f; F32 divider_half_w = 3.f; F32 divider_min_x = (F32)(S32)(center_x - divider_half_w); F32 divider_max_x = divider_min_x + 2.f*divider_half_w; - // render + F32 lside_grad_x0 = 352.f; + F32 lside_grad_x1 = lside_grad_x0 + 256.f; + F32 rside_grad_x0 = 1312.f; + F32 rside_grad_x1 = rside_grad_x0 + 256.f; + + F32 test1_y = graphics_h - 10.f; + F32 test2_y = graphics_h - 60.f; + F32 test3_y = graphics_h - 110.f; + F32 test4_y = graphics_h - 160.f; + + // render to canvas frame buffer { + glBindFramebuffer(GL_FRAMEBUFFER, canvas_framebuffer); glViewport(0, 0, (S32)graphics_w, (S32)graphics_h); // black background everywhere @@ -345,9 +605,228 @@ WinMain(HINSTANCE hInstance, v[i].ca = 1.f; } - opengl_draw_test_geometry(v, 6); + opengl_draw_basic_geometry(v, 6); } + // two sided test + for (U32 test_idx = 0; test_idx < 2; test_idx += 1){ + F32 grad_x0 = lside_grad_x0; + F32 grad_x1 = lside_grad_x1; + if (test_idx == 1){ + grad_x0 = rside_grad_x0; + grad_x1 = rside_grad_x1; + } + + // 1: draw gradient + { + F32 grad_y1 = test1_y; + F32 grad_y0 = test1_y - 40.f; + + Vertex v[6]; + v[0].px = grad_x0; v[0].py = grad_y0; + v[1].px = grad_x0; v[1].py = grad_y1; + v[2].px = grad_x1; v[2].py = grad_y0; + v[3].px = v[1].px; v[3].py = v[1].py; + v[4].px = v[2].px; v[4].py = v[2].py; + v[5].px = grad_x1; v[5].py = grad_y1; + for (U32 i = 0; i < 6; i += 1){ + if (v[i].px == grad_x0){ + v[i].cr = 1.f; v[i].cg = 1.f; v[i].cb = 1.f; v[i].ca = 1.f; + } + else{ + v[i].cr = 0.f; v[i].cg = 0.f; v[i].cb = 0.f; v[i].ca = 1.f; + } + } + + opengl_draw_basic_geometry(v, 6); + } + + // 2: draw extremes and midpoint + { + F32 box_x[4] = {0}; + box_x[0] = grad_x0; + box_x[1] = (F32)(S32)((grad_x0*2.f + grad_x1)/3.f); + box_x[2] = (F32)(S32)((grad_x0 + grad_x1*2.f)/3.f); + box_x[3] = grad_x1; + + F32 val[3] = { 1.f, 0.5f, 0.f }; + + F32 grad_y1 = test2_y; + F32 grad_y0 = test2_y - 40.f; + + for (U32 j = 0; j < 3; j += 1){ + F32 box_x0 = box_x[j]; + F32 box_x1 = box_x[j + 1]; + + F32 valj = val[j]; + + Vertex v[6]; + v[0].px = box_x0; v[0].py = grad_y0; + v[1].px = box_x0; v[1].py = grad_y1; + v[2].px = box_x1; v[2].py = grad_y0; + v[3].px = v[1].px; v[3].py = v[1].py; + v[4].px = v[2].px; v[4].py = v[2].py; + v[5].px = box_x1; v[5].py = grad_y1; + for (U32 i = 0; i < 6; i += 1){ + v[i].cr = valj; v[i].cg = valj; v[i].cb = valj; v[i].ca = 1.f; + } + + opengl_draw_basic_geometry(v, 6); + } + + } + + // 3: compare x^2.2 with texture sampling for (sRGB -> Linear) + if (test_idx == 1){ + F32 grad_y2 = test3_y; + F32 grad_y1 = test3_y - 20.f; + F32 grad_y0 = test3_y - 40.f; + + // convert (sRGB -> Linear) by texture sampling + { + Vertex v[6]; + v[0].px = grad_x0; v[0].py = grad_y1; + v[0].uvx = 0.f; v[0].uvy = 1.f; + v[1].px = grad_x0; v[1].py = grad_y2; + v[1].uvx = 0.f; v[1].uvy = 0.f; + v[2].px = grad_x1; v[2].py = grad_y1; + v[2].uvx = 1.f; v[2].uvy = 1.f; + v[5].px = grad_x1; v[5].py = grad_y2; + v[5].uvx = 1.f; v[5].uvy = 0.f; + + v[3] = v[1]; + v[4] = v[2]; + + opengl_draw_texture_geometry(v, 6, test3_texture); + } + + // convert (sRGB -> Linear) by pow(x, 2.2) in shader + { + F32 delta_x = (grad_x1 - grad_x0)/16.f; + + for (U32 i = 0; i < 16; i += 1){ + F32 l = (F32)(S32)(grad_x0 + i*delta_x); + F32 r = (F32)(S32)(l + delta_x); + + Vertex v[6]; + v[0].px = l; v[0].py = grad_y0; + v[1].px = l; v[1].py = grad_y1; + v[2].px = r; v[2].py = grad_y0; + v[5].px = r; v[5].py = grad_y1; + v[3] = v[1]; + v[4] = v[2]; + + F32 val = (F32)(0x08 + 0x10*i)/255.f; + for (U32 j = 0; j < 6; j += 1){ + v[j].cr = val; + v[j].cg = val; + v[j].cb = val; + v[j].ca = 1.f; + } + + opengl_draw_srgb_in_geometry(v, 6); + } + } + + } + + // 4: color gradient + { + F32 color_pairs[12][3] = { + {1.f, 0.f, 0.f}, {1.f, 1.f, 0.f}, + {1.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, + {0.f, 1.f, 0.f}, {0.f, 1.f, 1.f}, + {0.f, 1.f, 1.f}, {0.f, 0.f, 1.f}, + {0.f, 0.f, 1.f}, {1.f, 0.f, 1.f}, + {1.f, 0.f, 1.f}, {1.f, 0.f, 0.f}, + }; + + F32 cursor_y = test4_y; + + for (U32 j = 0; j < 12; j += 2){ + F32 grad_y1 = cursor_y; + F32 grad_y0 = cursor_y - 20.f; + cursor_y -= 30.f; + + F32 *col[2] = {0}; + col[0] = color_pairs[j]; + col[1] = color_pairs[j + 1]; + + Vertex v[6]; + v[0].px = grad_x0; v[0].py = grad_y0; + v[1].px = grad_x0; v[1].py = grad_y1; + v[2].px = grad_x1; v[2].py = grad_y0; + v[3].px = v[1].px; v[3].py = v[1].py; + v[4].px = v[2].px; v[4].py = v[2].py; + v[5].px = grad_x1; v[5].py = grad_y1; + for (U32 i = 0; i < 6; i += 1){ + U32 k = 0; + if (v[i].px == grad_x1){ + k = 1; + } + v[i].cr = col[k][0]; + v[i].cg = col[k][1]; + v[i].cb = col[k][2]; + v[i].ca = 1.f; + } + + if (test_idx == 0){ + opengl_draw_basic_geometry(v, 6); + } + else{ + opengl_draw_srgb_in_geometry(v, 6); + } + } + } + + } + + } + + // resolve canvas to screen + { + glBindFramebuffer(GL_READ_FRAMEBUFFER, canvas_framebuffer); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + + S32 w = (S32)graphics_w; + S32 h = (S32)graphics_h; + + // left side resolve *without* GL_FRAMEBUFFER_SRGB + glBlitFramebuffer(0, 0, w/2, h, + 0, 0, w/2, h, + GL_COLOR_BUFFER_BIT, + GL_NEAREST); + + // right side resolve *with* GL_FRAMEBUFFER_SRGB + glEnable(GL_FRAMEBUFFER_SRGB); + glBlitFramebuffer(w/2, 0, w, h, + w/2, 0, w, h, + GL_COLOR_BUFFER_BIT, + GL_NEAREST); + glDisable(GL_FRAMEBUFFER_SRGB); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + } + + // after render numerical value checks + { + // 1: each value differs from it's neighbors by exactly 1 + // (w/ 4-byte stride; alpha channel not included) + U8 buf[256*4] = {0}; + glReadPixels((S32)lside_grad_x0, (S32)test1_y - 5, 256, 1, + GL_RGBA, GL_UNSIGNED_BYTE, buf); + for (U32 i = 1; i < 256; i += 1){ + Assert(buf[(i - 1)*4] - 1 == buf[i*4]); + } + + + + } + + + // check for gl errors from this frame + if (glGetError() != 0){ + error_message("error in opengl frame render"); } // end render @@ -519,7 +998,7 @@ w32_graphics_window_proc(HWND wnd, w32_wglMakeCurrent(dc, graphics_context); // render - glClearColor(1.f, 0.f, 1.f, 1.f); + glClearColor(0.f, 0.f, 0.f, 1.f); glClear(GL_COLOR_BUFFER_BIT); // end render