precise srgb->linear conversion experiment; color gradients experiment

main
Allen Webster 2023-04-26 19:32:42 -07:00
parent 4aa71a8e0f
commit 7c4ca1dc6e
1 changed files with 497 additions and 18 deletions

View File

@ -1,5 +1,21 @@
#include <Windows.h>
////////////////////////////////
//- 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