#include #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 #include typedef int8_t S8; typedef int16_t S16; typedef int32_t S32; typedef int64_t S64; typedef uint8_t U8; typedef uint16_t U16; typedef uint32_t U32; typedef uint64_t U64; typedef S8 B8; typedef S16 B16; typedef S32 B32; typedef S64 B64; typedef float F32; typedef double F64; #define AssertBreak() (*(volatile int*)0 = 0) #define Assert(c) do{ if (!(c)){ AssertBreak(); } }while(0) #define U64FromPtr(ptr) (U64)(ptr) #define PtrFromU64(ptr) (void*)(x) #define ArrayCount(a) ((sizeof(a))/(sizeof(*a))) #define Member(T,m) ((T*)0)->m #define PtrOffsetOf(T,m) (void*)(&Member(T,m)) #define OffsetOf(T,m) U64FromPtr(&Member(T,m)) #define Clamp(a,x,b) (((x)<(a))?(a):((b)<(x))?(b):(x)) #define allocate_array(T,c) (T*)(malloc(sizeof(T)*(c))) //////////////////////////////// //- Types, Functions & Globals static void error_message(char *msg); //- wgl stuff static HGLRC graphics_context = 0; static HWND graphics_window = 0; static HDC graphics_dc = 0; static F32 graphics_w = 0; static F32 graphics_h = 0; static void wgl_helper_make_graphics_window(HINSTANCE hInstance); static void wgl_helper_begin_render(void); static void wgl_helper_end_render(void); //- opengl render stuff typedef struct Vertex{ F32 px; F32 py; F32 uvx; F32 uvy; F32 cr; F32 cg; F32 cb; F32 ca; F32 rx0; F32 ry0; F32 rx1; F32 ry1; } Vertex; static void opengl_render_init(void); 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_rect_geometry(Vertex *v, U64 count); static void opengl_draw_texture_geometry(Vertex *v, U64 count, U32 texture); static void opengl_draw_texrgb_geometry(Vertex *v, U64 count, U32 texture, F32 r, F32 g, F32 b, F32 a); //////////////////////////////// //- Circle Generator static F32 integral_indef(F32 r, F32 x){ Assert(fabs(x) <= fabs(r)); F32 t = asinf(x/r); F32 result = r*r*0.5f*(t + 0.5f*sinf(2*t)); return(result); } static F32 integral_def(F32 r, F32 min_x, F32 max_x){ F32 max_v = integral_indef(r, max_x); F32 min_v = integral_indef(r, min_x); F32 result = max_v - min_v; return(result); } static B32 approx_equal(F32 a, F32 b){ B32 result = (a - 0.0078125 <= b && b <= a + 0.0078125); return(result); } static void sanity_test_integrals(void){ { F32 a = integral_def(1.f, -1.f, 1.f); Assert(approx_equal(a, 3.141593f*0.5f)); } { F32 a = integral_def(2.f, -2.f, 2.f); Assert(approx_equal(a, 3.141593f*2.f)); } { F32 a = integral_def(2.f, 0.f, 2.f); Assert(approx_equal(a, 3.141593f)); } { F32 a0 = integral_def(3.f, 0.f, 1.f); F32 a1 = integral_def(3.f, 1.f, 2.f); F32 a2 = integral_def(3.f, 2.f, 3.f); Assert(approx_equal(a0 + a1 + a2, 3.141593f*9.f/4.f)); } } static F32 intersection_area(F32 cir_r, F32 unit_box_x0, F32 unit_box_y0){ F32 rr = cir_r*cir_r; F32 rel_y0 = unit_box_y0; F32 rel_y1 = unit_box_y0 + 1.f; F32 rel_x0 = unit_box_x0; F32 rel_x1 = unit_box_x0 + 1.f; F32 y0y0 = rel_y0*rel_y0; F32 y1y1 = rel_y1*rel_y1; F32 x0x0 = rel_x0*rel_x0; F32 x1x1 = rel_x1*rel_x1; // check each corner for hitting the circle B32 corner_bl = ((y0y0 + x0x0) <= rr); B32 corner_tl = ((y1y1 + x0x0) <= rr); B32 corner_br = ((y0y0 + x1x1) <= rr); B32 corner_tr = ((y1y1 + x1x1) <= rr); // calculate area F32 a = 0.f; #define COMB(a,b,c,d) ((a)|((b)<<1)|((c)<<2)|((d)<<3)) switch (COMB(corner_bl, corner_tl, corner_br, corner_tr)){ // full miss case 0x0: a = 0.f; break; // full hit case 0xF: a = 1.f; break; // 1 corner hit case COMB(1,0,0,0): case COMB(0,1,0,0): case COMB(0,0,1,0): case COMB(0,0,0,1): { F32 intersection_y = 0.f; if (corner_bl || corner_br){ intersection_y = rel_y0; } else{ intersection_y = rel_y1; } F32 intersection_x = sqrtf(rr - intersection_y*intersection_y); if (intersection_x < rel_x0 || rel_x1 < intersection_x){ intersection_x = -intersection_x; } F32 x_min = 0.f; F32 x_max = 0.f; if (corner_bl || corner_tl){ x_min = rel_x0; x_max = intersection_x; } else{ x_min = intersection_x; x_max = rel_x1; } F32 h = fabs(intersection_y); a = integral_def(cir_r, x_min, x_max) - h*(x_max - x_min); }break; // 2 corner hits (horizontal edge) case COMB(1,0,1,0): case COMB(0,1,0,1): { F32 h = 0.f; if (corner_bl){ h = rel_y0; } else{ h = -rel_y1; } a = integral_def(cir_r, rel_x0, rel_x1) - h; }break; // 2 corner hits (vertical edge) case COMB(1,1,0,0): case COMB(0,0,1,1): { F32 w = 0.f; if (corner_bl){ w = rel_x0; } else{ w = -rel_x1; } a = integral_def(cir_r, rel_y0, rel_y1) - w; }break; // 3 corner hits case COMB(0,1,1,1): case COMB(1,0,1,1): case COMB(1,1,0,1): case COMB(1,1,1,0): { F32 intersection_y = 0.f; if (!corner_bl || !corner_br){ intersection_y = rel_y0; } else{ intersection_y = rel_y1; } F32 intersection_x = sqrtf(rr - intersection_y*intersection_y); if (intersection_x < rel_x0 || rel_x1 < intersection_x){ intersection_x = -intersection_x; } F32 w_extra = 0.f; F32 x_min = 0.f; F32 x_max = 0.f; if (!corner_bl || !corner_tl){ x_min = rel_x0; x_max = intersection_x; w_extra = rel_x1 - intersection_x; } else{ x_min = intersection_x; x_max = rel_x1; w_extra = intersection_x - rel_x0; } F32 h = fabs(intersection_y) - 1.f; a = integral_def(cir_r, x_min, x_max) - h*(x_max - x_min) + w_extra; }break; // these cases should be impossible default: { a = -1.f; }break; } #undef COMB return(a); } static void sanity_test_circle_area(void){ { F32 cr = 5.f; F32 cx = 5.f; F32 cy = 5.f; F32 sum_area = 0.f; for (U32 y = 0; y < 10; y += 1){ F32 box_y0 = (F32)y; for (U32 x = 0; x < 10; x += 1){ F32 box_x0 = (F32)x; F32 a = intersection_area(cr, box_x0 - cx, box_y0 - cy); Assert(0.f <= a && a <= 1.f); sum_area += a; } } F32 formula_area = 3.141593f*cr*cr; Assert(approx_equal(sum_area, formula_area)); } { F32 cr = 8.f; F32 cx = 8.1f; F32 cy = 8.7f; F32 sum_area = 0.f; for (U32 y = 0; y < 20; y += 1){ F32 box_y0 = (F32)y; for (U32 x = 0; x < 20; x += 1){ F32 box_x0 = (F32)x; F32 a = intersection_area(cr, box_x0 - cx, box_y0 - cy); Assert(0.f <= a && a <= 1.f); sum_area += a; } } F32 formula_area = 3.141593f*cr*cr; Assert(approx_equal(sum_area, formula_area)); } } static void render_circle_in_buffer(F32 cir_r, F32 cir_x, F32 cir_y, U8 *buf, U32 buf_side_length){ for (U32 y = 0; y < buf_side_length; y += 1){ F32 box_y0 = (F32)y; for (U32 x = 0; x < buf_side_length; x += 1){ F32 box_x0 = (F32)x; // calculate area of the intersection (circle & box) F32 a = intersection_area(cir_r, box_x0 - cir_x, box_y0 - cir_y); Assert(0.f <= a && a <= 1.f); U32 vraw = (U32)(256.f*a); U8 v = (vraw <= 255)?((U8)vraw):(U8)255; buf[x + buf_side_length*y] = v; } } } //////////////////////////////// //- GL Definitions //- gl types typedef U32 GLenum; typedef F32 GLfloat; typedef U32 GLbitfield; typedef U32 GLuint; typedef S32 GLint; typedef S32 GLsizei; typedef S64 GLsizeiptr; typedef S64 GLintptr; typedef char GLchar; typedef unsigned char GLboolean; //- gl constants #define GL_COLOR_BUFFER_BIT 0x00004000 #define GL_TRIANGLES 0x0004 #define GL_SRC_ALPHA 0x0302 #define GL_ONE_MINUS_SRC_ALPHA 0x0303 #define GL_BLEND 0x0BE2 #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 #define GL_FRAGMENT_SHADER 0x8B30 #define GL_VERTEX_SHADER 0x8B31 #define GL_COMPILE_STATUS 0x8B81 #define GL_LINK_STATUS 0x8B82 #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 #define GL_FUNC_X_LIST() \ /* common stuff */ \ X(glGetError, GLenum, (void)) \ X(glViewport, void, (GLint x, GLint y, GLsizei width, GLsizei height)) \ X(glScissor, void, (GLint x, GLint y, GLsizei width, GLsizei height)) \ X(glClearColor, void, (GLfloat r,GLfloat g,GLfloat b,GLfloat a)) \ X(glClear, void, (GLbitfield mask)) \ X(glBlendFunc, void, (GLenum sfactor, GLenum dfactor)) \ X(glBlendFuncSeparate, void, (GLenum srcRGB,GLenum dstRGB,GLenum srcAlpha,GLenum dstAlpha)) \ X(glBlendEquation, void, (GLenum mode)) \ X(glBlendEquationSeparate, void, (GLenum modeRGB, GLenum modeAlpha)) \ X(glDisable, void, (GLenum cap)) \ X(glEnable, void, (GLenum cap)) \ X(glPixelStorei, void, (GLenum pname, GLint param)) \ X(glReadPixels, void, (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void *pixels)) \ /* buffers */ \ X(glGenBuffers, void, (GLsizei n, GLuint *buffers)) \ X(glDeleteBuffers, void, (GLsizei n, const GLuint *buffers)) \ X(glBindBuffer, void, (GLenum target, GLuint buffer)) \ X(glBufferData, void, (GLenum target, GLsizeiptr size, const void *data, GLenum usage)) \ X(glBufferSubData, void, (GLenum target, GLintptr offset, GLsizeiptr size, const void *data)) \ 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)) \ X(glIsTexture, GLboolean, (GLuint texture)) \ X(glTexParameteri, void, (GLenum target, GLenum pname, GLint param)) \ X(glTexImage1D, void, (GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const void *pixels)) \ X(glTexImage2D, void, (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels)) \ X(glTexSubImage1D, void, (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void *pixels)) \ X(glTexSubImage2D, void, (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels)) \ X(glTexImage2DMultisample, void, (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations)) \ /* shaders */ \ X(glAttachShader, void, (GLuint program, GLuint shader)) \ X(glCompileShader, void, (GLuint shader)) \ X(glCreateProgram, GLuint, (void)) \ X(glCreateShader, GLuint, (GLenum type)) \ X(glDeleteProgram, void, (GLuint program)) \ X(glDeleteShader, void, (GLuint shader)) \ X(glGetProgramiv, void, (GLuint program, GLenum pname, GLint *params)) \ X(glGetProgramInfoLog, void, (GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog)) \ X(glGetShaderiv, void, (GLuint shader, GLenum pname, GLint *params)) \ X(glGetShaderInfoLog, void, (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog)) \ X(glLinkProgram, void, (GLuint program)) \ X(glShaderSource, void, (GLuint shader, GLsizei count, const GLchar *const*string, const GLint *length)) \ X(glUseProgram, void, (GLuint program)) \ X(glGetUniformLocation, GLint, (GLuint program, const GLchar *name)) \ /* draw calls */ \ X(glDrawArrays, void, (GLenum mode, GLint first, GLsizei count)) \ X(glDrawElements, void, (GLenum mode, GLsizei count, GLenum type, const void *indices)) \ X(glDrawArraysInstanced, void, (GLenum mode, GLint first, GLsizei count, GLsizei instancecount)) \ X(glDrawElementsInstanced, void, (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount)) \ X(glEnableVertexAttribArray, void, (GLuint index)) \ X(glDisableVertexAttribArray, void, (GLuint index)) \ X(glVertexAttribPointer, void, (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer)) \ X(glVertexAttribIPointer, void, (GLuint index, GLint size, GLenum type, GLsizei stride, const void *pointer)) \ X(glVertexAttribDivisor, void, (GLuint index, GLuint divisor)) \ X(glUniform1f, void, (GLint location, GLfloat v0)) \ X(glUniform2f, void, (GLint location, GLfloat v0, GLfloat v1)) \ X(glUniform3f, void, (GLint location, GLfloat v0, GLfloat v1, GLfloat v2)) \ X(glUniform4f, void, (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3)) \ X(glUniform1i, void, (GLint location, GLint v0)) \ X(glUniform1fv, void, (GLint location, GLsizei count, const GLfloat *value)) \ X(glUniform2fv, void, (GLint location, GLsizei count, const GLfloat *value)) \ X(glUniform3fv, void, (GLint location, GLsizei count, const GLfloat *value)) \ X(glDrawBuffers, void, (GLsizei n, const GLenum *bufs)) \ /* framebuffers */ \ X(glGenFramebuffers, void, (GLsizei n, GLuint *framebuffers)) \ X(glDeleteFramebuffers, void, (GLsizei n, const GLuint *framebuffers)) \ X(glBindFramebuffer, void, (GLenum target, GLuint framebuffer)) \ X(glIsFramebuffer, GLboolean, (GLuint framebuffer)) \ X(glCheckFramebufferStatus, GLenum, (GLenum target)) \ X(glFramebufferTexture1D, void, (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level)) \ X(glFramebufferTexture2D, void, (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level)) \ X(glFramebufferTexture3D, void, (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset)) \ X(glFramebufferRenderbuffer, void, (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer)) \ X(glFramebufferTexture, void, (GLenum target, GLenum attachment, GLuint texture, GLint level)) \ X(glBlitFramebuffer, void, (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter)) \ X(glGetFramebufferAttachmentParameteriv, void, (GLenum target, GLenum attachment, GLenum pname, GLint *params)) //- gl function types #define X(N,R,P) typedef R GL_##N P; GL_FUNC_X_LIST() #undef X //- gl function pointers #define X(N,R,P) static GL_##N * N = 0; GL_FUNC_X_LIST() #undef X //////////////////////////////// //- OpenGL Helper Declarations static GLuint opengl_helper_make_shader(char *src, GLenum shader_type); static GLuint opengl_helper_make_program(GLuint *shaders, U32 count); //////////////////////////////// //- OpenGL Render static char basic_vshader[] = "#version 330\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" "gl_Position = vec4(norm_pos, 0.0, 1.0);\n" "f_c = v_c;\n" "}\n" ; 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" ; 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 rect_vshader[] = "#version 330\n" "uniform vec2 u_view_xform;\n" "layout (location = 0) in vec2 v_p;\n" "layout (location = 1) in vec4 v_r;\n" "out vec4 f_r;\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_r = v_r;\n" "}\n" ; static char rect_fshader[] = "#version 330\n" "in vec4 gl_FragCoord;\n" "in vec4 f_r;\n" "out vec4 out_color;\n" "void main(){\n" "float pix_x0 = gl_FragCoord.x - 0.5;\n" "float pix_y0 = gl_FragCoord.y - 0.5;\n" "float pix_x1 = gl_FragCoord.x + 0.5;\n" "float pix_y1 = gl_FragCoord.y + 0.5;\n" "float int_x0 = max(pix_x0, f_r.x);\n" "float int_y0 = max(pix_y0, f_r.y);\n" "float int_x1 = min(pix_x1, f_r.z);\n" "float int_y1 = min(pix_y1, f_r.w);\n" "float cover = (int_x1 - int_x0)*(int_y1 - int_y0);\n" "out_color = vec4(1, 1, 1, cover);\n" "}\n" ; static GLuint rect_program = 0; static GLint rect_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 char texrgb_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 texrgb_fshader[] = "#version 330\n" "uniform sampler2D u_texture;\n" "uniform vec4 u_color;\n" "in vec2 f_uv;\n" "out vec4 out_color;\n" "void main(){\n" "float texture_a = texture(u_texture, f_uv).r;\n" "out_color = vec4(u_color.rgb, u_color.a*texture_a);\n" "}\n" ; static GLuint texrgb_program = 0; static GLint texrgb_u_view_xform = -1; static GLint texrgb_u_texture = -1; static GLint texrgb_u_color = -1; static GLuint canvas_texture = 0; static GLuint canvas_framebuffer = 0; static GLuint test3_texture = 0; #define CIRCLE5_RADIUS 5.f #define CIRCLE5_X 6.f #define CIRCLE5_Y 6.f #define CIRCLE5_SIDE 12 static GLuint circle5_texture = 0; #define CIRCLE25_RADIUS 25.f #define CIRCLE25_X 30.f #define CIRCLE25_Y 30.f #define CIRCLE25_SIDE 60 static GLuint circle25_texture = 0; #define CIRCLE125_RADIUS 125.f #define CIRCLE125_X 150.f #define CIRCLE125_Y 150.f #define CIRCLE125_SIDE 300 static GLuint circle125_texture = 0; static void opengl_render_init(void){ //- setup basic shader program { GLuint shaders[2] = {0}; 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 rect shader program { GLuint shaders[2] = {0}; shaders[0] = opengl_helper_make_shader(rect_vshader, GL_VERTEX_SHADER); shaders[1] = opengl_helper_make_shader(rect_fshader, GL_FRAGMENT_SHADER); rect_program = opengl_helper_make_program(shaders, 2); rect_u_view_xform = glGetUniformLocation(rect_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"); } //- setup texture rgb shader program { GLuint shaders[2] = {0}; shaders[0] = opengl_helper_make_shader(texrgb_vshader, GL_VERTEX_SHADER); shaders[1] = opengl_helper_make_shader(texrgb_fshader, GL_FRAGMENT_SHADER); texrgb_program = opengl_helper_make_program(shaders, 2); texrgb_u_view_xform = glGetUniformLocation(texrgb_program, "u_view_xform"); texrgb_u_texture = glGetUniformLocation(texrgb_program, "u_texture"); texrgb_u_color = glGetUniformLocation(texrgb_program, "u_color"); } //- 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); } //- enable alpha blend { glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_BLEND); } //- 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); } //- circle5 texture { U8 buf[CIRCLE5_SIDE*CIRCLE5_SIDE] = {0}; render_circle_in_buffer(CIRCLE5_RADIUS, CIRCLE5_X, CIRCLE5_Y, buf, CIRCLE5_SIDE); U8 img[CIRCLE5_SIDE*CIRCLE5_SIDE*3] = {0}; for (U32 i = 0; i < CIRCLE5_SIDE*CIRCLE5_SIDE; i += 1){ U8 c = buf[i]; img[i*3 + 0] = c; img[i*3 + 1] = c; img[i*3 + 2] = c; } glGenTextures(1, &circle5_texture); glBindTexture(GL_TEXTURE_2D, circle5_texture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, CIRCLE5_SIDE, CIRCLE5_SIDE, 0, GL_RGB, GL_UNSIGNED_BYTE, 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); } //- circle25 texture { U8 buf[CIRCLE25_SIDE*CIRCLE25_SIDE] = {0}; render_circle_in_buffer(CIRCLE25_RADIUS, CIRCLE25_X, CIRCLE25_Y, buf, CIRCLE25_SIDE); U8 img[CIRCLE25_SIDE*CIRCLE25_SIDE*3] = {0}; for (U32 i = 0; i < CIRCLE25_SIDE*CIRCLE25_SIDE; i += 1){ U8 c = buf[i]; img[i*3 + 0] = c; img[i*3 + 1] = c; img[i*3 + 2] = c; } glGenTextures(1, &circle25_texture); glBindTexture(GL_TEXTURE_2D, circle25_texture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, CIRCLE25_SIDE, CIRCLE25_SIDE, 0, GL_RGB, GL_UNSIGNED_BYTE, 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); } //- circle125 texture { U8 buf[CIRCLE125_SIDE*CIRCLE125_SIDE] = {0}; render_circle_in_buffer(CIRCLE125_RADIUS, CIRCLE125_X, CIRCLE125_Y, buf, CIRCLE125_SIDE); U8 img[CIRCLE125_SIDE*CIRCLE125_SIDE*3] = {0}; for (U32 i = 0; i < CIRCLE125_SIDE*CIRCLE125_SIDE; i += 1){ U8 c = buf[i]; img[i*3 + 0] = c; img[i*3 + 1] = c; img[i*3 + 2] = c; } glGenTextures(1, &circle125_texture); glBindTexture(GL_TEXTURE_2D, circle125_texture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, CIRCLE125_SIDE, CIRCLE125_SIDE, 0, GL_RGB, GL_UNSIGNED_BYTE, 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_basic_geometry(Vertex *v, U64 count){ glBufferData(GL_ARRAY_BUFFER, sizeof(*v)*count, v, GL_STREAM_DRAW); glUseProgram(basic_program); glUniform2f(basic_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_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_rect_geometry(Vertex *v, U64 count){ glBufferData(GL_ARRAY_BUFFER, sizeof(*v)*count, v, GL_STREAM_DRAW); glUseProgram(rect_program); glUniform2f(rect_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, rx0)); 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); } static void opengl_draw_texrgb_geometry(Vertex *v, U64 count, U32 texture, F32 r, F32 g, F32 b, F32 a){ glBufferData(GL_ARRAY_BUFFER, sizeof(*v)*count, v, GL_STREAM_DRAW); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texture); glUseProgram(texrgb_program); glUniform2f(texrgb_u_view_xform, 2.f/graphics_w, 2.f/graphics_h); glUniform1i(texrgb_u_texture, 0); glUniform4f(texrgb_u_color, r, g, b, a); 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); } //////////////////////////////// //- Main typedef struct Rect{ F32 x0; F32 y0; F32 x1; F32 y1; F32 r; F32 g; F32 b; F32 a; } Rect; int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd){ sanity_test_integrals(); sanity_test_circle_area(); // do graphics tests wgl_helper_make_graphics_window(hInstance); U64 frame_counter = 0; for (;;){ // wait for input MSG msg = {0}; BOOL get_message = GetMessage(&msg, 0, 0, 0); if (get_message <= 0){ break; } DispatchMessage(&msg); // begin render wgl_helper_begin_render(); // initialization if (frame_counter == 0){ opengl_render_init(); } // screen layout 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; 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; F32 test5_y = graphics_h - 340.f; F32 test6_y = graphics_h - 450.f; F32 test7_y = graphics_h - 480.f; F32 test8_y = graphics_h - 800.f; // render to canvas frame buffer { glBindFramebuffer(GL_FRAMEBUFFER, canvas_framebuffer); glViewport(0, 0, (S32)graphics_w, (S32)graphics_h); // black background everywhere glClearColor(0.f, 0.f, 0.f, 1.f); glClear(GL_COLOR_BUFFER_BIT); // draw divider { Vertex v[6]; v[0].px = divider_min_x; v[0].py = 0.f; v[1].px = divider_min_x; v[1].py = graphics_h; v[2].px = divider_max_x; v[2].py = 0.f; 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 = divider_max_x; v[5].py = graphics_h; for (U32 i = 0; i < 6; i += 1){ v[i].cr = 1.f; v[i].cg = 1.f; v[i].cb = 1.f; v[i].ca = 1.f; } 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); } } } // 5: transparency blending { Rect rect[20] = {0}; U32 recti = 0; F32 top_y = test5_y; F32 bot_y = top_y - 100.f; rect[recti].x0 = grad_x0; rect[recti].y0 = bot_y; rect[recti].x1 = grad_x1; rect[recti].y1 = top_y; rect[recti].r = 1.f; rect[recti].g = 1.f; rect[recti].b = 1.f; rect[recti].a = 1.f; recti += 1; for (U32 i = 0; i < 3; i += 1){ rect[recti].x0 = grad_x0; rect[recti].y0 = top_y - 10.f - 30.f*i; rect[recti].x1 = grad_x1; rect[recti].y1 = top_y - 30.f - 30.f*i; rect[recti].r = (i == 0)?1.f:0.f; rect[recti].g = (i == 1)?1.f:0.f; rect[recti].b = (i == 2)?1.f:0.f; rect[recti].a = 1.f; recti += 1; } for (U32 j = 0; j < 2; j += 1){ for (U32 i = 0; i < 3; i += 1){ rect[recti].x0 = grad_x0 + 10.f + 30.f*i + 100.f*j; rect[recti].y0 = bot_y; rect[recti].x1 = grad_x0 + 30.f + 30.f*i + 100.f*j; rect[recti].y1 = top_y; rect[recti].r = (i == 0)?1.f:0.f; rect[recti].g = (i == 1)?1.f:0.f; rect[recti].b = (i == 2)?1.f:0.f; rect[recti].a = (j == 0)?0.5f:0.75f; recti += 1; } } for (U32 j = 0; j < recti; j += 1){ Vertex v[6] = {0}; v[0].px = rect[j].x0; v[0].py = rect[j].y0; v[1].px = rect[j].x0; v[1].py = rect[j].y1; v[2].px = rect[j].x1; v[2].py = rect[j].y0; v[3].px = rect[j].x0; v[3].py = rect[j].y1; v[4].px = rect[j].x1; v[4].py = rect[j].y0; v[5].px = rect[j].x1; v[5].py = rect[j].y1; for (U32 k = 0; k < 6; k += 1){ v[k].cr = rect[j].r; v[k].cg = rect[j].g; v[k].cb = rect[j].b; v[k].ca = rect[j].a; } if (test_idx == 0){ opengl_draw_basic_geometry(v, 6); } else{ opengl_draw_srgb_in_geometry(v, 6); } } } // 6: sub-pixel rectangle anti-aliasing { F32 top_y = test6_y; F32 bot_y = test6_y - 20.f; F32 x_cursor = grad_x0; for (U32 i = 0; i < 16; i += 1){ F32 l = x_cursor + 4.0625*i; F32 r = l + 2.f; F32 floor_l = (F32)(S32)l; F32 ceil_r = (F32)(S32)r; if (ceil_r < (F32)r){ ceil_r += 1.f; } Vertex v[6]; v[0].px = floor_l; v[0].py = bot_y; v[1].px = floor_l; v[1].py = top_y; v[2].px = ceil_r; v[2].py = bot_y; v[3].px = floor_l; v[3].py = top_y; v[4].px = ceil_r; v[4].py = bot_y; v[5].px = ceil_r; v[5].py = top_y; for (U32 j = 0; j < 6; j += 1){ v[j].rx0 = l; v[j].ry0 = bot_y; v[j].rx1 = r; v[j].ry1 = top_y; } opengl_draw_rect_geometry(v, 6); } } // 7: circles rendered with analytical antialiasing via integration { F32 y = test7_y; F32 x = grad_x0; // circle5 { F32 y1 = y; F32 y0 = y - CIRCLE5_SIDE; F32 x0 = x; F32 x1 = x + CIRCLE5_SIDE; x += CIRCLE5_SIDE + 10; Vertex v[6]; v[0].px = x0; v[0].py = y0; v[0].uvx = 0.f; v[0].uvy = 0.f; v[1].px = x0; v[1].py = y1; v[1].uvx = 0.f; v[1].uvy = 1.f; v[2].px = x1; v[2].py = y0; v[2].uvx = 1.f; v[2].uvy = 0.f; v[3].px = x0; v[3].py = y1; v[3].uvx = 0.f; v[3].uvy = 1.f; v[4].px = x1; v[4].py = y0; v[4].uvx = 1.f; v[4].uvy = 0.f; v[5].px = x1; v[5].py = y1; v[5].uvx = 1.f; v[5].uvy = 1.f; opengl_draw_texture_geometry(v, 6, circle5_texture); } // circle25 { F32 y1 = y; F32 y0 = y - CIRCLE25_SIDE; F32 x0 = x; F32 x1 = x + CIRCLE25_SIDE; x += CIRCLE25_SIDE + 10; Vertex v[6]; v[0].px = x0; v[0].py = y0; v[0].uvx = 0.f; v[0].uvy = 0.f; v[1].px = x0; v[1].py = y1; v[1].uvx = 0.f; v[1].uvy = 1.f; v[2].px = x1; v[2].py = y0; v[2].uvx = 1.f; v[2].uvy = 0.f; v[3].px = x0; v[3].py = y1; v[3].uvx = 0.f; v[3].uvy = 1.f; v[4].px = x1; v[4].py = y0; v[4].uvx = 1.f; v[4].uvy = 0.f; v[5].px = x1; v[5].py = y1; v[5].uvx = 1.f; v[5].uvy = 1.f; opengl_draw_texture_geometry(v, 6, circle25_texture); } // circle125 { F32 y1 = y; F32 y0 = y - CIRCLE125_SIDE; F32 x0 = x; F32 x1 = x + CIRCLE125_SIDE; x += CIRCLE125_SIDE + 10; Vertex v[6]; v[0].px = x0; v[0].py = y0; v[0].uvx = 0.f; v[0].uvy = 0.f; v[1].px = x0; v[1].py = y1; v[1].uvx = 0.f; v[1].uvy = 1.f; v[2].px = x1; v[2].py = y0; v[2].uvx = 1.f; v[2].uvy = 0.f; v[3].px = x0; v[3].py = y1; v[3].uvx = 0.f; v[3].uvy = 1.f; v[4].px = x1; v[4].py = y0; v[4].uvx = 1.f; v[4].uvy = 0.f; v[5].px = x1; v[5].py = y1; v[5].uvx = 1.f; v[5].uvy = 1.f; opengl_draw_texture_geometry(v, 6, circle125_texture); } } // 8: color on color circles { F32 back[][4] = { {1.f, 0.f, 0.f, 1.f}, {1.f, 0.f, 0.f, 1.f}, {0.f, 1.f, 0.f, 1.f}, {0.f, 1.f, 0.f, 1.f}, {0.f, 0.f, 1.f, 1.f}, {0.f, 0.f, 1.f, 1.f}, }; F32 fore[][4] = { {0.f, 1.f, 0.f, 1.f}, {0.f, 0.f, 1.f, 1.f}, {1.f, 0.f, 0.f, 1.f}, {0.f, 0.f, 1.f, 1.f}, {1.f, 0.f, 0.f, 1.f}, {0.f, 1.f, 0.f, 1.f}, }; F32 y = test8_y; F32 x = grad_x0; for (U32 k = 0; k < 6; k += 1){ // background { F32 x0 = x; F32 x1 = x + CIRCLE5_SIDE + CIRCLE25_SIDE + 20.f; F32 y1 = y; F32 y0 = y - CIRCLE25_SIDE; Vertex v[6]; v[0].px = x0; v[0].py = y0; v[1].px = x0; v[1].py = y1; v[2].px = x1; v[2].py = y0; v[3].px = x0; v[3].py = y1; v[4].px = x1; v[4].py = y0; v[5].px = x1; v[5].py = y1; for (U32 i = 0; i < 6; i += 1){ v[i].cr = back[k][0]; v[i].cg = back[k][1]; v[i].cb = back[k][2]; v[i].ca = back[k][3]; } if (test_idx == 0){ opengl_draw_basic_geometry(v, 6); } else{ opengl_draw_srgb_in_geometry(v, 6); } } // circle5 { F32 y1 = y; F32 y0 = y - CIRCLE5_SIDE; F32 x0 = x; F32 x1 = x + CIRCLE5_SIDE; x += CIRCLE5_SIDE + 10; Vertex v[6]; v[0].px = x0; v[0].py = y0; v[0].uvx = 0.f; v[0].uvy = 0.f; v[1].px = x0; v[1].py = y1; v[1].uvx = 0.f; v[1].uvy = 1.f; v[2].px = x1; v[2].py = y0; v[2].uvx = 1.f; v[2].uvy = 0.f; v[3].px = x0; v[3].py = y1; v[3].uvx = 0.f; v[3].uvy = 1.f; v[4].px = x1; v[4].py = y0; v[4].uvx = 1.f; v[4].uvy = 0.f; v[5].px = x1; v[5].py = y1; v[5].uvx = 1.f; v[5].uvy = 1.f; opengl_draw_texrgb_geometry(v, 6, circle5_texture, fore[k][0], fore[k][1], fore[k][2], fore[k][3]); } // circle25 { F32 y1 = y; F32 y0 = y - CIRCLE25_SIDE; F32 x0 = x; F32 x1 = x + CIRCLE25_SIDE; x += CIRCLE25_SIDE + 10; Vertex v[6]; v[0].px = x0; v[0].py = y0; v[0].uvx = 0.f; v[0].uvy = 0.f; v[1].px = x0; v[1].py = y1; v[1].uvx = 0.f; v[1].uvy = 1.f; v[2].px = x1; v[2].py = y0; v[2].uvx = 1.f; v[2].uvy = 0.f; v[3].px = x0; v[3].py = y1; v[3].uvx = 0.f; v[3].uvy = 1.f; v[4].px = x1; v[4].py = y0; v[4].uvx = 1.f; v[4].uvy = 0.f; v[5].px = x1; v[5].py = y1; v[5].uvx = 1.f; v[5].uvy = 1.f; opengl_draw_texrgb_geometry(v, 6, circle25_texture, fore[k][0], fore[k][1], fore[k][2], fore[k][3]); } // move y down after 2 if ((k + 1) % 2 == 0){ x = grad_x0; y -= CIRCLE25_SIDE; } } } } } // 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 wgl_helper_end_render(); // frame counter frame_counter += 1; } return(0); } //////////////////////////////// //- OpenGL Helpers static GLuint opengl_helper_make_shader(char *src, GLenum shader_type){ // create shader GLuint shader = glCreateShader(shader_type); GLenum error = glGetError(); // set source glShaderSource(shader, 1, &src, 0); // compile glCompileShader(shader); // read log GLint info_log_length = 0; glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &info_log_length); if (info_log_length > 0){ char *buffer = allocate_array(char, info_log_length + 1); GLint length = 0; glGetShaderInfoLog(shader, info_log_length + 1, &length, buffer); error_message(buffer); } // check status { GLint compile_status = 0; glGetShaderiv(shader, GL_COMPILE_STATUS, &compile_status); if (compile_status == 0){ error_message("could not create one of the shaders"); } } return(shader); } static GLuint opengl_helper_make_program(GLuint *shaders, U32 count){ // link program GLuint program = glCreateProgram(); for (U32 i = 0; i < count; i += 1){ glAttachShader(program, shaders[i]); } glLinkProgram(program); // read log GLint info_log_length = 0; glGetProgramiv(program, GL_INFO_LOG_LENGTH, &info_log_length); if (info_log_length > 0){ char *buffer = allocate_array(char, info_log_length + 1); GLint length = 0; glGetProgramInfoLog(program, info_log_length + 1, &length, buffer); error_message(buffer); } // check status { GLint link_status = 0; glGetProgramiv(program, GL_LINK_STATUS, &link_status); if (link_status == 0){ error_message("could not create one of the GPU programs"); } } return(program); } //////////////////////////////// //- WGL Definitions #define WGL_DRAW_TO_WINDOW_ARB 0x2001 #define WGL_ACCELERATION_ARB 0x2003 #define WGL_SWAP_METHOD_ARB 0x2007 #define WGL_SUPPORT_OPENGL_ARB 0x2010 #define WGL_DOUBLE_BUFFER_ARB 0x2011 #define WGL_PIXEL_TYPE_ARB 0x2013 #define WGL_COLOR_BITS_ARB 0x2014 #define WGL_RED_BITS_ARB 0x2015 #define WGL_GREEN_BITS_ARB 0x2017 #define WGL_BLUE_BITS_ARB 0x2019 #define WGL_FULL_ACCELERATION_ARB 0x2027 #define WGL_SWAP_EXCHANGE_ARB 0x2028 #define WGL_TYPE_RGBA_ARB 0x202B //- WGL_ARB_create_context constants #define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 #define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 #define WGL_CONTEXT_FLAGS_ARB 0x2094 #define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 #define WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x0002 #define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 //- wgl funcs x-list #define WGL_FUNCS_X_LIST() \ X(wglCreateContext, HGLRC, (HDC dc)) \ X(wglDeleteContext, BOOL, (HGLRC glrc)) \ X(wglMakeCurrent, BOOL, (HDC dc,HGLRC glrc)) \ X(wglGetProcAddress, PROC, (LPCSTR name)) //- wgl extension funcs x-list #define WGL_EXT_FUNCS_X_LIST() \ X(wglChoosePixelFormatARB, BOOL, (HDC,int*,FLOAT*,UINT,int*,UINT*)) \ X(wglCreateContextAttribsARB, HGLRC, (HDC dc,HGLRC share,int*atri)) //- wgl function types #define X(N,R,P) typedef R W32_##N P; WGL_FUNCS_X_LIST() WGL_EXT_FUNCS_X_LIST() #undef X //- wgl function pointers #define X(N,R,P) static W32_##N * w32_##N = 0; WGL_FUNCS_X_LIST() WGL_EXT_FUNCS_X_LIST() #undef X //////////////////////////////// //- WGL Helper #define GET_PROC_ADDR(v,m,s) (*(PROC*)(&(v))) = GetProcAddress((m),(s)) #define WGL_GET_PROC_ADDR(v,s) (*(PROC*)(&(v))) = w32_wglGetProcAddress(s) #define GL_FULL_PROC_ADDR(v,m,s) (*(PROC*)(&(v))) = wgl_helper_load((m),(s)) #define BOOTSTRAP_WINDOW_CLASS_NAME L"mr4th-opengl-bootstrap" #define GRAPHICS_WINDOW_CLASS_NAME L"mr4th-opengl-graphics" static LRESULT w32_graphics_window_proc(HWND wnd, UINT msg, WPARAM wparam, LPARAM lparam){ LRESULT result = 0; switch (msg){ case WM_CLOSE: { ExitProcess(0); }break; case WM_SIZE: { // Here we need to re-render IF we want the window not // to have ugly black borders during resizing. // begin render HDC dc = GetDC(wnd); w32_wglMakeCurrent(dc, graphics_context); // render glClearColor(0.f, 0.f, 0.f, 1.f); glClear(GL_COLOR_BUFFER_BIT); // end render SwapBuffers(dc); ReleaseDC(wnd, dc); }break; default: { result = DefWindowProcW(wnd, msg, wparam, lparam); }break; } return(result); } static PROC wgl_helper_load(HMODULE opengl_module, char *name){ PROC result = (PROC)GetProcAddress(opengl_module, name); if (result == 0){ result = (PROC)w32_wglGetProcAddress(name); } return(result); } static void wgl_helper_make_graphics_window(HINSTANCE hInstance){ //- load opengl module HMODULE w32_opengl_module = LoadLibraryA("opengl32.dll"); if (w32_opengl_module == 0){ error_message("failed to initialize opengl32.dll"); } //- load & check wgl functions { #define X(N,R,P) GET_PROC_ADDR(w32_##N, w32_opengl_module, #N); WGL_FUNCS_X_LIST() #undef X B32 missing_wgl_func = 0; #define X(N,R,P) if (w32_##N == 0){ missing_wgl_func = 1; } WGL_FUNCS_X_LIST() #undef X if (missing_wgl_func){ error_message("failed to load wgl function(s)"); } } // register bootstrap class WNDCLASSW bs_wnd_class = {0}; bs_wnd_class.lpfnWndProc = DefWindowProcW; bs_wnd_class.hInstance = hInstance; bs_wnd_class.lpszClassName = BOOTSTRAP_WINDOW_CLASS_NAME; ATOM bs_atom = RegisterClassW(&bs_wnd_class); if (bs_atom == 0){ error_message("failed to register bootstrap class"); } // create bootstrap window HWND bootstrap_window = CreateWindowW(BOOTSTRAP_WINDOW_CLASS_NAME, L"mr4th-opengl-bootstrap", 0, 0, 0, 0, 0, // style, x,y,w,h 0, 0, hInstance, 0 // parent, menu, inst, param ); if (bootstrap_window == 0){ error_message("failed to create bootstrap window"); } //- load wgl extension functions { HDC dc = GetDC(bootstrap_window); // create bootstrap context HGLRC hglrc = 0; { PIXELFORMATDESCRIPTOR format_desc = {0}; format_desc.nSize = sizeof(format_desc); format_desc.nVersion = 1; format_desc.dwFlags = PFD_SUPPORT_OPENGL; format_desc.cColorBits = 24; format_desc.cRedBits = 8; format_desc.cRedShift = 0; format_desc.cGreenBits = 8; format_desc.cGreenShift = 8; format_desc.cBlueBits = 8; format_desc.cBlueShift = 16; int format_idx = ChoosePixelFormat(dc, &format_desc); if (format_idx != 0){ BOOL spf = SetPixelFormat(dc, format_idx, &format_desc); if (spf){ hglrc = w32_wglCreateContext(dc); } } } if (hglrc == 0){ error_message("failed to create bootstrap context"); } // load wgl extension functions w32_wglMakeCurrent(dc, hglrc); #define X(N,R,P) WGL_GET_PROC_ADDR(w32_##N, #N); WGL_EXT_FUNCS_X_LIST() #undef X w32_wglMakeCurrent(0, 0); // check wgl extension functions B32 missing_wgl_ext_func = 0; #define X(N,R,P) if (w32_##N == 0){ missing_wgl_ext_func = 1; } WGL_EXT_FUNCS_X_LIST() #undef X // report error if (missing_wgl_ext_func){ error_message("failed to load wgl extension function(s)"); } // delete context { BOOL delete_context = w32_wglDeleteContext(hglrc); Assert(delete_context); } // cleanup dc ReleaseDC(bootstrap_window, dc); } //- destroy bootstrap window, and class { BOOL destroy_window = DestroyWindow(bootstrap_window); Assert(destroy_window); BOOL unregister = UnregisterClassW(BOOTSTRAP_WINDOW_CLASS_NAME, hInstance); Assert(unregister); } //- register graphics class WNDCLASSW gr_wnd_class = {0}; gr_wnd_class.lpfnWndProc = w32_graphics_window_proc; gr_wnd_class.hInstance = hInstance; gr_wnd_class.lpszClassName = GRAPHICS_WINDOW_CLASS_NAME; ATOM gr_atom = RegisterClassW(&gr_wnd_class); if (gr_atom == 0){ error_message("failed to register graphics class"); } //- create graphics context int format_idx = 0; { // create dummy window HWND dummy_window = CreateWindowW(GRAPHICS_WINDOW_CLASS_NAME, L"DUMMY", WS_TILEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, // x,y CW_USEDEFAULT, CW_USEDEFAULT, // w,h 0, 0, hInstance, 0 // parent, menu, inst, param ); if (dummy_window == 0){ error_message("failed to create dummy window"); } HDC dc = GetDC(dummy_window); // choose pixel format int format_attribs[] = { WGL_DRAW_TO_WINDOW_ARB, TRUE, WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB, WGL_SWAP_METHOD_ARB, WGL_SWAP_EXCHANGE_ARB, WGL_SUPPORT_OPENGL_ARB, TRUE, WGL_DOUBLE_BUFFER_ARB, TRUE, WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB, WGL_COLOR_BITS_ARB, 24, WGL_RED_BITS_ARB, 8, WGL_GREEN_BITS_ARB, 8, WGL_BLUE_BITS_ARB, 8, 0 }; UINT num_formats = 0; BOOL cpf = w32_wglChoosePixelFormatARB(dc, format_attribs, 0, 1, &format_idx, &num_formats); if (cpf && num_formats > 0){ // set pixel format PIXELFORMATDESCRIPTOR format_desc = {0}; BOOL spf = SetPixelFormat(dc, format_idx, &format_desc); if (spf){ // create context int attribs[] = { WGL_CONTEXT_MAJOR_VERSION_ARB, 3, WGL_CONTEXT_MINOR_VERSION_ARB, 3, WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB, 0 }; graphics_context = w32_wglCreateContextAttribsARB(dc, 0, attribs); } } //- load & check gl functions w32_wglMakeCurrent(dc, graphics_context); #define X(N,R,P) GL_FULL_PROC_ADDR(N, w32_opengl_module, #N); GL_FUNC_X_LIST() #undef X w32_wglMakeCurrent(0, 0); B32 missing_gl_func = 0; #define X(N,R,P) if (N == 0){ missing_gl_func = 1; } GL_FUNC_X_LIST() #undef X if (missing_gl_func){ error_message("failed to load gl functions"); } // cleanup ReleaseDC(dummy_window, dc); DestroyWindow(dummy_window); } if (graphics_context == 0){ error_message("failed to create graphics context"); } //- create the window graphics_window = CreateWindowW(GRAPHICS_WINDOW_CLASS_NAME, L"TITLE ME", WS_TILEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, // x,y CW_USEDEFAULT, CW_USEDEFAULT, // w,h 0, 0, hInstance, 0 // parent, menu, inst, param ); if (graphics_window == 0){ error_message("failed to create graphics window"); } //- set pixel format { HDC dc = GetDC(graphics_window); PIXELFORMATDESCRIPTOR format_desc = {0}; SetPixelFormat(dc, format_idx, &format_desc); ReleaseDC(graphics_window, dc); } //- make the window full screen { DWORD style = GetWindowLongW(graphics_window, GWL_STYLE); HMONITOR mon = MonitorFromWindow(graphics_window, MONITOR_DEFAULTTOPRIMARY); if (mon != 0){ MONITORINFO moninfo = {0}; moninfo.cbSize = sizeof(moninfo); if (GetMonitorInfoW(mon, &moninfo)){ SetWindowLongW(graphics_window, GWL_STYLE, style & ~WS_TILEDWINDOW); SetWindowPos(graphics_window, HWND_TOP, moninfo.rcMonitor.left, moninfo.rcMonitor.top, moninfo.rcMonitor.right - moninfo.rcMonitor.left, moninfo.rcMonitor.bottom - moninfo.rcMonitor.top, SWP_NOOWNERZORDER | SWP_FRAMECHANGED); } } } //- show the window ShowWindow(graphics_window, SW_SHOW); } static void wgl_helper_begin_render(void){ graphics_dc = GetDC(graphics_window); w32_wglMakeCurrent(graphics_dc, graphics_context); RECT rect = {0}; if (GetClientRect(graphics_window, &rect)){ graphics_w = (F32)(rect.right - rect.left); graphics_h = (F32)(rect.bottom - rect.top); } } static void wgl_helper_end_render(void){ SwapBuffers(graphics_dc); ReleaseDC(graphics_window, graphics_dc); } //////////////////////////////// //- Error Message static void error_message(char *msg){ MessageBoxA(0, msg, "Error", MB_OK); ExitProcess(1); } //$ graphical //