diff --git a/build.bat b/build.bat index 51f73739..d8a2ca72 100644 --- a/build.bat +++ b/build.bat @@ -40,6 +40,7 @@ if "%~1"=="release" if "%~2"=="" echo [default mode, assuming `raddbg` build] && set auto_compile_flags= if "%telemetry%"=="1" set auto_compile_flags=%auto_compile_flags% -DPROFILE_TELEMETRY=1 && echo [telemetry profiling enabled] if "%asan%"=="1" set auto_compile_flags=%auto_compile_flags% -fsanitize=address && echo [asan enabled] +if "%opengl%"=="1" set auto_compile_flags=%auto_compile_flags% -DR_BACKEND=R_BACKEND_OPENGL && echo [opengl render backend] :: --- Compile/Link Line Definitions ------------------------------------------ set cl_common= /I..\src\ /I..\local\ /nologo /FC /Z7 diff --git a/project.4coder b/project.4coder index a725e0d6..3e46160f 100644 --- a/project.4coder +++ b/project.4coder @@ -46,7 +46,7 @@ load_paths = commands = { //- rjf: [raddbg] - .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg telemetry", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, + .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg telemetry opengl", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, //- rjf: [textperf] // .f1 = { .win = "raddbg_stable --ipc kill_all && build no_meta telemetry textperf && raddbg_stable --ipc bring_to_front && raddbg_stable --ipc run", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, diff --git a/src/base/base_math.c b/src/base/base_math.c index 4f5903c5..7a65b45b 100644 --- a/src/base/base_math.c +++ b/src/base/base_math.c @@ -386,6 +386,21 @@ derotate_4x4f32(Mat4x4F32 mat) return mat; } +internal Mat4x4F32 +transpose_4x4f32(Mat4x4F32 mat) +{ + Mat4x4F32 result = + { + { + mat.v[0][0], mat.v[1][0], mat.v[2][0], mat.v[3][0], + mat.v[0][1], mat.v[1][1], mat.v[2][1], mat.v[3][1], + mat.v[0][2], mat.v[1][2], mat.v[2][2], mat.v[3][2], + mat.v[0][3], mat.v[1][3], mat.v[2][3], mat.v[3][3], + } + }; + return result; +} + //////////////////////////////// //~ rjf: Range Ops diff --git a/src/base/base_math.h b/src/base/base_math.h index e6f76423..eb322865 100644 --- a/src/base/base_math.h +++ b/src/base/base_math.h @@ -545,6 +545,7 @@ internal Mat4x4F32 mul_4x4f32(Mat4x4F32 a, Mat4x4F32 b); internal Mat4x4F32 scale_4x4f32(Mat4x4F32 m, F32 scale); internal Mat4x4F32 inverse_4x4f32(Mat4x4F32 m); internal Mat4x4F32 derotate_4x4f32(Mat4x4F32 mat); +internal Mat4x4F32 transpose_4x4f32(Mat4x4F32 mat); //////////////////////////////// //~ rjf: Range Ops diff --git a/src/render/d3d11/generated/render_d3d11.meta.h b/src/render/d3d11/generated/render_d3d11.meta.h index 5f5e1f12..6fd7f833 100644 --- a/src/render/d3d11/generated/render_d3d11.meta.h +++ b/src/render/d3d11/generated/render_d3d11.meta.h @@ -279,8 +279,7 @@ str8_lit_comp( " corner_radii_px.w,\n" " corner_radii_px.z,\n" " };\n" -" float2 cornercoords__pct = float2(\n" -" (c2v.vertex_id >> 1) ? 1.f : 0.f,\n" +" float2 cornercoords__pct = float2((c2v.vertex_id >> 1) ? 1.f : 0.f,\n" " (c2v.vertex_id & 1) ? 0.f : 1.f);\n" " \n" " float2 vertex_position__pct = vertex_positions__scrn[c2v.vertex_id] / viewport_size;\n" diff --git a/src/render/d3d11/render_d3d11.c b/src/render/d3d11/render_d3d11.c index f3ba394c..dca4586c 100644 --- a/src/render/d3d11/render_d3d11.c +++ b/src/render/d3d11/render_d3d11.c @@ -669,11 +669,11 @@ r_tex2d_release(R_Handle handle) ProfBeginFunction(); OS_MutexScopeW(r_d3d11_state->device_rw_mutex) { - R_D3D11_Tex2D *texture = r_d3d11_tex2d_from_handle(handle); - if(texture != &r_d3d11_tex2d_nil) - { - SLLStackPush(r_d3d11_state->first_to_free_tex2d, texture); - } + R_D3D11_Tex2D *texture = r_d3d11_tex2d_from_handle(handle); + if(texture != &r_d3d11_tex2d_nil) + { + SLLStackPush(r_d3d11_state->first_to_free_tex2d, texture); + } } ProfEnd(); } @@ -705,19 +705,19 @@ r_fill_tex2d_region(R_Handle handle, Rng2S32 subrect, void *data) ProfBeginFunction(); OS_MutexScopeW(r_d3d11_state->device_rw_mutex) { - R_D3D11_Tex2D *texture = r_d3d11_tex2d_from_handle(handle); - if(texture != &r_d3d11_tex2d_nil) - { + R_D3D11_Tex2D *texture = r_d3d11_tex2d_from_handle(handle); + if(texture != &r_d3d11_tex2d_nil) + { Assert(texture->kind == R_ResourceKind_Dynamic && "only dynamic texture can update region"); U64 bytes_per_pixel = r_tex2d_format_bytes_per_pixel_table[texture->format]; - Vec2S32 dim = v2s32(subrect.x1 - subrect.x0, subrect.y1 - subrect.y0); + Vec2S32 dim = v2s32(subrect.x1 - subrect.x0, subrect.y1 - subrect.y0); D3D11_BOX dst_box = { (UINT)subrect.x0, (UINT)subrect.y0, 0, (UINT)subrect.x1, (UINT)subrect.y1, 1, }; - r_d3d11_state->device_ctx->lpVtbl->UpdateSubresource(r_d3d11_state->device_ctx, (ID3D11Resource *)texture->texture, 0, &dst_box, data, dim.x*bytes_per_pixel, 0); - } + r_d3d11_state->device_ctx->lpVtbl->UpdateSubresource(r_d3d11_state->device_ctx, (ID3D11Resource *)texture->texture, 0, &dst_box, data, dim.x*bytes_per_pixel, 0); + } } ProfEnd(); } @@ -823,10 +823,10 @@ r_end_frame(void) tex = next) { next = tex->next; - tex->view->lpVtbl->Release(tex->view); - tex->texture->lpVtbl->Release(tex->texture); - tex->view = 0; - tex->texture = 0; + tex->view->lpVtbl->Release(tex->view); + tex->texture->lpVtbl->Release(tex->texture); + tex->view = 0; + tex->texture = 0; tex->generation += 1; SLLStackPush(r_d3d11_state->first_free_tex2d, tex); } @@ -1119,29 +1119,14 @@ r_window_submit(OS_Handle window, R_Handle window_equip, R_PassList *passes) R_D3D11_Tex2D *texture = r_d3d11_tex2d_from_handle(texture_handle); // rjf: get texture sample map matrix, based on format - Vec4F32 texture_sample_channel_map[] = - { - {1, 0, 0, 0}, - {0, 1, 0, 0}, - {0, 0, 1, 0}, - {0, 0, 0, 1}, - }; - switch(texture->format) - { - default: break; - case R_Tex2DFormat_R8: - { - MemoryZeroArray(texture_sample_channel_map); - texture_sample_channel_map[0] = v4f32(1, 1, 1, 1); - }break; - } + Mat4x4F32 texture_sample_channel_map = r_sample_channel_map_from_tex2dformat(texture->format); // rjf: upload uniforms R_D3D11_Uniforms_Rect uniforms = {0}; { uniforms.viewport_size = v2f32(resolution.x, resolution.y); uniforms.opacity = 1-group_params->transparency; - MemoryCopyArray(uniforms.texture_sample_channel_map, texture_sample_channel_map); + uniforms.texture_sample_channel_map = texture_sample_channel_map; uniforms.texture_t2d_size = v2f32(texture->size.x, texture->size.y); uniforms.xform[0] = v4f32(group_params->xform.v[0][0], group_params->xform.v[1][0], group_params->xform.v[2][0], 0); uniforms.xform[1] = v4f32(group_params->xform.v[0][1], group_params->xform.v[1][1], group_params->xform.v[2][1], 0); diff --git a/src/render/d3d11/render_d3d11.h b/src/render/d3d11/render_d3d11.h index aba28899..b1b0d5fd 100644 --- a/src/render/d3d11/render_d3d11.h +++ b/src/render/d3d11/render_d3d11.h @@ -29,7 +29,7 @@ struct R_D3D11_Uniforms_Rect Vec2F32 viewport_size; F32 opacity; F32 _padding0_; - Vec4F32 texture_sample_channel_map[4]; + Mat4x4F32 texture_sample_channel_map; Vec2F32 texture_t2d_size; Vec2F32 translate; Vec4F32 xform[3]; diff --git a/src/render/d3d11/render_d3d11.mdesk b/src/render/d3d11/render_d3d11.mdesk index 1c78b56b..81e2084d 100644 --- a/src/render/d3d11/render_d3d11.mdesk +++ b/src/render/d3d11/render_d3d11.mdesk @@ -277,8 +277,7 @@ vs_main(CPU2Vertex c2v) corner_radii_px.w, corner_radii_px.z, }; - float2 cornercoords__pct = float2( - (c2v.vertex_id >> 1) ? 1.f : 0.f, + float2 cornercoords__pct = float2((c2v.vertex_id >> 1) ? 1.f : 0.f, (c2v.vertex_id & 1) ? 0.f : 1.f); float2 vertex_position__pct = vertex_positions__scrn[c2v.vertex_id] / viewport_size; diff --git a/src/render/opengl/generated/render_opengl.meta.c b/src/render/opengl/generated/render_opengl.meta.c new file mode 100644 index 00000000..60c10194 --- /dev/null +++ b/src/render/opengl/generated/render_opengl.meta.c @@ -0,0 +1,38 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//- GENERATED CODE + +C_LINKAGE_BEGIN +String8 r_ogl_shader_kind_name_table[2] = +{ +str8_lit_comp("rect"), +str8_lit_comp("blur"), +}; + +String8 * r_ogl_shader_kind_vshad_src_table[2] = +{ +&r_ogl_rect_vshad_src, +&r_ogl_blur_vshad_src, +}; + +String8 * r_ogl_shader_kind_pshad_src_table[2] = +{ +&r_ogl_rect_pshad_src, +&r_ogl_blur_pshad_src, +}; + +R_OGL_AttributeArray r_ogl_shader_kind_input_attributes_table[2] = +{ +{ r_ogl_rect_input_attributes, ArrayCount(r_ogl_rect_input_attributes) }, +{ 0, }, +}; + +R_OGL_AttributeArray r_ogl_shader_kind_output_attributes_table[2] = +{ +{ r_ogl_single_color_output_attributes, ArrayCount(r_ogl_single_color_output_attributes) }, +{ r_ogl_single_color_output_attributes, ArrayCount(r_ogl_single_color_output_attributes) }, +}; + +C_LINKAGE_END + diff --git a/src/render/opengl/generated/render_opengl.meta.h b/src/render/opengl/generated/render_opengl.meta.h new file mode 100644 index 00000000..6e7f6024 --- /dev/null +++ b/src/render/opengl/generated/render_opengl.meta.h @@ -0,0 +1,276 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//- GENERATED CODE + +#ifndef RENDER_OPENGL_META_H +#define RENDER_OPENGL_META_H + +typedef enum R_OGL_ShaderKind +{ +R_OGL_ShaderKind_Rect, +R_OGL_ShaderKind_Blur, +R_OGL_ShaderKind_COUNT, +} R_OGL_ShaderKind; + +C_LINKAGE_BEGIN +extern String8 r_ogl_shader_kind_name_table[2]; +extern String8 * r_ogl_shader_kind_vshad_src_table[2]; +extern String8 * r_ogl_shader_kind_pshad_src_table[2]; +extern R_OGL_AttributeArray r_ogl_shader_kind_input_attributes_table[2]; +extern R_OGL_AttributeArray r_ogl_shader_kind_output_attributes_table[2]; +read_only global String8 r_ogl_rect_vshad_src = +str8_lit_comp( +"" +"\n" +"#version 330 core\n" +"\n" +"in vec4 c2v_dst_rect;\n" +"in vec4 c2v_src_rect;\n" +"in vec4 c2v_colors_0;\n" +"in vec4 c2v_colors_1;\n" +"in vec4 c2v_colors_2;\n" +"in vec4 c2v_colors_3;\n" +"in vec4 c2v_corner_radii;\n" +"in vec4 c2v_style; // x: border_thickness_px, y: softness_px, z: omit_texture, w: unused\n" +"\n" +"out vec2 v2p_sdf_sample_pos;\n" +"out vec2 v2p_texcoord_pct;\n" +"out vec2 v2p_rect_half_size_px;\n" +"out vec4 v2p_tint;\n" +"out float v2p_corner_radius;\n" +"out float v2p_border_thickness;\n" +"out float v2p_softness;\n" +"out float v2p_omit_texture;\n" +"\n" +"uniform sampler2D u_tex_color;\n" +"uniform vec2 u_viewport_size_px;\n" +"\n" +"void main(void)\n" +"{\n" +" // rjf: constants\n" +" vec2 vertices[] = vec2[](vec2(-1, -1), vec2(-1, +1), vec2(+1, -1), vec2(+1, +1));\n" +" \n" +" // rjf: find dst position\n" +" vec2 dst_half_size = (c2v_dst_rect.zw - c2v_dst_rect.xy) / 2;\n" +" vec2 dst_center = (c2v_dst_rect.zw + c2v_dst_rect.xy) / 2;\n" +" vec2 dst_position = vertices[gl_VertexID] * dst_half_size + dst_center;\n" +" \n" +" // rjf: find src position\n" +" vec2 src_half_size = (c2v_src_rect.zw - c2v_src_rect.xy) / 2;\n" +" vec2 src_center = (c2v_src_rect.zw + c2v_src_rect.xy) / 2;\n" +" vec2 src_position = vertices[gl_VertexID] * src_half_size + src_center;\n" +" \n" +" // rjf: find color\n" +" vec4 colors[] = vec4[](c2v_colors_0, c2v_colors_1, c2v_colors_2, c2v_colors_3);\n" +" vec4 color = colors[gl_VertexID];\n" +" \n" +" // rjf: find corner radius\n" +" float corner_radii[] = float[](c2v_corner_radii.x, c2v_corner_radii.y, c2v_corner_radii.z, c2v_corner_radii.w);\n" +" float corner_radius = corner_radii[gl_VertexID];\n" +" \n" +" // rjf: fill outputs\n" +" vec2 dst_verts_pct = vec2(((gl_VertexID >> 1) != 1) ? 1.f : 0.f,\n" +" ((gl_VertexID & 1) != 0) ? 0.f : 1.f);\n" +" ivec2 u_tex_color_size_i = textureSize(u_tex_color, 0);\n" +" vec2 u_tex_color_size = vec2(float(u_tex_color_size_i.x), float(u_tex_color_size_i.y));\n" +" {\n" +" gl_Position = vec4(2 * dst_position.x / u_viewport_size_px.x - 1,\n" +" 2 * (1 - dst_position.y / u_viewport_size_px.y) - 1,\n" +" 0.0, 1.0);\n" +" v2p_sdf_sample_pos = (2.f * dst_verts_pct - 1.f) * dst_half_size;\n" +" v2p_texcoord_pct = src_position / u_tex_color_size;\n" +" v2p_rect_half_size_px = dst_half_size;\n" +" v2p_tint = color;\n" +" v2p_corner_radius = corner_radius;\n" +" v2p_border_thickness = c2v_style.x;\n" +" v2p_softness = c2v_style.y;\n" +" v2p_omit_texture = c2v_style.z;\n" +" }\n" +"}\n" +"" +); + +read_only global String8 r_ogl_rect_pshad_src = +str8_lit_comp( +"" +"\n" +"#version 330 core\n" +"\n" +"in vec2 v2p_sdf_sample_pos;\n" +"in vec2 v2p_texcoord_pct;\n" +"in vec2 v2p_rect_half_size_px;\n" +"in vec4 v2p_tint;\n" +"in float v2p_corner_radius;\n" +"in float v2p_border_thickness;\n" +"in float v2p_softness;\n" +"in float v2p_omit_texture;\n" +"\n" +"out vec4 final_color;\n" +"\n" +"uniform float u_opacity;\n" +"uniform sampler2D u_tex_color;\n" +"uniform mat4 u_texture_sample_channel_map;\n" +"\n" +"float rect_sdf(vec2 sample_pos, vec2 rect_half_size, float r)\n" +"{\n" +" return length(max(abs(sample_pos) - rect_half_size + r, 0.0)) - r;\n" +"}\n" +"\n" +"float linear_from_srgb_f32(float x)\n" +"{\n" +" return x < 0.0404482362771082 ? x / 12.92 : pow((x + 0.055) / 1.055, 2.4);\n" +"}\n" +"\n" +"vec4 linear_from_srgba(vec4 v)\n" +"{\n" +" vec4 result = vec4(linear_from_srgb_f32(v.x),\n" +" linear_from_srgb_f32(v.y),\n" +" linear_from_srgb_f32(v.z),\n" +" v.w);\n" +" return result;\n" +"}\n" +"\n" +"void main(void)\n" +"{\n" +" // rjf: sample texture\n" +" vec4 albedo_sample = vec4(1, 1, 1, 1);\n" +" if(v2p_omit_texture < 1)\n" +" {\n" +" albedo_sample = u_texture_sample_channel_map * texture(u_tex_color, v2p_texcoord_pct);\n" +" albedo_sample = linear_from_srgba(albedo_sample);\n" +" }\n" +" \n" +" // rjf: sample for borders\n" +" float border_sdf_t = 1;\n" +" if(v2p_border_thickness > 0)\n" +" {\n" +" float border_sdf_s = rect_sdf(v2p_sdf_sample_pos,\n" +" v2p_rect_half_size_px - vec2(v2p_softness*2.f, v2p_softness*2.f) - v2p_border_thickness,\n" +" max(v2p_corner_radius-v2p_border_thickness, 0));\n" +" border_sdf_t = smoothstep(0, 2*v2p_softness, border_sdf_s);\n" +" }\n" +" if(border_sdf_t < 0.001f)\n" +" {\n" +" discard;\n" +" }\n" +" \n" +" // rjf: sample for corners\n" +" float corner_sdf_t = 1;\n" +" if(v2p_corner_radius > 0 || v2p_softness > 0.75f)\n" +" {\n" +" float corner_sdf_s = rect_sdf(v2p_sdf_sample_pos,\n" +" v2p_rect_half_size_px - vec2(v2p_softness*2.f, v2p_softness*2.f),\n" +" v2p_corner_radius);\n" +" corner_sdf_t = 1-smoothstep(0, 2*v2p_softness, corner_sdf_s);\n" +" }\n" +" \n" +" // rjf: form+return final color\n" +" final_color = albedo_sample;\n" +" final_color *= v2p_tint;\n" +" final_color.a *= u_opacity;\n" +" final_color.a *= corner_sdf_t;\n" +" final_color.a *= border_sdf_t;\n" +"}\n" +"" +); + +read_only global String8 r_ogl_blur_vshad_src = +str8_lit_comp( +"" +"\n" +"#version 330 core\n" +"\n" +"uniform vec4 rect;\n" +"uniform vec4 corner_radii_px;\n" +"uniform vec2 viewport_size;\n" +"uniform uint blur_count;\n" +"\n" +"out vec2 texcoord;\n" +"out vec2 sdf_sample_pos;\n" +"out vec2 rect_half_size;\n" +"out float corner_radius;\n" +"\n" +"void main(void)\n" +"{\n" +" vec2 vertex_positions_scrn[] = vec2[](rect.xw,\n" +" rect.xy,\n" +" rect.zw,\n" +" rect.zy);\n" +" float corner_radii_px[] = float[](corner_radii_px.y,\n" +" corner_radii_px.x,\n" +" corner_radii_px.w,\n" +" corner_radii_px.z);\n" +" vec2 cornercoords_pct = vec2((gl_VertexID >> 1) != 0 ? 1.f : 0.f,\n" +" (gl_VertexID & 1) != 0 ? 0.f : 1.f);\n" +" \n" +" vec2 vertex_position_pct = vertex_positions_scrn[gl_VertexID] / viewport_size;\n" +" vec2 vertex_position_scr = 2.f * vertex_position_pct - 1.f;\n" +" \n" +" vec2 rect_half_size = vec2((rect.z-rect.x)/2, (rect.w-rect.y)/2);\n" +" \n" +" gl_Position = vec4(vertex_position_scr.x, -vertex_position_scr.y, 0.f, 1.f);\n" +" texcoord = vertex_position_pct;\n" +" sdf_sample_pos = (2.f * cornercoords_pct - 1.f) * rect_half_size;\n" +" rect_half_size = rect_half_size - 2.f;\n" +" corner_radius = corner_radii_px[gl_VertexID];\n" +"}\n" +"" +); + +read_only global String8 r_ogl_blur_pshad_src = +str8_lit_comp( +"" +"\n" +"#version 330 core\n" +"\n" +"uniform sampler2D tex;\n" +"uniform vec4 kernel[32];\n" +"uniform int blur_count;\n" +"uniform vec2 direction;\n" +"\n" +"in vec2 texcoord;\n" +"in vec2 sdf_sample_pos;\n" +"in vec2 rect_half_size;\n" +"in float corner_radius;\n" +"\n" +"out vec4 final_color;\n" +"\n" +"float rect_sdf(vec2 sample_pos, vec2 rect_half_size, float r)\n" +"{\n" +" return length(max(abs(sample_pos) - rect_half_size + r, 0.0)) - r;\n" +"}\n" +"\n" +"void main(void)\n" +"{\n" +" // rjf: blend weighted texture samples into color\n" +" vec3 color = kernel[0].x * texture(tex, texcoord).rgb;\n" +" \n" +" for(int i = 1; i < blur_count; i += 1)\n" +" {\n" +" float weight = kernel[i].x;\n" +" float offset = kernel[i].y;\n" +" color += weight * texture(tex, texcoord - offset * direction).rgb;\n" +" color += weight * texture(tex, texcoord + offset * direction).rgb;\n" +" }\n" +" \n" +" // rjf: sample for corners\n" +" float corner_sdf_s = rect_sdf(sdf_sample_pos, rect_half_size, corner_radius);\n" +" float corner_sdf_t = 1-smoothstep(0, 2, corner_sdf_s);\n" +" \n" +" // rjf: weight output color by sdf\n" +" // this is doing alpha testing, leave blurring only where mostly opaque pixels are\n" +" if(corner_sdf_t < 0.9f)\n" +" {\n" +" discard;\n" +" }\n" +" \n" +" final_color = vec4(color, 1.f);\n" +"}\n" +"" +); + + +C_LINKAGE_END + +#endif // RENDER_OPENGL_META_H diff --git a/src/render/opengl/render_opengl.c b/src/render/opengl/render_opengl.c new file mode 100644 index 00000000..cfbec7de --- /dev/null +++ b/src/render/opengl/render_opengl.c @@ -0,0 +1,480 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: OS Portion Includes + +#if OS_WINDOWS +# include "render/opengl/win32/render_opengl_win32.c" +#else +# error OS portion of OpenGL rendering backend not defined. +#endif + +//////////////////////////////// +//~ rjf: Attribute Tables + +global read_only R_OGL_Attribute r_ogl_rect_input_attributes[] = +{ + {0, str8_lit_comp("c2v_dst_rect"), GL_FLOAT, 4}, + {1, str8_lit_comp("c2v_src_rect"), GL_FLOAT, 4}, + {2, str8_lit_comp("c2v_colors_0"), GL_FLOAT, 4}, + {3, str8_lit_comp("c2v_colors_1"), GL_FLOAT, 4}, + {4, str8_lit_comp("c2v_colors_2"), GL_FLOAT, 4}, + {5, str8_lit_comp("c2v_colors_3"), GL_FLOAT, 4}, + {6, str8_lit_comp("c2v_corner_radii"), GL_FLOAT, 4}, + {7, str8_lit_comp("c2v_style"), GL_FLOAT, 4}, +}; + +global read_only R_OGL_Attribute r_ogl_single_color_output_attributes[] = +{ + {0, str8_lit_comp("final_color")}, +}; + +//////////////////////////////// +//~ rjf: Generated Code + +#include "render/opengl/generated/render_opengl.meta.c" + +//////////////////////////////// +//~ rjf: Helpers + +internal R_Handle +r_ogl_handle_from_tex2d(R_OGL_Tex2D *t) +{ + R_Handle h = {(U64)t}; + return h; +} + +internal R_OGL_Tex2D * +r_ogl_tex2d_from_handle(R_Handle h) +{ + R_OGL_Tex2D *t = (R_OGL_Tex2D *)h.u64[0]; + return t; +} + +internal R_OGL_FormatInfo +r_ogl_format_info_from_tex2dformat(R_Tex2DFormat fmt) +{ + R_OGL_FormatInfo result; + result.internal_format = GL_RGBA; + result.format = GL_RGBA; + result.base_type = GL_UNSIGNED_BYTE; + // TODO(rjf) + return result; +} + +internal void +r_ogl_debug_message_callback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam) +{ + raddbg_log("[OpenGL] %.*s\n", (int)length, message); +} + +//////////////////////////////// +//~ rjf: Backend Hooks + +//- rjf: top-level layer initialization + +r_hook void +r_init(CmdLine *cmdln) +{ + //- rjf: do os-specific portion of work + r_ogl_os_init(cmdln); + + //- rjf: top-level initialization + Arena *arena = arena_alloc(); + r_ogl_state = push_array(arena, R_OGL_State, 1); + r_ogl_state->arena = arena; + + //- rjf: load gl procedures +#define X(name, r, p) name = (name##_FunctionType *)r_ogl_os_load_procedure(#name); + R_OGL_ProcedureXList +#undef X + + //- rjf: build all shaders + for EachEnumVal(R_OGL_ShaderKind, k) + { + // rjf: compile + struct {GLenum type; String8 *src; GLuint out; String8 errors;} stages[] = + { + {GL_VERTEX_SHADER, r_ogl_shader_kind_vshad_src_table[k]}, + {GL_FRAGMENT_SHADER, r_ogl_shader_kind_pshad_src_table[k]}, + }; + for EachElement(idx, stages) + { + stages[idx].out = glCreateShader(stages[idx].type); + GLint src_size = stages[idx].src->size; + glShaderSource(stages[idx].out, 1, &stages[idx].src->str, &src_size); + glCompileShader(stages[idx].out); + GLint info_log_length = 0; + GLint status = 0; + glGetShaderiv(stages[idx].out, GL_COMPILE_STATUS, &status); + glGetShaderiv(stages[idx].out, GL_INFO_LOG_LENGTH, &info_log_length); + if(info_log_length != 0) + { + stages[idx].errors.str = push_array(r_ogl_state->arena, U8, info_log_length+1); + stages[idx].errors.size = info_log_length; + glGetShaderInfoLog(stages[idx].out, info_log_length, 0, stages[idx].errors.str); + } + raddbg_pin(text(stages[idx].errors.str)); + } + + // rjf: attach compilations to program + GLuint program = glCreateProgram(); + for EachElement(idx, stages) + { + glAttachShader(program, stages[idx].out); + } + + // rjf: bind inputs + R_OGL_AttributeArray inputs = r_ogl_shader_kind_input_attributes_table[k]; + for EachIndex(idx, inputs.count) + { + glBindAttribLocation(program, inputs.v[idx].index, inputs.v[idx].name.str); + } + + // rjf: bind outputs + R_OGL_AttributeArray outputs = r_ogl_shader_kind_output_attributes_table[k]; + for EachIndex(idx, outputs.count) + { + glBindFragDataLocation(program, outputs.v[idx].index, outputs.v[idx].name.str); + } + + // rjf: link / validate / store + glLinkProgram(program); + glValidateProgram(program); + r_ogl_state->shaders[k] = program; + } + + //- rjf: set up built-in resources + glGenVertexArrays(1, &r_ogl_state->all_purpose_vao); + glGenBuffers(1, &r_ogl_state->scratch_buffer_2mb); + glBindBuffer(GL_ARRAY_BUFFER, r_ogl_state->scratch_buffer_2mb); + glBufferData(GL_ARRAY_BUFFER, MB(2), 0, GL_DYNAMIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glGenTextures(1, &r_ogl_state->white_texture); + glBindTexture(GL_TEXTURE_2D, r_ogl_state->white_texture); + U32 white_pixel = 0xffffffff; + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, &white_pixel); + glEnable(GL_FRAMEBUFFER_SRGB); + + //- rjf: set up debug callback + glEnable(GL_DEBUG_OUTPUT); + glDebugMessageCallback(r_ogl_debug_message_callback, 0); +} + +//- rjf: window setup/teardown + +r_hook R_Handle +r_window_equip(OS_Handle window) +{ + R_Handle result = r_ogl_os_window_equip(window); + return result; +} + +r_hook void +r_window_unequip(OS_Handle window, R_Handle window_equip) +{ + r_ogl_os_window_unequip(window, window_equip); +} + +//- rjf: textures + +r_hook R_Handle +r_tex2d_alloc(R_ResourceKind kind, Vec2S32 size, R_Tex2DFormat format, void *data) +{ + //- rjf: allocate texture record + R_OGL_Tex2D *tex2d = r_ogl_state->free_tex2d; + if(tex2d) + { + SLLStackPop(r_ogl_state->free_tex2d); + } + else + { + tex2d = push_array(r_ogl_state->arena, R_OGL_Tex2D, 1); + } + + //- rjf: map kind/format -> gl counterparts + R_OGL_FormatInfo gl_fmt_info = r_ogl_format_info_from_tex2dformat(format); + + //- rjf: allocate GL texture + { + glGenTextures(1, &tex2d->id); + glBindTexture(GL_TEXTURE_2D, tex2d->id); + glTexImage2D(GL_TEXTURE_2D, 0, gl_fmt_info.internal_format, size.x, size.y, 0, gl_fmt_info.format, gl_fmt_info.base_type, data); + glBindTexture(GL_TEXTURE_2D, 0); + } + + //- rjf: fill + tex2d->resource_kind = kind; + tex2d->fmt = format; + tex2d->size = size; + + //- rjf: bundle & return + R_Handle result = r_ogl_handle_from_tex2d(tex2d); + return result; +} + +r_hook void +r_tex2d_release(R_Handle texture) +{ + R_OGL_Tex2D *t = r_ogl_tex2d_from_handle(texture); + if(t != 0) + { + glDeleteTextures(1, &t->id); + SLLStackPush(r_ogl_state->free_tex2d, t); + } +} + +r_hook R_ResourceKind +r_kind_from_tex2d(R_Handle texture) +{ + R_ResourceKind result = R_ResourceKind_Static; + R_OGL_Tex2D *t = r_ogl_tex2d_from_handle(texture); + if(t) + { + result = t->resource_kind; + } + return result; +} + +r_hook Vec2S32 +r_size_from_tex2d(R_Handle texture) +{ + Vec2S32 result = {0, 0}; + R_OGL_Tex2D *t = r_ogl_tex2d_from_handle(texture); + if(t) + { + result = t->size; + } + return result; +} + +r_hook R_Tex2DFormat +r_format_from_tex2d(R_Handle texture) +{ + R_Tex2DFormat result = R_Tex2DFormat_RGBA8; + R_OGL_Tex2D *t = r_ogl_tex2d_from_handle(texture); + if(t) + { + result = t->fmt; + } + return result; +} + +r_hook void +r_fill_tex2d_region(R_Handle texture, Rng2S32 subrect, void *data) +{ + R_OGL_Tex2D *t = r_ogl_tex2d_from_handle(texture); + if(t) + { + R_OGL_FormatInfo fmt_info = r_ogl_format_info_from_tex2dformat(t->fmt); + glBindTexture(GL_TEXTURE_2D, t->id); + Vec2S32 rect_size = dim_2s32(subrect); + glTexSubImage2D(GL_TEXTURE_2D, 0, subrect.x0, subrect.y0, rect_size.x, rect_size.y, fmt_info.format, fmt_info.base_type, data); + glBindTexture(GL_TEXTURE_2D, 0); + } +} + +//- rjf: buffers + +r_hook R_Handle +r_buffer_alloc(R_ResourceKind kind, U64 size, void *data) +{ + R_Handle result = {0}; + return result; +} + +r_hook void +r_buffer_release(R_Handle buffer) +{ + // TODO(rjf) +} + +//- rjf: frame markers + +r_hook void +r_begin_frame(void) +{ + // TODO(rjf) +} + +r_hook void +r_end_frame(void) +{ + // TODO(rjf) +} + +r_hook void +r_window_begin_frame(OS_Handle os, R_Handle r) +{ + r_ogl_os_select_window(os, r); + + //- rjf: unpack window viewport info + Rng2F32 client_rect = os_client_rect_from_window(os); + Vec2F32 client_rect_dim = dim_2f32(client_rect); + + //- rjf: clear and reset state + glClearColor(0, 0, 0, 0); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glViewport(0, 0, (S32)client_rect_dim.x, (S32)client_rect_dim.y); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); +} + +r_hook void +r_window_end_frame(OS_Handle os, R_Handle r) +{ + r_ogl_os_window_swap(os, r); +} + +//- rjf: render pass submission + +r_hook void +r_window_submit(OS_Handle window, R_Handle window_equip, R_PassList *passes) +{ + Rng2F32 viewport_rect = os_client_rect_from_window(window); + Vec2F32 viewport_dim = dim_2f32(viewport_rect); + for(R_PassNode *pass_n = passes->first; pass_n != 0; pass_n = pass_n->next) + { + R_Pass *pass = &pass_n->v; + switch(pass->kind) + { + default:{}break; + + //////////////////////// + //- rjf: ui rendering pass + // + case R_PassKind_UI: + { + //- rjf: unpack params + R_PassParams_UI *params = pass->params_ui; + R_BatchGroup2DList *rect_batch_groups = ¶ms->rects; + + //- rjf: draw each batch group + GLuint shader = r_ogl_state->shaders[R_OGL_ShaderKind_Rect]; + glBindVertexArrayScope(r_ogl_state->all_purpose_vao) glUseProgramScope(shader) + { + for(R_BatchGroup2DNode *group_n = rect_batch_groups->first; group_n != 0; group_n = group_n->next) + { + R_BatchList *batches = &group_n->batches; + R_BatchGroup2DParams *group_params = &group_n->params; + + //- rjf: unpack texture + R_Tex2DFormat texture_fmt = R_Tex2DFormat_RGBA8; + GLuint texture_id = r_ogl_state->white_texture; + { + R_OGL_Tex2D *tex = r_ogl_tex2d_from_handle(group_params->tex); + if(tex != 0) + { + texture_id = tex->id; + texture_fmt = tex->fmt; + } + } + + //- rjf: get & fill buffer + GLuint buffer = r_ogl_state->scratch_buffer_2mb; // TODO(rjf): r_ogl_instance_buffer_from_size(batches->byte_count); + { + glBindBuffer(GL_ARRAY_BUFFER, buffer); + U64 off = 0; + for(R_BatchNode *batch_n = batches->first; batch_n != 0; batch_n = batch_n->next) + { + glBufferSubData(GL_ARRAY_BUFFER, off, batch_n->v.byte_count, batch_n->v.v); + off += batch_n->v.byte_count; + } + } + + //- rjf: bind input attributes + { + R_OGL_AttributeArray inputs = r_ogl_shader_kind_input_attributes_table[R_OGL_ShaderKind_Rect]; + U64 off = 0; + for EachIndex(idx, inputs.count) + { + glEnableVertexAttribArray(inputs.v[idx].index); + glVertexAttribDivisor(inputs.v[idx].index, 1); + glVertexAttribPointer(inputs.v[idx].index, inputs.v[idx].count, inputs.v[idx].type, GL_FALSE, sizeof(R_Rect2DInst), (void *)(off)); + // TODO(rjf): this is not correct if type != GL_FLOAT + off += inputs.v[idx].count*sizeof(F32); + } + } + + //- rjf: bind texture + { + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, texture_id); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + switch(group_params->tex_sample_kind) + { + default: + case R_Tex2DSampleKind_Nearest: + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + }break; + case R_Tex2DSampleKind_Linear: + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + }break; + } + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); + glUniform1i(glGetUniformLocation(shader, "u_tex_color"), 0); + } + + //- rjf: upload misc. uniforms + { + Mat4x4F32 texture_sample_channel_map = r_sample_channel_map_from_tex2dformat(texture_fmt); + glUniformMatrix4fv(glGetUniformLocation(shader, "u_texture_sample_channel_map"), 1, 0, &texture_sample_channel_map.v[0][0]); + glUniform2f(glGetUniformLocation(shader, "u_viewport_size_px"), viewport_dim.x, viewport_dim.y); + glUniform1f(glGetUniformLocation(shader, "u_opacity"), 1.f - group_params->transparency); + } + + //- rjf: set up scissor + if(group_params->clip.x0 != 0 || + group_params->clip.x1 != 0 || + group_params->clip.y0 != 0 || + group_params->clip.y1 != 0) + { + Rng2F32 clip = group_params->clip; + glScissor(clip.x0, viewport_dim.y - clip.y1, (clip.x1-clip.x0) + 1, (clip.y1-clip.y0)+1); + glEnable(GL_SCISSOR_TEST); + } + + //- rjf: draw + { + glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, batches->byte_count / batches->bytes_per_inst); + } + + //- rjf: unset scissor + glDisable(GL_SCISSOR_TEST); + } + } + }break; + + //////////////////////// + //- rjf: blur rendering pass + // + case R_PassKind_Blur: + { + R_PassParams_Blur *params = pass->params_blur; + GLuint shader = r_ogl_state->shaders[R_OGL_ShaderKind_Blur]; + glBindVertexArrayScope(r_ogl_state->all_purpose_vao) glUseProgramScope(shader) + { + // TODO(rjf) + } + }break; + + + //////////////////////// + //- rjf: 3d geometry rendering pass + // + case R_PassKind_Geo3D: + { + //- rjf: unpack params + R_PassParams_Geo3D *params = pass->params_geo3d; + R_BatchGroup3DMap *mesh_group_map = ¶ms->mesh_batches; + // TODO(rjf) + }break; + } + } +} diff --git a/src/render/opengl/render_opengl.h b/src/render/opengl/render_opengl.h new file mode 100644 index 00000000..39ee5b5d --- /dev/null +++ b/src/render/opengl/render_opengl.h @@ -0,0 +1,217 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef RENDER_OPENGL_H +#define RENDER_OPENGL_H + +//////////////////////////////// +//~ rjf: OS Backend Includes + +#if OS_WINDOWS +# include "render/opengl/win32/render_opengl_win32.h" +#else +# error OS portion of OpenGL rendering backend not defined. +#endif + +//////////////////////////////// +//~ rjf: Shader Metadata Types + +typedef struct R_OGL_Attribute R_OGL_Attribute; +struct R_OGL_Attribute +{ + U64 index; + String8 name; + GLenum type; + U64 count; +}; + +typedef struct R_OGL_AttributeArray R_OGL_AttributeArray; +struct R_OGL_AttributeArray +{ + R_OGL_Attribute *v; + U64 count; +}; + +//////////////////////////////// +//~ rjf: Generated Code + +#include "render/opengl/generated/render_opengl.meta.h" + +//////////////////////////////// +//~ rjf: Defines + +typedef char GLchar; +typedef ptrdiff_t GLsizeiptr; +typedef ptrdiff_t GLintptr; + +#define GL_FRAMEBUFFER_SRGB 0x8DB9 +#define GL_TEXTURE_MAX_LEVEL 0x813D + +#define GL_R8 0x8229 + +#define GL_ARRAY_BUFFER 0x8892 +#define GL_STREAM_DRAW 0x88E0 +#define GL_STREAM_READ 0x88E1 +#define GL_STREAM_COPY 0x88E2 +#define GL_STATIC_DRAW 0x88E4 +#define GL_STATIC_READ 0x88E5 +#define GL_STATIC_COPY 0x88E6 +#define GL_DYNAMIC_DRAW 0x88E8 +#define GL_DYNAMIC_READ 0x88E9 +#define GL_DYNAMIC_COPY 0x88EA + +#define GL_FRAGMENT_SHADER 0x8B30 +#define GL_VERTEX_SHADER 0x8B31 +#define GL_TESS_EVALUATION_SHADER 0x8E87 +#define GL_TESS_CONTROL_SHADER 0x8E88 +#define GL_INFO_LOG_LENGTH 0x8B84 + +#define GL_TEXTURE_2D_ARRAY 0x8C1A + +#define GL_COMPILE_STATUS 0x8B81 + +#define GL_TEXTURE0 0x84C0 +#define GL_TEXTURE1 0x84C1 +#define GL_TEXTURE2 0x84C2 +#define GL_TEXTURE3 0x84C3 +#define GL_TEXTURE4 0x84C4 +#define GL_TEXTURE5 0x84C5 +#define GL_TEXTURE6 0x84C6 +#define GL_TEXTURE7 0x84C7 +#define GL_TEXTURE8 0x84C8 +#define GL_TEXTURE9 0x84C9 +#define GL_TEXTURE10 0x84CA +#define GL_TEXTURE11 0x84CB +#define GL_TEXTURE12 0x84CC +#define GL_TEXTURE13 0x84CD +#define GL_TEXTURE14 0x84CE +#define GL_TEXTURE15 0x84CF +#define GL_TEXTURE16 0x84D0 +#define GL_TEXTURE17 0x84D1 +#define GL_TEXTURE18 0x84D2 +#define GL_TEXTURE19 0x84D3 +#define GL_TEXTURE20 0x84D4 +#define GL_TEXTURE21 0x84D5 +#define GL_TEXTURE22 0x84D6 +#define GL_TEXTURE23 0x84D7 +#define GL_TEXTURE24 0x84D8 +#define GL_TEXTURE25 0x84D9 +#define GL_TEXTURE26 0x84DA +#define GL_TEXTURE27 0x84DB +#define GL_TEXTURE28 0x84DC +#define GL_TEXTURE29 0x84DD +#define GL_TEXTURE30 0x84DE +#define GL_TEXTURE31 0x84DF + +#define GL_DEBUG_OUTPUT 0x92E0 + +//////////////////////////////// +//~ rjf: OpenGL Procedure List + +#define R_OGL_ProcedureXList \ +X(glGenBuffers, void, (GLsizei n, GLuint *buffers))\ +X(glBindBuffer, void, (GLenum target, GLuint buffer))\ +X(glGenVertexArrays, void, (GLsizei n, GLuint *arrays))\ +X(glBindVertexArray, void, (GLuint array))\ +X(glCreateProgram, GLuint, (void))\ +X(glCreateShader, GLuint, (GLenum type))\ +X(glShaderSource, void, (GLuint shader, GLsizei count, char **string, GLint *length))\ +X(glCompileShader, void, (GLuint shader))\ +X(glGetShaderiv, void, (GLuint shader, GLenum pname, GLint *params))\ +X(glGetShaderInfoLog, void, (GLuint shader, GLsizei bufSize, GLsizei *length, char *infoLog))\ +X(glGetProgramiv, void, (GLuint program, GLenum pname, GLint *params))\ +X(glGetProgramInfoLog, void, (GLuint program, GLsizei bufSize, GLsizei *length, char *infoLog))\ +X(glAttachShader, void, (GLuint program, GLuint shader))\ +X(glLinkProgram, void, (GLuint program))\ +X(glValidateProgram, void, (GLuint program))\ +X(glDeleteShader, void, (GLuint shader))\ +X(glUseProgram, void, (GLuint program))\ +X(glGetUniformLocation, GLint, (GLuint program, char *name))\ +X(glGetAttribLocation, GLint, (GLuint program, char *name))\ +X(glEnableVertexAttribArray, void, (GLuint index))\ +X(glVertexAttribPointer, void, (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer))\ +X(glBufferData, void, (GLenum target, ptrdiff_t size, void *data, GLenum usage))\ +X(glBufferSubData, void, (GLenum target, ptrdiff_t offset, ptrdiff_t size, const void *data))\ +X(glBlendFuncSeparate, void, (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha))\ +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(glUniformMatrix4fv, void, (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value))\ +X(glUniform1i, void, (GLint location, GLint v0))\ +X(glTexImage3D, void, (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void *pixels))\ +X(glTexSubImage3D, void, (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *pixels))\ +X(glGenerateMipmap, void, (GLenum target))\ +X(glBindAttribLocation, void, (GLuint programObj, GLuint index, char *name))\ +X(glBindFragDataLocation, void, (GLuint program, GLuint color, char *name))\ +X(glActiveTexture, void, (GLenum texture))\ +X(glVertexAttribDivisor, void, (GLuint index, GLuint divisor))\ +X(glDrawArraysInstanced, void, (GLenum mode, GLint first, GLsizei count, GLsizei instancecount))\ +X(glDebugMessageCallback, void, (void (*)(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam), void *user_data))\ + +#define X(name, r, p) typedef r name##_FunctionType p; +R_OGL_ProcedureXList +#undef X +#define X(name, r, p) global name##_FunctionType *name = 0; +R_OGL_ProcedureXList +#undef X + +//////////////////////////////// +//~ rjf: State Types + +typedef struct R_OGL_FormatInfo R_OGL_FormatInfo; +struct R_OGL_FormatInfo +{ + GLint internal_format; + GLenum format; + GLenum base_type; +}; + +typedef struct R_OGL_Tex2D R_OGL_Tex2D; +struct R_OGL_Tex2D +{ + R_OGL_Tex2D *next; + GLuint id; + R_ResourceKind resource_kind; + R_Tex2DFormat fmt; + Vec2S32 size; +}; + +typedef struct R_OGL_State R_OGL_State; +struct R_OGL_State +{ + Arena *arena; + R_OGL_Tex2D *free_tex2d; + GLuint shaders[R_OGL_ShaderKind_COUNT]; + GLuint all_purpose_vao; + GLuint scratch_buffer_2mb; + GLuint white_texture; +}; + +//////////////////////////////// +//~ rjf: Globals + +global R_OGL_State *r_ogl_state = 0; + +//////////////////////////////// +//~ rjf: Helpers + +internal R_Handle r_ogl_handle_from_tex2d(R_OGL_Tex2D *t); +internal R_OGL_Tex2D *r_ogl_tex2d_from_handle(R_Handle h); +internal R_OGL_FormatInfo r_ogl_format_info_from_tex2dformat(R_Tex2DFormat fmt); +internal void r_ogl_debug_message_callback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam); + +#define glUseProgramScope(...) DeferLoop(glUseProgram(__VA_ARGS__), glUseProgram(0)) +#define glBindVertexArrayScope(...) DeferLoop(glBindVertexArray(__VA_ARGS__), glBindVertexArray(0)) + +//////////////////////////////// +//~ rjf: OS-Specific Hooks + +internal VoidProc *r_ogl_os_load_procedure(char *name); +internal void r_ogl_os_init(CmdLine *cmdln); +internal R_Handle r_ogl_os_window_equip(OS_Handle window); +internal void r_ogl_os_window_unequip(OS_Handle os, R_Handle r); +internal void r_ogl_os_select_window(OS_Handle os, R_Handle r); +internal void r_ogl_os_window_swap(OS_Handle os, R_Handle r); + +#endif // RENDER_OPENGL_H diff --git a/src/render/opengl/render_opengl.mdesk b/src/render/opengl/render_opengl.mdesk new file mode 100644 index 00000000..d96874cd --- /dev/null +++ b/src/render/opengl/render_opengl.mdesk @@ -0,0 +1,292 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Shader Table + +@table(name name_lower input_atts output_atts) +R_OGL_ShaderTable: +{ + {Rect rect r_ogl_rect_input_attributes r_ogl_single_color_output_attributes} + {Blur blur 0 r_ogl_single_color_output_attributes} +} + +@enum R_OGL_ShaderKind: +{ + @expand(R_OGL_ShaderTable a) `$(a.name)`, + COUNT +} + +@data(String8) r_ogl_shader_kind_name_table: +{ + @expand(R_OGL_ShaderTable a) `str8_lit_comp("$(a.name_lower)")`, +} + +@data(`String8 *`) r_ogl_shader_kind_vshad_src_table: +{ + @expand(R_OGL_ShaderTable a) `&r_ogl_$(a.name_lower)_vshad_src`, +} + +@data(`String8 *`) r_ogl_shader_kind_pshad_src_table: +{ + @expand(R_OGL_ShaderTable a) `&r_ogl_$(a.name_lower)_pshad_src`, +} + +@data(R_OGL_AttributeArray) r_ogl_shader_kind_input_attributes_table: +{ + @expand(R_OGL_ShaderTable a) `{ $(a.input_atts), $(a.input_atts != 0 -> "ArrayCount(" .. a.input_atts .. ")") }`, +} + +@data(R_OGL_AttributeArray) r_ogl_shader_kind_output_attributes_table: +{ + @expand(R_OGL_ShaderTable a) `{ $(a.output_atts), ArrayCount($(a.output_atts)) }`, +} + +//////////////////////////////// +//~ rjf: UI Rectangle Shaders + +//- rjf: vertex +@embed_string r_ogl_rect_vshad_src: +``` +#version 330 core + +in vec4 c2v_dst_rect; +in vec4 c2v_src_rect; +in vec4 c2v_colors_0; +in vec4 c2v_colors_1; +in vec4 c2v_colors_2; +in vec4 c2v_colors_3; +in vec4 c2v_corner_radii; +in vec4 c2v_style; // x: border_thickness_px, y: softness_px, z: omit_texture, w: unused + +out vec2 v2p_sdf_sample_pos; +out vec2 v2p_texcoord_pct; +out vec2 v2p_rect_half_size_px; +out vec4 v2p_tint; +out float v2p_corner_radius; +out float v2p_border_thickness; +out float v2p_softness; +out float v2p_omit_texture; + +uniform sampler2D u_tex_color; +uniform vec2 u_viewport_size_px; + +void main(void) +{ + // rjf: constants + vec2 vertices[] = vec2[](vec2(-1, -1), vec2(-1, +1), vec2(+1, -1), vec2(+1, +1)); + + // rjf: find dst position + vec2 dst_half_size = (c2v_dst_rect.zw - c2v_dst_rect.xy) / 2; + vec2 dst_center = (c2v_dst_rect.zw + c2v_dst_rect.xy) / 2; + vec2 dst_position = vertices[gl_VertexID] * dst_half_size + dst_center; + + // rjf: find src position + vec2 src_half_size = (c2v_src_rect.zw - c2v_src_rect.xy) / 2; + vec2 src_center = (c2v_src_rect.zw + c2v_src_rect.xy) / 2; + vec2 src_position = vertices[gl_VertexID] * src_half_size + src_center; + + // rjf: find color + vec4 colors[] = vec4[](c2v_colors_0, c2v_colors_1, c2v_colors_2, c2v_colors_3); + vec4 color = colors[gl_VertexID]; + + // rjf: find corner radius + float corner_radii[] = float[](c2v_corner_radii.x, c2v_corner_radii.y, c2v_corner_radii.z, c2v_corner_radii.w); + float corner_radius = corner_radii[gl_VertexID]; + + // rjf: fill outputs + vec2 dst_verts_pct = vec2(((gl_VertexID >> 1) != 1) ? 1.f : 0.f, + ((gl_VertexID & 1) != 0) ? 0.f : 1.f); + ivec2 u_tex_color_size_i = textureSize(u_tex_color, 0); + vec2 u_tex_color_size = vec2(float(u_tex_color_size_i.x), float(u_tex_color_size_i.y)); + { + gl_Position = vec4(2 * dst_position.x / u_viewport_size_px.x - 1, + 2 * (1 - dst_position.y / u_viewport_size_px.y) - 1, + 0.0, 1.0); + v2p_sdf_sample_pos = (2.f * dst_verts_pct - 1.f) * dst_half_size; + v2p_texcoord_pct = src_position / u_tex_color_size; + v2p_rect_half_size_px = dst_half_size; + v2p_tint = color; + v2p_corner_radius = corner_radius; + v2p_border_thickness = c2v_style.x; + v2p_softness = c2v_style.y; + v2p_omit_texture = c2v_style.z; + } +} +``` + +//- rjf: pixel +@embed_string r_ogl_rect_pshad_src: +``` +#version 330 core + +in vec2 v2p_sdf_sample_pos; +in vec2 v2p_texcoord_pct; +in vec2 v2p_rect_half_size_px; +in vec4 v2p_tint; +in float v2p_corner_radius; +in float v2p_border_thickness; +in float v2p_softness; +in float v2p_omit_texture; + +out vec4 final_color; + +uniform float u_opacity; +uniform sampler2D u_tex_color; +uniform mat4 u_texture_sample_channel_map; + +float rect_sdf(vec2 sample_pos, vec2 rect_half_size, float r) +{ + return length(max(abs(sample_pos) - rect_half_size + r, 0.0)) - r; +} + +float linear_from_srgb_f32(float x) +{ + return x < 0.0404482362771082 ? x / 12.92 : pow((x + 0.055) / 1.055, 2.4); +} + +vec4 linear_from_srgba(vec4 v) +{ + vec4 result = vec4(linear_from_srgb_f32(v.x), + linear_from_srgb_f32(v.y), + linear_from_srgb_f32(v.z), + v.w); + return result; +} + +void main(void) +{ + // rjf: sample texture + vec4 albedo_sample = vec4(1, 1, 1, 1); + if(v2p_omit_texture < 1) + { + albedo_sample = u_texture_sample_channel_map * texture(u_tex_color, v2p_texcoord_pct); + albedo_sample = linear_from_srgba(albedo_sample); + } + + // rjf: sample for borders + float border_sdf_t = 1; + if(v2p_border_thickness > 0) + { + float border_sdf_s = rect_sdf(v2p_sdf_sample_pos, + v2p_rect_half_size_px - vec2(v2p_softness*2.f, v2p_softness*2.f) - v2p_border_thickness, + max(v2p_corner_radius-v2p_border_thickness, 0)); + border_sdf_t = smoothstep(0, 2*v2p_softness, border_sdf_s); + } + if(border_sdf_t < 0.001f) + { + discard; + } + + // rjf: sample for corners + float corner_sdf_t = 1; + if(v2p_corner_radius > 0 || v2p_softness > 0.75f) + { + float corner_sdf_s = rect_sdf(v2p_sdf_sample_pos, + v2p_rect_half_size_px - vec2(v2p_softness*2.f, v2p_softness*2.f), + v2p_corner_radius); + corner_sdf_t = 1-smoothstep(0, 2*v2p_softness, corner_sdf_s); + } + + // rjf: form+return final color + final_color = albedo_sample; + final_color *= v2p_tint; + final_color.a *= u_opacity; + final_color.a *= corner_sdf_t; + final_color.a *= border_sdf_t; +} +``` + +//////////////////////////////// +//~ rjf: Blur Shaders + +//- rjf: vertex +@embed_string r_ogl_blur_vshad_src: +``` +#version 330 core + +uniform vec4 rect; +uniform vec4 corner_radii_px; +uniform vec2 viewport_size; +uniform uint blur_count; + +out vec2 texcoord; +out vec2 sdf_sample_pos; +out vec2 rect_half_size; +out float corner_radius; + +void main(void) +{ + vec2 vertex_positions_scrn[] = vec2[](rect.xw, + rect.xy, + rect.zw, + rect.zy); + float corner_radii_px[] = float[](corner_radii_px.y, + corner_radii_px.x, + corner_radii_px.w, + corner_radii_px.z); + vec2 cornercoords_pct = vec2((gl_VertexID >> 1) != 0 ? 1.f : 0.f, + (gl_VertexID & 1) != 0 ? 0.f : 1.f); + + vec2 vertex_position_pct = vertex_positions_scrn[gl_VertexID] / viewport_size; + vec2 vertex_position_scr = 2.f * vertex_position_pct - 1.f; + + vec2 rect_half_size = vec2((rect.z-rect.x)/2, (rect.w-rect.y)/2); + + gl_Position = vec4(vertex_position_scr.x, -vertex_position_scr.y, 0.f, 1.f); + texcoord = vertex_position_pct; + sdf_sample_pos = (2.f * cornercoords_pct - 1.f) * rect_half_size; + rect_half_size = rect_half_size - 2.f; + corner_radius = corner_radii_px[gl_VertexID]; +} +``` + +//- rjf: pixel +@embed_string r_ogl_blur_pshad_src: +``` +#version 330 core + +uniform sampler2D tex; +uniform vec4 kernel[32]; +uniform int blur_count; +uniform vec2 direction; + +in vec2 texcoord; +in vec2 sdf_sample_pos; +in vec2 rect_half_size; +in float corner_radius; + +out vec4 final_color; + +float rect_sdf(vec2 sample_pos, vec2 rect_half_size, float r) +{ + return length(max(abs(sample_pos) - rect_half_size + r, 0.0)) - r; +} + +void main(void) +{ + // rjf: blend weighted texture samples into color + vec3 color = kernel[0].x * texture(tex, texcoord).rgb; + + for(int i = 1; i < blur_count; i += 1) + { + float weight = kernel[i].x; + float offset = kernel[i].y; + color += weight * texture(tex, texcoord - offset * direction).rgb; + color += weight * texture(tex, texcoord + offset * direction).rgb; + } + + // rjf: sample for corners + float corner_sdf_s = rect_sdf(sdf_sample_pos, rect_half_size, corner_radius); + float corner_sdf_t = 1-smoothstep(0, 2, corner_sdf_s); + + // rjf: weight output color by sdf + // this is doing alpha testing, leave blurring only where mostly opaque pixels are + if(corner_sdf_t < 0.9f) + { + discard; + } + + final_color = vec4(color, 1.f); +} +``` diff --git a/src/render/opengl/win32/render_opengl_win32.c b/src/render/opengl/win32/render_opengl_win32.c new file mode 100644 index 00000000..7b40d711 --- /dev/null +++ b/src/render/opengl/win32/render_opengl_win32.c @@ -0,0 +1,184 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +internal VoidProc * +r_ogl_os_load_procedure(char *name) +{ + VoidProc *p = (VoidProc*)wglGetProcAddress(name); + if(p == (VoidProc*)1 || p == (VoidProc*)2 || p == (VoidProc*)3 || p == (VoidProc*)-1) + { + p = 0; + } + return p; +} + +internal void +r_ogl_os_init(CmdLine *cmdline) +{ + //- rjf: create bootstrapping window + HWND bootstrap_hwnd = 0; + { + WNDCLASSEXW wndclass = { sizeof(wndclass) }; + wndclass.lpfnWndProc = DefWindowProcW; + wndclass.hInstance = GetModuleHandle(0); + wndclass.lpszClassName = L"bootstrap-window"; + ATOM wndatom = RegisterClassExW(&wndclass); + bootstrap_hwnd = CreateWindowExW(0, L"bootstrap-window", L"", 0, + CW_USEDEFAULT, CW_USEDEFAULT, + CW_USEDEFAULT, CW_USEDEFAULT, + 0, 0, wndclass.hInstance, 0); + } + + //- rjf: grab dc + HDC dc = GetDC(bootstrap_hwnd); + + //- rjf: build pixel format descriptor + int pf = 0; + { + PIXELFORMATDESCRIPTOR pfd = {sizeof(pfd)}; + pfd.nVersion = 1; + pfd.dwFlags = PFD_DRAW_TO_WINDOW|PFD_SUPPORT_OPENGL|PFD_DOUBLEBUFFER; + pfd.iPixelType = PFD_TYPE_RGBA; + pfd.cColorBits = 32; + pfd.cDepthBits = 24; + pfd.cStencilBits = 8; + pfd.iLayerType = PFD_MAIN_PLANE; + pf = ChoosePixelFormat(dc, &pfd); + BOOL describe = DescribePixelFormat(dc, pf, sizeof(pfd), &pfd); + BOOL set_pf = SetPixelFormat(dc, pf, &pfd); + } + + //- rjf: make bootstrap ctx + make current + HGLRC bootstrap_ctx = wglCreateContext(dc); + wglMakeCurrent(dc, bootstrap_ctx); + + //- rjf: load modern extensions + wglChoosePixelFormatARB = (FNWGLCHOOSEPIXELFORMATARBPROC*) r_ogl_os_load_procedure("wglChoosePixelFormatARB"); + wglCreateContextAttribsARB = (FNWGLCREATECONTEXTATTRIBSARBPROC*)r_ogl_os_load_procedure("wglCreateContextAttribsARB"); + wglSwapIntervalEXT = (FNWGLSWAPINTERVALEXTPROC*) r_ogl_os_load_procedure("wglSwapIntervalEXT"); + + //- rjf: set up real pixel format + { + int pf_attribs_i[] = + { + WGL_DRAW_TO_WINDOW_ARB, 1, + WGL_SUPPORT_OPENGL_ARB, 1, + WGL_DOUBLE_BUFFER_ARB, 1, + WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB, + WGL_COLOR_BITS_ARB, 32, + WGL_DEPTH_BITS_ARB, 24, + WGL_STENCIL_BITS_ARB, 8, + 0 + }; + UINT num_formats = 0; + wglChoosePixelFormatARB(dc, pf_attribs_i, 0, 1, &pf, &num_formats); + } + + //- rjf: make real gl ctx + HGLRC real_ctx = 0; + if(pf) + { + const int context_attribs[] = + { + WGL_CONTEXT_MAJOR_VERSION_ARB, 3, + WGL_CONTEXT_MINOR_VERSION_ARB, 3, + WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_DEBUG_BIT_ARB, + 0 + }; + real_ctx = wglCreateContextAttribsARB(dc, bootstrap_ctx, context_attribs); + r_ogl_w32_hglrc = real_ctx; + } + + //- rjf: clean up bootstrap context + wglMakeCurrent(dc, 0); + wglDeleteContext(bootstrap_ctx); + wglMakeCurrent(dc, real_ctx); + wglSwapIntervalEXT(1); + ReleaseDC(bootstrap_hwnd, dc); + DestroyWindow(bootstrap_hwnd); +} + +internal R_Handle +r_ogl_os_window_equip(OS_Handle window) +{ + //- rjf: unpack window + OS_W32_Window *w = os_w32_window_from_handle(window); + HWND hwnd = w->hwnd; + HDC hdc = GetDC(hwnd); + + //- rjf: select in ctx + wglMakeCurrent(hdc, r_ogl_w32_hglrc); + + //- rjf: setup real pixel format + int pixel_format = 0; + UINT num_formats = 0; + int pf_attribs_i[] = + { + WGL_DRAW_TO_WINDOW_ARB, GL_TRUE, + WGL_SUPPORT_OPENGL_ARB, GL_TRUE, + WGL_DOUBLE_BUFFER_ARB, GL_TRUE, + WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB, + WGL_COLOR_BITS_ARB, 32, + WGL_DEPTH_BITS_ARB, 24, + WGL_STENCIL_BITS_ARB, 8, + 0 + }; + wglChoosePixelFormatARB(hdc, + pf_attribs_i, + 0, + 1, + &pixel_format, + &num_formats); + + // NOTE(rjf): This doesn't seem to be necessary for SetPixelFormat, we can + // just pass 0 for it, and SetPixelFormat needs to be called here, but the + // docs don't seem to suggest that 0 is an acceptable value, so I am just + // filling this out with the same attribs as that for the wgl function, + // and passing it. + PIXELFORMATDESCRIPTOR pfd = {sizeof(pfd)}; + pfd.nVersion = 1; + pfd.dwFlags = PFD_DRAW_TO_WINDOW|PFD_SUPPORT_OPENGL|PFD_DOUBLEBUFFER; + pfd.iPixelType = PFD_TYPE_RGBA; + pfd.cColorBits = 32; + pfd.cDepthBits = 24; + pfd.cStencilBits = 8; + pfd.iLayerType = PFD_MAIN_PLANE; + + //- rjf: set pixel format + SetPixelFormat(hdc, pixel_format, &pfd); + + //- rjf: release hdc + ReleaseDC(hwnd, hdc); + R_Handle result = {0}; + return result; +} + +internal void +r_ogl_os_window_unequip(OS_Handle os, R_Handle r) +{ +} + +internal void +r_ogl_os_select_window(OS_Handle os, R_Handle r) +{ + OS_W32_Window *w = os_w32_window_from_handle(os); + if(w != 0) + { + HWND hwnd = w->hwnd; + HDC hdc = GetDC(hwnd); + wglMakeCurrent(hdc, r_ogl_w32_hglrc); + ReleaseDC(hwnd, hdc); + } +} + +internal void +r_ogl_os_window_swap(OS_Handle os, R_Handle r) +{ + OS_W32_Window *w = os_w32_window_from_handle(os); + if(w != 0) + { + HDC dc = GetDC(w->hwnd); + SwapBuffers(dc); + ReleaseDC(w->hwnd, dc); + } +} diff --git a/src/render/opengl/win32/render_opengl_win32.h b/src/render/opengl/win32/render_opengl_win32.h new file mode 100644 index 00000000..24e53c14 --- /dev/null +++ b/src/render/opengl/win32/render_opengl_win32.h @@ -0,0 +1,34 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef RENDER_OPENGL_WIN32_H +#define RENDER_OPENGL_WIN32_H + +#include +#pragma comment(lib, "opengl32") + +#define WGL_DRAW_TO_WINDOW_ARB 0x2001 +#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_DEPTH_BITS_ARB 0x2022 +#define WGL_STENCIL_BITS_ARB 0x2023 +#define WGL_TYPE_RGBA_ARB 0x202B +#define WGL_CONTEXT_DEBUG_BIT_ARB 0x00000001 + +#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 +#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 +#define WGL_CONTEXT_FLAGS_ARB 0x2094 + +typedef BOOL WINAPI FNWGLCHOOSEPIXELFORMATARBPROC(HDC hdc, const int *piAttribIList, const FLOAT *pfAttribFList, UINT nMaxFormats, int *piFormats, UINT *nNumFormats); +typedef HGLRC WINAPI FNWGLCREATECONTEXTATTRIBSARBPROC(HDC hDC, HGLRC hShareContext, const int *attribList); +typedef BOOL WINAPI FNWGLSWAPINTERVALEXTPROC(int interval); + +FNWGLCHOOSEPIXELFORMATARBPROC *wglChoosePixelFormatARB; +FNWGLCREATECONTEXTATTRIBSARBPROC *wglCreateContextAttribsARB; +FNWGLSWAPINTERVALEXTPROC *wglSwapIntervalEXT; + +global HGLRC r_ogl_w32_hglrc = 0; + +#endif // RENDER_OPENGL_WIN32_H diff --git a/src/render/render_core.c b/src/render/render_core.c index 4d6ebdf7..80ca84bf 100644 --- a/src/render/render_core.c +++ b/src/render/render_core.c @@ -6,6 +6,33 @@ #include "generated/render.meta.c" +//////////////////////////////// +//~ rjf: Helpers + +internal Mat4x4F32 +r_sample_channel_map_from_tex2dformat(R_Tex2DFormat fmt) +{ + Mat4x4F32 result = + { + { + {1, 0, 0, 0}, + {0, 1, 0, 0}, + {0, 0, 1, 0}, + {0, 0, 0, 1}, + } + }; + switch(fmt) + { + default:{}break; + case R_Tex2DFormat_R8: + { + MemoryZeroArray(result.v[0]); + result.v[0][0] = result.v[0][1] = result.v[0][2] = result.v[0][3] = 1.f; + }break; + } + return result; +} + //////////////////////////////// //~ rjf: Basic Type Functions diff --git a/src/render/render_core.h b/src/render/render_core.h index a7ef99c9..7725c5a3 100644 --- a/src/render/render_core.h +++ b/src/render/render_core.h @@ -194,6 +194,11 @@ struct R_PassList U64 count; }; +//////////////////////////////// +//~ rjf: Helpers + +internal Mat4x4F32 r_sample_channel_map_from_tex2dformat(R_Tex2DFormat fmt); + //////////////////////////////// //~ rjf: Handle Type Functions diff --git a/src/render/render_inc.c b/src/render/render_inc.c index 52e5c365..bb15be84 100644 --- a/src/render/render_inc.c +++ b/src/render/render_inc.c @@ -10,6 +10,8 @@ # include "stub/render_stub.c" #elif R_BACKEND == R_BACKEND_D3D11 # include "d3d11/render_d3d11.c" +#elif R_BACKEND == R_BACKEND_OPENGL +# include "opengl/render_opengl.c" #else # error Renderer backend not specified. #endif diff --git a/src/render/render_inc.h b/src/render/render_inc.h index 64802e5c..d0e87c3c 100644 --- a/src/render/render_inc.h +++ b/src/render/render_inc.h @@ -9,12 +9,15 @@ #define R_BACKEND_STUB 0 #define R_BACKEND_D3D11 1 +#define R_BACKEND_OPENGL 2 //////////////////////////////// //~ rjf: Decide On Backend #if !defined(R_BACKEND) && OS_WINDOWS # define R_BACKEND R_BACKEND_D3D11 +#elif !defined(R_BACKEND) && OS_LINUX +# define R_BACKEND R_BACKEND_OPENGL #endif //////////////////////////////// @@ -29,6 +32,8 @@ # include "stub/render_stub.h" #elif R_BACKEND == R_BACKEND_D3D11 # include "d3d11/render_d3d11.h" +#elif R_BACKEND == R_BACKEND_OPENGL +# include "opengl/render_opengl.h" #else # error Renderer backend not specified. #endif