From 911df4ce0563ac4b2b919c50a4ea7167fb619af0 Mon Sep 17 00:00:00 2001 From: Simon Anciaux <14198779+mrmixer@users.noreply.github.com> Date: Fri, 23 Feb 2024 19:03:22 +0100 Subject: [PATCH] code/bin/4ed_build.cpp: Removed OpenGL.lib. It's added with #pragma comment in win32_opengl.cpp so that we don't need to do special things to keep OpenGL and DirectX both working. code/platform_win32/win32_4ed.cpp: - Modification to be able to choose between OpenGL and DirectX. You choose between the two by defining WIN32_DIRECTX macro. If you don't define it it uses OpenGL. - Note that win32_gl_create_window and other part of the file that depend on OpenGL or DirectX have been moved to win32_opengl.cpp and win32_directx.cpp. - Fixed os_popup_error using the title as the message and the message as the title. code/platform_win32/win32_opengl.cpp: Contains previous code that was in win32_4ed.cpp. code/platform_win32/win32_directx.cpp: Code for creating a DirectX window and context. code/platform_win32/4ed_directx_render.cpp: Imlementation of the 4coder renderer using DirectX. --- code/bin/4ed_build.cpp | 2 +- code/platform_win32/4ed_directx_render.cpp | 571 +++++++++++++++++++++ code/platform_win32/win32_4ed.cpp | 354 ++++--------- code/platform_win32/win32_directx.cpp | 491 ++++++++++++++++++ code/platform_win32/win32_opengl.cpp | 257 ++++++++++ 5 files changed, 1417 insertions(+), 258 deletions(-) create mode 100644 code/platform_win32/4ed_directx_render.cpp create mode 100644 code/platform_win32/win32_directx.cpp create mode 100644 code/platform_win32/win32_opengl.cpp diff --git a/code/bin/4ed_build.cpp b/code/bin/4ed_build.cpp index fa0a8e81..12f21128 100644 --- a/code/bin/4ed_build.cpp +++ b/code/bin/4ed_build.cpp @@ -185,7 +185,7 @@ get_defines_from_flags(Arena *arena, u32 flags){ "-wd4611 -wd4189 -WX -GR- -EHa- -nologo -FC" #define CL_LIBS_COMMON \ -"user32.lib winmm.lib gdi32.lib opengl32.lib comdlg32.lib userenv.lib " +"user32.lib winmm.lib gdi32.lib comdlg32.lib userenv.lib " #define CL_LIBS_X64 CL_LIBS_COMMON FOREIGN_WIN "\\x64\\freetype.lib" #define CL_LIBS_X86 CL_LIBS_COMMON FOREIGN_WIN "\\x86\\freetype.lib" diff --git a/code/platform_win32/4ed_directx_render.cpp b/code/platform_win32/4ed_directx_render.cpp new file mode 100644 index 00000000..405e4490 --- /dev/null +++ b/code/platform_win32/4ed_directx_render.cpp @@ -0,0 +1,571 @@ + +#define DIRECTX_MAX_TEXTURE_COUNT 32 + +struct DirectXTexture { + ID3D11Texture2D* pointer; + ID3D11ShaderResourceView* view; +}; + +struct GL_Program { + ID3D11VertexShader* vertex; + ID3D11InputLayout* layout; + ID3D11PixelShader* pixel; + b8 valid; +}; + +struct DirectX { + b8 initialized; + ID3D11Device1* device; + ID3D11DeviceContext1* context; + IDXGISwapChain1* swap_chain; + ID3D11SamplerState* sampler; + ID3D11RenderTargetView* render_target_view; + GL_Program gpu_program; + ID3D11Buffer* vertex_buffer; + ID3D11Buffer* constants_buffer; + + // NOTE(simon): To keep the API the same since the OpenGL texture handle are store in other places + // than the graphics parts (e.g. in the font Face struct), we create an array of textures, and use + // the indices as texture handles. + DirectXTexture textures[ DIRECTX_MAX_TEXTURE_COUNT + 1 ]; + // NOTE(simon): First slot in the array should not be used so we can consider an index of 0 to be invalid. + // OpenGL should not return 0 for texture handle, so we sort of do the same. + u32 texture_count; // TODO(simon): We don't reuse freed texture handle. Use a free list instead ? +}; + +global DirectX g_directx = { }; + +// NOTE(simon): Passing 0 for textid use the reserved texture in the array, and passing a resource +// view of zero unbinds the resource. +internal void +gl__bind_texture(Render_Target *t, i32 texid){ + if (t->bound_texture != texid){ + DirectXTexture* texture = g_directx.textures + texid; + g_directx.context->PSSetShaderResources( 0, 1, &texture->view ); + t->bound_texture = texid; + } +} + +internal void +gl__bind_any_texture(Render_Target *t){ + if (t->bound_texture == 0){ + Assert(t->fallback_texture_id != 0); + DirectXTexture* texture = g_directx.textures + t->fallback_texture_id; + g_directx.context->PSSetShaderResources( 0, 1, &texture->view ); + t->bound_texture = t->fallback_texture_id; + } +} + +internal u32 +gl__get_texture(Vec3_i32 dim, Texture_Kind texture_kind){ + + u32 tex = 0; + + if ( g_directx.texture_count < DIRECTX_MAX_TEXTURE_COUNT + 1 ) { + + tex = g_directx.texture_count; + + DirectXTexture* texture = g_directx.textures + tex; + Assert( texture->pointer == 0 ); + Assert( texture->view == 0 ); + + D3D11_TEXTURE2D_DESC texture_desc = { 0 }; + texture_desc.Width = dim.x; + texture_desc.Height = dim.y; + texture_desc.MipLevels = 1; + texture_desc.ArraySize = dim.z; + texture_desc.Format = DXGI_FORMAT_A8_UNORM; + texture_desc.SampleDesc.Count = 1; + texture_desc.Usage = D3D11_USAGE_DEFAULT; + texture_desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; + texture_desc.CPUAccessFlags = 0; // D3D11_CPU_ACCESS_WRITE; + + HRESULT hr = g_directx.device->CreateTexture2D( &texture_desc, 0, &texture->pointer ); + + if ( SUCCEEDED( hr ) ) { + hr = g_directx.device->CreateShaderResourceView( ( ID3D11Resource* ) texture->pointer, 0, &texture->view ); + } + + if ( SUCCEEDED( hr ) ) { + g_directx.texture_count++; + } else { + tex = 0; + + if ( texture->pointer ) { + texture->pointer->Release( ); + texture->pointer = 0; + } + + if ( texture->view ) { + texture->view->Release( ); + texture->view = 0; + } + } + } + + return(tex); +} + +internal b32 +gl__fill_texture(Texture_Kind texture_kind, u32 texture, Vec3_i32 p, Vec3_i32 dim, void *data){ + + // NOTE(simon): The OpenGL version always returns false. + b32 result = false; + + // NOTE(simon): In the OpenGL version, if we don't pass zero as texture handle, the function + // works on the currently bound texture. In directx we need to get the texture pointer. We + // could retrieve that from Render_Target->bound_texture, but we don't have that as a parameter + // to this function and don't want to change the signature since it's used by the font + // rendering code and other platforms. Fortunately the only call that specified 0 for the + // texture handle was for the creation of the fallback texture in gl_render, and we can modify + // that call to pass the fallback texture handle. + Assert( texture != 0 ); + + if (dim.x > 0 && dim.y > 0 && dim.z > 0){ + + DirectXTexture* tex = g_directx.textures + texture; + + D3D11_BOX box = { }; + box.left = p.x; + box.right = p.x + dim.x; + box.top = p.y; + box.bottom = p.y + dim.y; + box.front = p.z; + box.back = p.z + dim.z; + // TODO(simon): Try to make the font renderer several texture in the array to verify this works. + g_directx.context->UpdateSubresource( tex->pointer, 0, &box, data, dim.x, dim.x * dim.y ); + } + + return(result); +} + +internal void gl__free_texture( u32 texture ) { + + if ( texture ) { + + DirectXTexture* tex = g_directx.textures + texture; + + if ( tex->view ) { + tex->view->Release( ); + tex->view = 0; + } + + if ( tex->pointer ) { + tex->pointer->Release( ); + tex->pointer = 0; + } + } +} + +char *gl__vertex = R"foo( + +// NOTE(simon): The layout of this is (constants are store in 16 bytes vectors (4 floats)) +// vector1: view_m._11, view_m._12, 0, 0 +// vector2: view_m._21, view_m._22, view_t.x, view_t.y +cbuffer constants : register( b0 ) { + row_major float2x2 view_m; + float2 view_t; +} + +struct input_t { + float2 vertex_p : POSITION; + float3 vertex_t : UVW; + float4 vertex_c : COLOR; + float vertex_ht : THICKNESS; +}; + +struct output_t { + float4 position : SV_POSITION; + float4 color : COLOR; + float3 uvw : UVW; + float2 xy : XY; + float2 adjusted_half_dim: HALF_DIM; + float half_thickness : THICKNESS; +}; + +output_t main(input_t input) { + + output_t output; + + output.position = float4( mul( view_m, ( input.vertex_p - view_t ) ), 0.0, 1.0 ); + // NOTE(simon): The input colors are BGRA, we need them as RGBA. + output.color = input.vertex_c.zyxw; + output.uvw = input.vertex_t; + output.xy = input.vertex_p; + output.half_thickness = input.vertex_ht; + + float2 center = input.vertex_t.xy; + float2 half_dim = abs( input.vertex_p - center ); + output.adjusted_half_dim = half_dim - input.vertex_t.zz + float2( 0.5, 0.5 ); + + return output; +} +)foo"; + +char *gl__fragment = R"foo( + +struct input_t { + float4 position : SV_POSITION; + float4 color : COLOR; + float3 uvw : UVW; + float2 xy : XY; + float2 adjusted_half_dim: HALF_DIM; + float half_thickness : THICKNESS; +}; + +Texture2DArray alpha : register( t0 ); +SamplerState alpha_sampler : register( s0 ); + +float rectangle_sd( float2 p, float2 b ) { + + float2 d = abs( p ) - b; + return( length( max( d, float2( 0.0, 0.0 ) ) ) + min( max( d.x, d.y ), 0.0 ) ); +} + +float4 main( input_t input ) : SV_TARGET { + + float has_thickness = step( 0.49, input.half_thickness ); + float does_not_have_thickness = 1.0 - has_thickness; + + float sample_value = alpha.Sample( alpha_sampler, input.uvw ).a; + sample_value *= does_not_have_thickness; + + float2 center = input.uvw.xy; + float roundness = input.uvw.z; + float sd = rectangle_sd( input.xy - center, input.adjusted_half_dim ); + sd = sd - roundness; + sd = abs( sd + input.half_thickness ) - input.half_thickness; + float shape_value = 1.0 - smoothstep(-1.0, 0.0, sd); + shape_value *= has_thickness; + + float4 result = float4( input.color.xyz, input.color.a * ( sample_value + shape_value ) ); + return result; +} +)foo"; + +// NOTE(simon): This function is not generic. It can compile any shader, but the vertex input +// layout is fixed. 4coder only has one vertex format and shader, so we could remove this function +// and move its content in the win32_gl_create_window. I removed the header parameter as it's not +// useful in directx. +internal GL_Program +gl__make_program( char* vertex, char* pixel ) { + + GL_Program result = { }; + + u32 vertex_length = 0; + + while ( vertex && vertex[ vertex_length ] != 0 ) { + vertex_length++; + } + + u32 pixel_length = 0; + + while ( pixel && pixel[ pixel_length ] != 0 ) { + pixel_length++; + } + + ID3DBlob* vs_blob = 0; + ID3DBlob* vs_error_blob = 0; + ID3D11VertexShader* vertex_shader = 0; + ID3D11InputLayout* input_layout = 0; + + ID3DBlob* ps_blob = 0; + ID3DBlob* ps_error_blob = 0; + ID3D11PixelShader* pixel_shader = 0; + + do { + + HRESULT hr = D3DCompile( vertex, vertex_length, 0, 0, 0, "main", "vs_5_0", 0, 0, &vs_blob, &vs_error_blob ); + + if ( FAILED( hr ) ) { + log_os( "Failed to compile vertex shader.\n" ); + + if ( vs_error_blob ) { + u8* error_message = ( u8* ) vs_error_blob->GetBufferPointer( ); + u32 length = ( u32 ) vs_error_blob->GetBufferSize( ); + log_os( "vertex shader error:\n%.*s\n", length, error_message ); + } + + break; + } + + hr = g_directx.device->CreateVertexShader( vs_blob->GetBufferPointer( ), vs_blob->GetBufferSize( ), 0, &vertex_shader ); + + if ( FAILED( hr ) ) { + log_os( "Failed to create a vertex shader.\n" ); + break; + } + + + D3D11_INPUT_ELEMENT_DESC layout_desc[ 4 ] = { }; + + layout_desc[ 0 ].SemanticName = "POSITION"; + layout_desc[ 0 ].Format = DXGI_FORMAT_R32G32_FLOAT; + layout_desc[ 0 ].AlignedByteOffset = 0; + layout_desc[ 0 ].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; + + layout_desc[ 1 ].SemanticName = "UVW"; + layout_desc[ 1 ].Format = DXGI_FORMAT_R32G32B32_FLOAT; + layout_desc[ 1 ].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT; + layout_desc[ 1 ].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; + + layout_desc[ 2 ].SemanticName = "COLOR"; + layout_desc[ 2 ].Format = DXGI_FORMAT_R8G8B8A8_UNORM; + layout_desc[ 2 ].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT; + layout_desc[ 2 ].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; + + layout_desc[ 3 ].SemanticName = "THICKNESS"; + layout_desc[ 3 ].Format = DXGI_FORMAT_R32_FLOAT; + layout_desc[ 3 ].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT; + layout_desc[ 3 ].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; + + hr = g_directx.device->CreateInputLayout( layout_desc, ArrayCount( layout_desc ), vs_blob->GetBufferPointer( ), vs_blob->GetBufferSize( ), &input_layout ); + + if ( FAILED( hr ) ) { + log_os( "Failed to create input layout.\n" ); + break; + } + + + hr = D3DCompile( pixel, pixel_length, 0, 0, 0, "main", "ps_5_0", 0, 0, &ps_blob, &ps_error_blob ); + + if ( FAILED( hr ) ) { + log_os( "Failed to compile pixel shader.\n" ); + + if ( ps_error_blob ) { + u8* error_message = ( u8* ) ps_error_blob->GetBufferPointer( ); + u32 length = ( u32 ) ps_error_blob->GetBufferSize( ); + log_os( "pixel shader error:\n%.*s\n", length, error_message ); + } + + break; + } + + hr = g_directx.device->CreatePixelShader( ps_blob->GetBufferPointer( ), ps_blob->GetBufferSize( ), 0, &pixel_shader ); + + if ( FAILED( hr ) ) { + log_os( "Failed to create a pixel shader.\n" ); + break; + } + + result.vertex = vertex_shader; + result.layout = input_layout; + result.pixel = pixel_shader; + result.valid = true; + + } while ( 0 ); + + if ( vs_blob ) { + vs_blob->Release( ); + vs_blob = 0; + } + + if ( vs_error_blob ) { + vs_error_blob->Release( ); + vs_error_blob = 0; + } + + if ( ps_blob ) { + ps_blob->Release( ); + ps_blob = 0; + } + + if ( ps_error_blob ) { + ps_error_blob->Release( ); + ps_error_blob = 0; + } + + if ( !result.valid ) { + + if ( vertex_shader ) { + vertex_shader->Release( ); + vertex_shader = 0; + } + + if ( input_layout ) { + input_layout->Release( ); + input_layout = 0; + } + + if ( pixel_shader ) { + pixel_shader->Release( ); + pixel_shader = 0; + } + + os_popup_error( "Error", "Shader compilation failed." ); + } + + return result; +} + +internal void +gl_render(Render_Target *t){ + Font_Set *font_set = (Font_Set*)t->font_set; + + local_persist b32 first_call = true; + + if (first_call){ + + // NOTE(simon): Most of the code here has been moved in win32_gl_create_window because if + // that code fails we should exit the application directly. + first_call = false; + + u32 stride = sizeof( Render_Vertex ); + u32 offset = 0; + + g_directx.context->IASetVertexBuffers( 0, 1, &g_directx.vertex_buffer, &stride, &offset ); + g_directx.context->IASetInputLayout( g_directx.gpu_program.layout ); + g_directx.context->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST ); + + g_directx.context->VSSetShader( g_directx.gpu_program.vertex, 0, 0 ); + g_directx.context->VSSetConstantBuffers( 0, 1, &g_directx.constants_buffer ); + + g_directx.context->PSSetShader( g_directx.gpu_program.pixel, 0, 0 ); + g_directx.context->PSSetSamplers( 0, 1, &g_directx.sampler ); + + { + t->fallback_texture_id = gl__get_texture(V3i32(2, 2, 1), TextureKind_Mono); + u8 white_block[] = { 0xFF, 0xFF, 0xFF, 0xFF, }; + // NOTE(simon): Passing the fallback texture, because we can't rely on the fact that + // gl__get_texture has bound the fallback texture. + gl__fill_texture(TextureKind_Mono, t->fallback_texture_id, V3i32(0, 0, 0), V3i32(2, 2, 1), white_block); + } + } + + // NOTE(simon): OMSetRenderTargets needs to be set each frame when using a FLIP swap chain. + g_directx.context->OMSetRenderTargets( 1, &g_directx.render_target_view, 0 ); + + i32 width = t->width; + i32 height = t->height; + + // NOTE(simon): Viewport (0, 0) is top left in directx. Important for viewport and scissor calls. + + D3D11_VIEWPORT viewport = { + 0, // TopLeftX + 0, // TopLeftY + ( float ) width, // Width + ( float ) height, // Height + 0, // MinDepth + 1// MaxDepth + }; + + g_directx.context->RSSetViewports( 1, &viewport ); + + D3D11_RECT scissor = { + 0, // left + 0, // top + width, // right + height // bottom + }; + + g_directx.context->RSSetScissorRects( 1, &scissor ); + + float magenta[ 4 ] = { 1.0f, 0.0f, 1.0f, 1.0f }; + g_directx.context->ClearRenderTargetView( g_directx.render_target_view, magenta ); + + // NOTE(simon): The constants (uniforms) were set in the render loop in the OpenGL version. + // But since they don't vary between draw calls I moved the code before the render loop. + D3D11_MAPPED_SUBRESOURCE constants_map = { }; + HRESULT hr = g_directx.context->Map( ( ID3D11Resource* ) g_directx.constants_buffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &constants_map ); + + // NOTE(simon): The layout of the constants buffer was a bit confusing. This link explains a + // little about how data is laid out: https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-packing-rules + // The article doesn't explain anything about matrices. What I found out while making this work is + // that each row or column (depending on if we use column or row major matrices) of a matrix + // needs to start on a new 16 bytes vector. For a 2 by 2 matrix, this means that there are two + // register elements at the end of the first vector that aren't used. + // Another thing is that the second vector only needs the first two elements for the matrix, + // so the two elements we want to put next can be in the same vector. + + // NOTE(simon): The code here could be shorter, but I prefer to make it clear what's happening. + f32 view_m[ 4 ] = { + 2.0f / width, 0, + 0, -2.0f / height + }; + f32 view_t[ 2 ] = { width / 2.0f, height / 2.0f }; + + f32* vector_1 = ( f32* ) constants_map.pData; + f32* vector_2 = vector_1 + 4; + + vector_1[ 0 ] = view_m[ 0 ]; + vector_1[ 1 ] = view_m[ 1 ]; + vector_1[ 2 ] = 0; // Padding + vector_1[ 3 ] = 0; // Padding + + vector_2[ 0 ] = view_m[ 2 ]; + vector_2[ 1 ] = view_m[ 3 ]; + vector_2[ 2 ] = view_t[ 0 ]; + vector_2[ 3 ] = view_t[ 1 ]; + + g_directx.context->Unmap( ( ID3D11Resource* ) g_directx.constants_buffer, 0 ); + + gl__bind_texture( t, 0 ); + + for (Render_Free_Texture *free_texture = t->free_texture_first; + free_texture != 0; + free_texture = free_texture->next){ + + gl__free_texture( free_texture->tex_id ); + } + + t->free_texture_first = 0; + t->free_texture_last = 0; + + for (Render_Group *group = t->group_first; + group != 0; + group = group->next){ + Rect_i32 box = Ri32(group->clip_box); + + D3D11_RECT group_scissor = { }; + group_scissor.left = box.x0; + group_scissor.right = box.x1; + group_scissor.top = box.y0; + group_scissor.bottom = box.y1; + + g_directx.context->RSSetScissorRects( 1, &group_scissor ); + + i32 vertex_count = group->vertex_list.vertex_count; + if (vertex_count > 0){ + Face *face = font_set_face_from_id(font_set, group->face_id); + if (face != 0){ + gl__bind_texture(t, face->texture); + } + else{ + gl__bind_any_texture(t); + } + + // NOTE(simon): We fill the buffer, draw what we filled and then do the next group, + // which allows to always start drawing from vertex 0. Alternatively we could do a pass + // to fill the vertex buffer completly so we only map the vertex buffer once, and then + // a second pass that just execute the draw calls. It doesn't seems necessary since we + // have less than 10 draw call. + + D3D11_MAPPED_SUBRESOURCE vertex_map = { }; + hr = g_directx.context->Map( ( ID3D11Resource* ) g_directx.vertex_buffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &vertex_map ); + + if ( FAILED( hr ) ) { + // NOTE(simon): It's improbable that Map will fail, but if it does we just stop + // rendering, and we'll try on the next frame. We could just skip the group and try + // with the next (using 'continue' instead of 'break'), but Map would probably fail + // again. Waiting for the next frame "might" work. I don't really know. We could + // also just exit the application assuming we won't be able to render anything. + break; + } + + u8* bytes = ( u8* ) vertex_map.pData; + + for (Render_Vertex_Array_Node *node = group->vertex_list.first; + node != 0; + node = node->next){ + + i32 size = node->vertex_count*sizeof(*node->vertices); + memcpy( bytes, node->vertices, size ); + bytes += size; + } + + g_directx.context->Unmap( ( ID3D11Resource* ) g_directx.vertex_buffer, 0 ); + + g_directx.context->Draw( vertex_count, 0 ); + } + } +} \ No newline at end of file diff --git a/code/platform_win32/win32_4ed.cpp b/code/platform_win32/win32_4ed.cpp index 8afcd3f1..ba81f334 100644 --- a/code/platform_win32/win32_4ed.cpp +++ b/code/platform_win32/win32_4ed.cpp @@ -12,6 +12,8 @@ // #define FPS 144 // #define frame_useconds (1000000 / FPS) +#define WIN32_DIRECTX + #include #include "4coder_base_types.h" @@ -56,7 +58,6 @@ #define function static #include "win32_utf8.h" -#include "win32_gl.h" //////////////////////////////// @@ -649,18 +650,18 @@ system_cli_end_update_sig(){ function void os_popup_error(char *title, char *message){ - MessageBoxA(0, title, message, MB_OK); + MessageBoxA(0, message, title, MB_OK); ExitProcess(1); } #include "4ed_font_provider_freetype.h" #include "4ed_font_provider_freetype.cpp" -#include -#include "opengl/4ed_opengl_defines.h" -#define GL_FUNC(N,R,P) typedef R (CALL_CONVENTION N##_Function)P; N##_Function *N = 0; -#include "opengl/4ed_opengl_funcs.h" -#include "opengl/4ed_opengl_render.cpp" +#if defined( WIN32_DIRECTX ) +#include "win32_directx.cpp" +#else +#include "win32_opengl.cpp" +#endif internal graphics_get_texture_sig(){ @@ -823,6 +824,72 @@ win32_resize(i32 width, i32 height){ if (width > 0 && height > 0){ target.width = width; target.height = height; + +#if defined( WIN32_DIRECTX ) + + HRESULT hr = S_OK; + ID3D11Texture2D* frame_buffer = 0; + + do { + + if ( g_directx.initialized ) { + + if ( g_directx.render_target_view ) { + g_directx.render_target_view->Release( ); + g_directx.render_target_view = 0; + } + + hr = g_directx.swap_chain->ResizeBuffers( 0, 0, 0, DXGI_FORMAT_UNKNOWN, 0 ); + + if ( FAILED( hr ) ) { + log_os( " Failed to resize the swap chain buffers.\n" ); + break; + } + + hr = g_directx.swap_chain->GetBuffer( 0, __uuidof( ID3D11Texture2D ), ( void** ) &frame_buffer ); + + if ( FAILED( hr ) ) { + log_os( " Failled to get the swap chain back buffer.\n" ); + break; + } + + D3D11_RENDER_TARGET_VIEW_DESC render_target_view_desc = { 0 }; + // NOTE(simon): 4coder checks for sRGB support but never actually enables it in the + // OpenGL version. Note that enabling it would require to convert collors passed to the + // shader to linear (when using sRBG back buffer, the shader values must be linear + // values). This would be more problematic than just passing linear values as the + // blending wouldn't produce the same result as with sRGB off. + // render_target_view_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; + render_target_view_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + render_target_view_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D; + + hr = g_directx.device->CreateRenderTargetView( ( ID3D11Resource* ) frame_buffer, &render_target_view_desc, &g_directx.render_target_view ); + + if ( FAILED( hr ) ) { + log_os( " Failed to create a render target view.\n" ); + break; + } + } + + } while ( 0 ); + + if ( frame_buffer ) { + frame_buffer->Release( ); + frame_buffer = 0; + } + + if ( FAILED( hr ) ) { + + if ( g_directx.render_target_view ) { + g_directx.render_target_view->Release( ); + g_directx.render_target_view = 0; + } + + // NOTE(simon): Failing here means no rendering possible, so we exit. + exit( 1 ); + } + +#endif } } @@ -901,10 +968,10 @@ internal void date_time_from_win32_system_time(Date_Time *out, SYSTEMTIME *in){ out->year = in->wYear; out->mon = (u8)(in->wMonth - 1); - out->day = (u8)(in->wDay - 1); - out->hour = (u8)(in->wHour); - out->min = (u8)(in->wMinute); - out->sec = (u8)(in->wSecond); + out->day = (u8)(in->wDay - 1); + out->hour = (u8)(in->wHour); + out->min = (u8)(in->wMinute); + out->sec = (u8)(in->wSecond); out->msec = in->wMilliseconds; } @@ -1191,7 +1258,7 @@ win32_proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam){ } }break; } - + b8 ctrl = (controls->r_ctrl || (controls->l_ctrl && !controls->r_alt)); b8 alt = (controls->l_alt || (controls->r_alt && !controls->l_ctrl)); if (win32vars.lctrl_lalt_is_altgr && controls->l_alt && controls->l_ctrl){ @@ -1432,249 +1499,7 @@ win32_proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam){ //- -internal b32 -win32_wgl_good(Void_Func *f){ - return(f != 0 && - f != (Void_Func*)1 && - f != (Void_Func*)2 && - f != (Void_Func*)3 && - f != (Void_Func*)-1); -} -typedef HGLRC (CALL_CONVENTION wglCreateContextAttribsARB_Function)(HDC,HGLRC,i32*); -typedef BOOL (CALL_CONVENTION wglChoosePixelFormatARB_Function)(HDC,i32*,f32*,u32,i32*,u32*); -typedef char* (CALL_CONVENTION wglGetExtensionsStringEXT_Function)(); -typedef VOID (CALL_CONVENTION wglSwapIntervalEXT_Function)(i32); - -global wglCreateContextAttribsARB_Function *wglCreateContextAttribsARB = 0; -global wglChoosePixelFormatARB_Function *wglChoosePixelFormatARB = 0; -global wglGetExtensionsStringEXT_Function *wglGetExtensionsStringEXT = 0; -global wglSwapIntervalEXT_Function *wglSwapIntervalEXT = 0; - -internal b32 -win32_gl_create_window(HWND *wnd_out, HGLRC *context_out, DWORD style, RECT rect){ - HINSTANCE this_instance = GetModuleHandle(0); - - local_persist b32 srgb_support = false; - local_persist b32 register_success = true; - local_persist b32 first_call = true; - if (first_call){ - log_os(" GL bootstrapping...\n"); - - first_call = false; - - // NOTE(allen): Create the GL bootstrap window - log_os(" registering bootstrap class...\n"); - WNDCLASSW wglclass = {}; - wglclass.lpfnWndProc = DefWindowProcW; - wglclass.hInstance = this_instance; - wglclass.lpszClassName = L"wgl-loader"; - if (RegisterClassW(&wglclass) == 0){ - register_success = false; - goto fail_register; - } - - log_os(" creating bootstrap window...\n"); - HWND wglwindow = CreateWindowW(wglclass.lpszClassName, L"", 0, 0, 0, 0, 0, - 0, 0, this_instance, 0); - if (wglwindow == 0){ - register_success = false; - goto fail_register; - } - - // NOTE(allen): Create the GL bootstrap context - log_os(" setting bootstrap pixel format...\n"); - - HDC wgldc = GetDC(wglwindow); - - PIXELFORMATDESCRIPTOR format = {}; - format.nSize = sizeof(format); - format.nVersion = 1; - format.dwFlags = PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW | PFD_DOUBLEBUFFER; - format.iPixelType = PFD_TYPE_RGBA; - format.cColorBits = 32; - format.cAlphaBits = 8; - format.cDepthBits = 24; - format.iLayerType = PFD_MAIN_PLANE; - i32 suggested_format_index = ChoosePixelFormat(wgldc, &format); - if (!SetPixelFormat(wgldc, suggested_format_index, &format)){ - register_success = false; - goto fail_register; - } - - log_os(" creating bootstrap GL context...\n"); - HGLRC wglcontext = wglCreateContext(wgldc); - if (wglcontext == 0){ - register_success = false; - goto fail_register; - } - - log_os(" making bootstrap GL context current...\n"); - if (!wglMakeCurrent(wgldc, wglcontext)){ - register_success = false; - goto fail_register; - } - - // NOTE(allen): Load wgl extensions - log_os(" loading wgl extensions...\n"); - -#define LoadWGL(f,l) Stmnt((f) = (f##_Function*)wglGetProcAddress(#f); \ -(l) = (l) && win32_wgl_good((Void_Func*)(f));) - - b32 load_success = true; - LoadWGL(wglCreateContextAttribsARB, load_success); - LoadWGL(wglChoosePixelFormatARB, load_success); - LoadWGL(wglGetExtensionsStringEXT, load_success); - - if (!load_success){ - register_success = false; - goto fail_register; - } - - log_os(" checking wgl extensions...\n"); - char *extensions_c = wglGetExtensionsStringEXT(); - String_Const_u8 extensions = SCu8((u8*)extensions_c); - - { - String_Const_u8 s = string_skip_whitespace(extensions); - for (;s.size > 0;){ - u64 end = string_find_first_whitespace(s); - String_Const_u8 m = string_prefix(s, end); - if (string_match(m, string_u8_litexpr("WGL_EXT_framebuffer_sRGB")) || - string_match(m, string_u8_litexpr("WGL_ARB_framebuffer_sRGB"))){ - srgb_support = true; - } - else if (string_match(m, string_u8_litexpr("WGL_EXT_swap_interval"))){ - b32 wgl_swap_interval_ext = true; - LoadWGL(wglSwapIntervalEXT, wgl_swap_interval_ext); - if (!wgl_swap_interval_ext){ - wglSwapIntervalEXT = 0; - } - } - s = string_skip_whitespace(string_skip(s, end)); - } - } - - // NOTE(allen): Load gl functions - log_os(" loading core GL functions...\n"); - -#define GL_FUNC(f,R,P) LoadWGL(f,load_success); -#include "opengl/4ed_opengl_funcs.h" - - if (!load_success){ - register_success = false; - goto fail_register; - } - - // NOTE(allen): Cleanup the GL bootstrap resources - log_os(" cleaning up boostrap resources...\n"); - - ReleaseDC(wglwindow, wgldc); - DestroyWindow(wglwindow); - wglDeleteContext(wglcontext); - - // NOTE(allen): Register the graphics window class - log_os(" registering graphics class...\n"); - - WNDCLASSW wndclass = {}; - wndclass.style = CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS; - wndclass.lpfnWndProc = win32_proc; - wndclass.hIcon = LoadIconW(GetModuleHandle(0), L"main"); - wndclass.hInstance = this_instance; - wndclass.lpszClassName = L"GRAPHICS-WINDOW-NAME"; - if (RegisterClassW(&wndclass) == 0){ - register_success = false; - goto fail_register; - } - } - fail_register:; - - b32 result = false; - if (register_success){ - // NOTE(allen): Create the graphics window - log_os(" creating graphics window...\n"); - - HWND wnd = CreateWindowExW(0, L"GRAPHICS-WINDOW-NAME", L"GRAPHICS", style, - CW_USEDEFAULT, CW_USEDEFAULT, rect.right - rect.left, rect.bottom - rect.top, - 0, 0, this_instance, 0); - - *wnd_out = 0; - *context_out = 0; - if (wnd != 0){ - log_os(" setting graphics pixel format...\n"); - - HDC dc = GetDC(wnd); - - PIXELFORMATDESCRIPTOR format = {}; - - i32 pixel_attrib_list[] = { - /* 0*/WGL_DRAW_TO_WINDOW_ARB, TRUE, - /* 2*/WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB, - /* 4*/WGL_SUPPORT_OPENGL_ARB, TRUE, - /* 6*/WGL_DOUBLE_BUFFER_ARB, false, - /* 8*/WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB, - /*10*/WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB, GL_TRUE, - /*12*/0, - }; - if (!srgb_support){ - pixel_attrib_list[10] = 0; - } - - i32 suggested_format_index = 0; - u32 ignore = 0; - if (!wglChoosePixelFormatARB(dc, pixel_attrib_list, 0, 1, &suggested_format_index, &ignore)){ - goto fail_window_init; - } - - DescribePixelFormat(dc, suggested_format_index, sizeof(format), &format); - if (!SetPixelFormat(dc, suggested_format_index, &format)){ - goto fail_window_init; - } - - log_os(" setting graphics attributes...\n"); - - i32 context_attrib_list[] = { - /*0*/WGL_CONTEXT_MAJOR_VERSION_ARB, 3, - /*2*/WGL_CONTEXT_MINOR_VERSION_ARB, 2, - /*4*/WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB -#if GL_DEBUG_MODE - |WGL_CONTEXT_DEBUG_BIT_ARB -#endif - , - /*6*/WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB, - /*8*/0 - }; - - log_os(" creating graphics GL context...\n"); - HGLRC context = wglCreateContextAttribsARB(dc, 0, context_attrib_list); - if (context == 0){ - goto fail_window_init; - } - - log_os(" making graphics GL context current...\n"); - wglMakeCurrent(dc, context); - - - if (wglSwapIntervalEXT != 0){ - log_os(" setting swap interval...\n"); - wglSwapIntervalEXT(1); - } - *wnd_out = wnd; - *context_out = context; - result = true; - - if (false){ - fail_window_init:; - DWORD error = GetLastError(); - ReleaseDC(wnd, dc); - DestroyWindow(wnd); - SetLastError(error); - } - } - } - - return(result); -} //- @@ -1923,10 +1748,16 @@ WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdS window_rect.bottom - window_rect.top, ((window_style & WS_MAXIMIZE) != 0)); +#if defined( WIN32_DIRECTX ) + if( !win32_gl_create_window( &win32vars.window_handle, window_style, window_rect ) ) { + exit(1); + } +#else HGLRC window_opengl_context = 0; if (!win32_gl_create_window(&win32vars.window_handle, &window_opengl_context, window_style, window_rect)){ exit(1); } +#endif log_os(" window created successfully\n"); @@ -2027,7 +1858,7 @@ WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdS // NOTE(allen): while we're doing this (and possibly sleeping) // we can let async processes get there time in. - system_release_global_frame_mutex(win32vars.tctx); + system_release_global_frame_mutex(win32vars.tctx); b32 get_more_messages = true; do{ @@ -2105,7 +1936,7 @@ WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdS } }while (get_more_messages); - system_acquire_global_frame_mutex(win32vars.tctx); + system_acquire_global_frame_mutex(win32vars.tctx); } // NOTE(allen): Mouse Out of Window Detection @@ -2210,10 +2041,15 @@ WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdS win32vars.lctrl_lalt_is_altgr = (b8)result.lctrl_lalt_is_altgr; // NOTE(allen): render +#if defined( WIN32_DIRECTX ) + gl_render(&target); + g_directx.swap_chain->Present( 1, 0 ); +#else HDC hdc = GetDC(win32vars.window_handle); gl_render(&target); SwapBuffers(hdc); ReleaseDC(win32vars.window_handle, hdc); +#endif // NOTE(allen): toggle full screen if (win32vars.do_toggle){ @@ -2230,7 +2066,7 @@ WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdS } // NOTE(allen): sleep a bit to cool off :) - system_release_global_frame_mutex(win32vars.tctx); + system_release_global_frame_mutex(win32vars.tctx); u64 timer_end = system_now_time(); u64 end_target = timer_start + frame_useconds; @@ -2249,6 +2085,10 @@ WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdS win32vars.first = false; } +#if defined( WIN32_DIRECTX ) && !SHIP_MODE + win32_gl_cleanup( ); +#endif + return(0); } diff --git a/code/platform_win32/win32_directx.cpp b/code/platform_win32/win32_directx.cpp new file mode 100644 index 00000000..8c093ef6 --- /dev/null +++ b/code/platform_win32/win32_directx.cpp @@ -0,0 +1,491 @@ + +#pragma comment(lib, "d3d11.lib") +#pragma comment(lib, "dxgi.lib") +#pragma comment(lib, "d3dcompiler.lib") + +#include +#include +#include +#include + +#if !SHIP_MODE +#include +IDXGIDebug1* dxgi_debug; +#endif + +#include "4ed_directx_render.cpp" + +internal LRESULT CALL_CONVENTION +win32_proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + +internal b32 +win32_gl_create_window(HWND *wnd_out, DWORD style, RECT rect){ + + local_persist b32 srgb_support = false; + local_persist b32 first_call = true; + + b32 result = false; + + *wnd_out = 0; + + Assert( g_directx.initialized == 0 ); + Assert( g_directx.device == 0 ); + Assert( g_directx.context == 0 ); + Assert( g_directx.swap_chain == 0 ); + Assert( g_directx.sampler == 0 ); + Assert( g_directx.render_target_view == 0 ); + Assert( g_directx.gpu_program.vertex == 0 ); + Assert( g_directx.gpu_program.layout == 0 ); + Assert( g_directx.gpu_program.pixel == 0 ); + Assert( g_directx.gpu_program.valid == 0 ); + Assert( g_directx.vertex_buffer == 0 ); + Assert( g_directx.constants_buffer == 0 ); + Assert( g_directx.texture_count == 0 ); + + g_directx = { }; + + HINSTANCE this_instance = GetModuleHandle(0); + + HWND wnd = 0; + + ID3D11Device* base_device = 0; + ID3D11DeviceContext* base_device_context = 0; + IDXGIFactory2* dxgi_factory = 0; + + ID3D11BlendState* blend_state = 0; + ID3D11RasterizerState1* rasterizer_state = 0; + + do { + + // NOTE(simon): There is nothing in the code suggesting that this function could be called several time. + // If it is called several time, we would need to make sure that we cleaned up previous DirectX resources. + // The reason this function would be called twice would be if it failed previously, and in that case we + // clean up everythings so we should be good. Still we assume it's only ever call once. + Assert( first_call ); + + if (first_call){ + + first_call = false; + + // NOTE(allen): Register the graphics window class + log_os(" registering graphics class...\n"); + + WNDCLASSW wndclass = {}; + wndclass.style = CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS; + wndclass.lpfnWndProc = win32_proc; + wndclass.hIcon = LoadIconW(GetModuleHandle(0), L"main"); + wndclass.hInstance = this_instance; + wndclass.lpszClassName = L"GRAPHICS-WINDOW-NAME"; + if (RegisterClassW(&wndclass) == 0){ + break; + } + } + + // NOTE(allen): Create the graphics window + log_os(" creating graphics window...\n"); + + wnd = CreateWindowExW(0, L"GRAPHICS-WINDOW-NAME", L"GRAPHICS", style, + CW_USEDEFAULT, CW_USEDEFAULT, rect.right - rect.left, rect.bottom - rect.top, + 0, 0, this_instance, 0); + + if (wnd == 0) { + log_os( " Failed to create a window.\n" ); + break; + } + + // NOTE(simon): We are creating a directx 11.1 device and context (supported since windows 8). + D3D_FEATURE_LEVEL feature_levels[ ] = { D3D_FEATURE_LEVEL_11_1 }; + + u32 device_flags = 0; + // device_flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT; + +#if !SHIP_MODE + device_flags |= D3D11_CREATE_DEVICE_DEBUG; +#endif + + HRESULT hr = D3D11CreateDevice( 0, D3D_DRIVER_TYPE_HARDWARE, 0, device_flags, feature_levels, ArrayCount( feature_levels ), D3D11_SDK_VERSION, &base_device, 0, &base_device_context ); + + if ( FAILED( hr ) ) { + log_os( " Failed to create a hardware DirectX device and context.\n" ); + log_os( " Trying to create a software (WARP) DirectX device.\n" ); + // NOTE(simon): Try creating a high performance software device as a fallback. + hr = D3D11CreateDevice( 0, D3D_DRIVER_TYPE_WARP, 0, device_flags, feature_levels, ArrayCount( feature_levels ), D3D11_SDK_VERSION, &base_device, 0, &base_device_context ); + } + + if ( FAILED( hr ) ) { + log_os( " Failed to create software (WARP) DirectX device and context.\n" ); + break; + } + + hr = base_device->QueryInterface( __uuidof( ID3D11Device1 ), ( void** ) &g_directx.device ); + + if ( FAILED( hr ) ) { + log_os( " Failed to create a ID3D11Device1.\n" ); + break; + } + + hr = base_device_context->QueryInterface( __uuidof( ID3D11DeviceContext1 ), ( void** ) &g_directx.context ); + + if ( FAILED( hr ) ) { + log_os( " Failed to create a ID3D11DeviceContext1.\n" ); + break; + } + + ID3D11Device1* device = g_directx.device; + ID3D11DeviceContext1* context = g_directx.context; + +#if !SHIP_MODE + ID3D11InfoQueue* info; + hr = device->QueryInterface( __uuidof( ID3D11InfoQueue ), ( void** ) &info ); + + if ( FAILED( hr ) ) { + log_os( " Failed to get ID3D11InfoQueue. This is not important if you're not debugging graphics.\n" ); + } else { + info->SetBreakOnSeverity( D3D11_MESSAGE_SEVERITY_CORRUPTION, TRUE ); + info->SetBreakOnSeverity( D3D11_MESSAGE_SEVERITY_ERROR, TRUE ); + info->Release( ); + } + + hr = DXGIGetDebugInterface1( 0, __uuidof( IDXGIDebug1 ), ( void** ) &dxgi_debug ); + + if ( FAILED( hr ) ) { + log_os( " Failed to get IDXGIDebug1. This is not important if you're not debugging graphics.\n" ); + } +#endif + + // NOTE(simon): sRGB should be supported by any hardware now, but there was a check so I added one. + DXGI_FORMAT back_buffer_format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; + UINT format_support = 0; + + if ( SUCCEEDED( device->CheckFormatSupport( back_buffer_format, &format_support ) ) ) { + u32 required = D3D11_FORMAT_SUPPORT_RENDER_TARGET | D3D11_FORMAT_SUPPORT_DISPLAY; + srgb_support = ( ( format_support & required ) == required ); + } + + if ( !srgb_support ) { + log_os( " sRBG back buffer not supported.\n" ); + back_buffer_format = DXGI_FORMAT_R8G8B8A8_UNORM; + } else { + log_os( " sRBG back buffer supported.\n" ); + } + + hr = CreateDXGIFactory( __uuidof( IDXGIFactory2 ), ( void** ) &dxgi_factory ); + + if ( FAILED( hr ) ) { + log_os( " Failed to create IDXGIFactory2.\n" ); + break; + } + + DXGI_SWAP_CHAIN_DESC1 swap_chain_desc = { }; + // NOTE(simon): Can't request sRGB format here when using FLIP_* swap chain. It's requested we creating the render target view. + // NOTE(simon): 4coder never calls glEnable( GL_FRAMEBUFFER_SRGB ) so we don't enable sRGB. + swap_chain_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + swap_chain_desc.SampleDesc.Count = 1; + swap_chain_desc.SampleDesc.Quality = 0; + swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + swap_chain_desc.BufferCount = 2; + swap_chain_desc.Scaling = DXGI_SCALING_NONE; + swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; + + hr = dxgi_factory->CreateSwapChainForHwnd( device, wnd, &swap_chain_desc, 0, 0, &g_directx.swap_chain ); + + if ( FAILED( hr ) ) { + log_os( " Failed to create IDXGISwapChain1.\n" ); + break; + } + + // NOTE(simon): We setup alpha blending here as it's always on in 4coder. + D3D11_BLEND_DESC blend_state_desc = { }; + blend_state_desc.RenderTarget[ 0 ].BlendEnable = TRUE; + blend_state_desc.RenderTarget[ 0 ].SrcBlend = D3D11_BLEND_SRC_ALPHA; + blend_state_desc.RenderTarget[ 0 ].DestBlend = D3D11_BLEND_INV_SRC_ALPHA; + blend_state_desc.RenderTarget[ 0 ].BlendOp = D3D11_BLEND_OP_ADD; + blend_state_desc.RenderTarget[ 0 ].SrcBlendAlpha = D3D11_BLEND_SRC_ALPHA; + blend_state_desc.RenderTarget[ 0 ].DestBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA; + blend_state_desc.RenderTarget[ 0 ].BlendOpAlpha = D3D11_BLEND_OP_ADD; + blend_state_desc.RenderTarget[ 0 ].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; + + hr = device->CreateBlendState( &blend_state_desc, &blend_state ); + + if ( FAILED( hr ) ) { + log_os( " Failed to create a blend state.\n" ); + break; + } + + log_os( " Setting blend state...\n" ); + context->OMSetBlendState( blend_state, 0, 0xffffffff ); + + // NOTE(simon): Enable scissor and disable backface culling. + D3D11_RASTERIZER_DESC1 rasterizer_desc = { }; + rasterizer_desc.FillMode = D3D11_FILL_SOLID; + rasterizer_desc.CullMode = D3D11_CULL_NONE; + rasterizer_desc.DepthClipEnable = TRUE; + rasterizer_desc.ScissorEnable = TRUE; + + hr = device->CreateRasterizerState1( &rasterizer_desc, &rasterizer_state ); + + if ( FAILED( hr ) ) { + log_os( " Failed to create a rasterizer state.\n" ); + break; + } + + log_os( " Setting rasterizer state...\n" ); + context->RSSetState( rasterizer_state ); + + // NOTE(simon): Not setting depth stencil as 4coder doesn't use it. + // NOTE(simon): Swap interval is a parameter of swap_chain->present. + + D3D11_SAMPLER_DESC linear_desc = { }; + linear_desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; + linear_desc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP; + linear_desc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP; + linear_desc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP; + linear_desc.ComparisonFunc = D3D11_COMPARISON_NEVER; + linear_desc.MinLOD = 0; + linear_desc.MaxLOD = D3D11_FLOAT32_MAX; + + hr = device->CreateSamplerState( &linear_desc, &g_directx.sampler ); + + if ( FAILED( hr ) ) { + log_os( " Failed to create a sampler state.\n" ); + break; + } + + // NOTE(simon): We create the vertex buffer, constants buffers and shader here because if + // we can't create them we won't be able to render anything and so we should just exit the program. + + D3D11_BUFFER_DESC vertex_buffer_desc = { }; + // NOTE(simon): Reserving 400K vertices which is about 11 megabytes and would allow 100K characters. + // On a 1080p monitor, with 4 by 10 pixels characters we would need + // (1920/4)*(1080/10) = 51840 characters to fill the screen. + vertex_buffer_desc.ByteWidth = 400000 * sizeof( Render_Vertex ); + vertex_buffer_desc.Usage = D3D11_USAGE_DYNAMIC; + vertex_buffer_desc.BindFlags = D3D11_BIND_VERTEX_BUFFER; + vertex_buffer_desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; + + hr = device->CreateBuffer( &vertex_buffer_desc, 0, &g_directx.vertex_buffer ); + + if ( FAILED( hr ) ) { + log_os( " Failed to create a vertex buffer.\n" ); + break; + } + + D3D11_BUFFER_DESC constants_buffer_desc = { }; + // NOTE(simon): constants buffer size needs to be a multiple of 16. + // NOTE(simon): The layout is explained where we set the values in the buffer in gl_render. + constants_buffer_desc.ByteWidth = 32; + constants_buffer_desc.Usage = D3D11_USAGE_DYNAMIC; + constants_buffer_desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; + constants_buffer_desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; + + hr = device->CreateBuffer( &constants_buffer_desc, 0, &g_directx.constants_buffer ); + + if ( FAILED( hr ) ) { + log_os( " Failed to create a constants buffer.\n" ); + break; + } + + g_directx.gpu_program = gl__make_program( gl__vertex, gl__fragment ); + + if ( !g_directx.gpu_program.valid ) { + break; + } + + *wnd_out = wnd; + g_directx.texture_count = 1; // NOTE(simon): Reserve the first texture slot as a invalid/unbind texture. + g_directx.initialized = true; + result = true; + + } while ( 0 ); + + if ( !result ) { + + if ( wnd ) { + DestroyWindow( wnd ); + ( *wnd_out ) = 0; + } + + if ( g_directx.context ) { + g_directx.context->OMSetBlendState( 0, 0, 0xffffffff ); + g_directx.context->RSSetState( 0 ); + } + + if ( g_directx.constants_buffer ) { + g_directx.constants_buffer->Release( ); + g_directx.constants_buffer = 0; + } + + if ( g_directx.vertex_buffer ) { + g_directx.vertex_buffer->Release( ); + g_directx.vertex_buffer = 0; + } + + if ( g_directx.gpu_program.valid ) { + + if ( g_directx.gpu_program.vertex ) { + g_directx.gpu_program.vertex->Release( ); + g_directx.gpu_program.vertex = 0; + } + + if ( g_directx.gpu_program.layout ) { + g_directx.gpu_program.layout->Release( ); + g_directx.gpu_program.layout = 0; + } + + if ( g_directx.gpu_program.pixel ) { + g_directx.gpu_program.pixel->Release( ); + g_directx.gpu_program.pixel = 0; + } + } + + // NOTE(simon): No render target view at this point as it's created in the WM_SIZE message. + + if ( g_directx.sampler ) { + g_directx.sampler->Release( ); + g_directx.sampler = 0; + } + + if ( g_directx.swap_chain ) { + g_directx.swap_chain->Release( ); + g_directx.swap_chain = 0; + } + + if ( g_directx.context ) { + g_directx.context->Release( ); + g_directx.context = 0; + } + + if ( g_directx.device ) { + g_directx.device->Release( ); + g_directx.device = 0; + } + + g_directx.initialized = false; + +#if SHIP_MODE + os_popup_error( "Error", "Window creation failed."); +#endif + } + + if( base_device ) { + base_device->Release( ); + base_device = 0; + } + + if ( base_device_context ) { + base_device_context->Release( ); + base_device_context = 0; + } + + if ( dxgi_factory ) { + dxgi_factory->Release( ); + dxgi_factory = 0; + } + + if ( blend_state ) { + blend_state->Release( ); + blend_state = 0; + } + + if ( rasterizer_state ) { + rasterizer_state->Release( ); + rasterizer_state = 0; + } + + return(result); +} + +#if !SHIP_MODE + +// NOTE(simon): Only call this when working on 4coder, to make sure we don't do something stupid. +// In SHIP_MODE we let the os clean up resources. +internal void +win32_gl_cleanup( void ) { + + if ( dxgi_debug && g_directx.initialized ) { + + OutputDebugString( L"win32_gl_cleanup start report...\n" ); + dxgi_debug->ReportLiveObjects( DXGI_DEBUG_ALL, DXGI_DEBUG_RLO_ALL ); + + g_directx.initialized = false; + + if ( g_directx.context ) { + g_directx.context->OMSetBlendState( 0, 0, 0xffffffff ); + g_directx.context->RSSetState( 0 ); + } + + for ( u32 i = 1; i < g_directx.texture_count; i++ ) { + + DirectXTexture* texture = g_directx.textures + i; + + if ( texture->view ) { + texture->view->Release( ); + texture->view = 0; + } + + if ( texture->pointer ) { + texture->pointer->Release( ); + texture->pointer = 0; + } + } + + g_directx.texture_count = 0; + + if ( g_directx.constants_buffer ) { + g_directx.constants_buffer->Release( ); + g_directx.constants_buffer = 0; + } + + if ( g_directx.vertex_buffer ) { + g_directx.vertex_buffer->Release( ); + g_directx.vertex_buffer = 0; + } + + if ( g_directx.gpu_program.valid ) { + + if ( g_directx.gpu_program.vertex ) { + g_directx.gpu_program.vertex->Release( ); + g_directx.gpu_program.vertex = 0; + } + + if ( g_directx.gpu_program.layout ) { + g_directx.gpu_program.layout->Release( ); + g_directx.gpu_program.layout = 0; + } + + if ( g_directx.gpu_program.pixel ) { + g_directx.gpu_program.pixel->Release( ); + g_directx.gpu_program.pixel = 0; + } + } + + if ( g_directx.render_target_view ) { + g_directx.render_target_view->Release( ); + g_directx.render_target_view = 0; + } + + if ( g_directx.sampler ) { + g_directx.sampler->Release( ); + g_directx.sampler = 0; + } + + if ( g_directx.swap_chain ) { + g_directx.swap_chain->Release( ); + g_directx.swap_chain = 0; + } + + if ( g_directx.context ) { + g_directx.context->Release( ); + g_directx.context = 0; + } + + if ( g_directx.device ) { + g_directx.device->Release( ); + g_directx.device = 0; + } + + OutputDebugString( L"win32_gl_cleanup end report (nothing should be printed after this line)...\n" ); + dxgi_debug->ReportLiveObjects( DXGI_DEBUG_ALL, DXGI_DEBUG_RLO_ALL ); + } +} +#endif \ No newline at end of file diff --git a/code/platform_win32/win32_opengl.cpp b/code/platform_win32/win32_opengl.cpp new file mode 100644 index 00000000..8c2c50c9 --- /dev/null +++ b/code/platform_win32/win32_opengl.cpp @@ -0,0 +1,257 @@ + +#pragma comment(lib, "OpenGL32.lib") + +#include "win32_gl.h" + +#include +#include "opengl/4ed_opengl_defines.h" +#define GL_FUNC(N,R,P) typedef R (CALL_CONVENTION N##_Function)P; N##_Function *N = 0; +#include "opengl/4ed_opengl_funcs.h" +#include "opengl/4ed_opengl_render.cpp" + +internal b32 +win32_wgl_good(Void_Func *f){ + return(f != 0 && + f != (Void_Func*)1 && + f != (Void_Func*)2 && + f != (Void_Func*)3 && + f != (Void_Func*)-1); +} + +typedef HGLRC (CALL_CONVENTION wglCreateContextAttribsARB_Function)(HDC,HGLRC,i32*); +typedef BOOL (CALL_CONVENTION wglChoosePixelFormatARB_Function)(HDC,i32*,f32*,u32,i32*,u32*); +typedef char* (CALL_CONVENTION wglGetExtensionsStringEXT_Function)(); +typedef VOID (CALL_CONVENTION wglSwapIntervalEXT_Function)(i32); + +global wglCreateContextAttribsARB_Function *wglCreateContextAttribsARB = 0; +global wglChoosePixelFormatARB_Function *wglChoosePixelFormatARB = 0; +global wglGetExtensionsStringEXT_Function *wglGetExtensionsStringEXT = 0; +global wglSwapIntervalEXT_Function *wglSwapIntervalEXT = 0; + +internal LRESULT CALL_CONVENTION +win32_proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + +internal b32 +win32_gl_create_window(HWND *wnd_out, HGLRC *context_out, DWORD style, RECT rect){ + HINSTANCE this_instance = GetModuleHandle(0); + + local_persist b32 srgb_support = false; + local_persist b32 register_success = true; + local_persist b32 first_call = true; + if (first_call){ + log_os(" GL bootstrapping...\n"); + + first_call = false; + + // NOTE(allen): Create the GL bootstrap window + log_os(" registering bootstrap class...\n"); + WNDCLASSW wglclass = {}; + wglclass.lpfnWndProc = DefWindowProcW; + wglclass.hInstance = this_instance; + wglclass.lpszClassName = L"wgl-loader"; + if (RegisterClassW(&wglclass) == 0){ + register_success = false; + goto fail_register; + } + + log_os(" creating bootstrap window...\n"); + HWND wglwindow = CreateWindowW(wglclass.lpszClassName, L"", 0, 0, 0, 0, 0, + 0, 0, this_instance, 0); + if (wglwindow == 0){ + register_success = false; + goto fail_register; + } + + // NOTE(allen): Create the GL bootstrap context + log_os(" setting bootstrap pixel format...\n"); + + HDC wgldc = GetDC(wglwindow); + + PIXELFORMATDESCRIPTOR format = {}; + format.nSize = sizeof(format); + format.nVersion = 1; + format.dwFlags = PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW | PFD_DOUBLEBUFFER; + format.iPixelType = PFD_TYPE_RGBA; + format.cColorBits = 32; + format.cAlphaBits = 8; + format.cDepthBits = 24; + format.iLayerType = PFD_MAIN_PLANE; + i32 suggested_format_index = ChoosePixelFormat(wgldc, &format); + if (!SetPixelFormat(wgldc, suggested_format_index, &format)){ + register_success = false; + goto fail_register; + } + + log_os(" creating bootstrap GL context...\n"); + HGLRC wglcontext = wglCreateContext(wgldc); + if (wglcontext == 0){ + register_success = false; + goto fail_register; + } + + log_os(" making bootstrap GL context current...\n"); + if (!wglMakeCurrent(wgldc, wglcontext)){ + register_success = false; + goto fail_register; + } + + // NOTE(allen): Load wgl extensions + log_os(" loading wgl extensions...\n"); + +#define LoadWGL(f,l) Stmnt((f) = (f##_Function*)wglGetProcAddress(#f); \ +(l) = (l) && win32_wgl_good((Void_Func*)(f));) + + b32 load_success = true; + LoadWGL(wglCreateContextAttribsARB, load_success); + LoadWGL(wglChoosePixelFormatARB, load_success); + LoadWGL(wglGetExtensionsStringEXT, load_success); + + if (!load_success){ + register_success = false; + goto fail_register; + } + + log_os(" checking wgl extensions...\n"); + char *extensions_c = wglGetExtensionsStringEXT(); + String_Const_u8 extensions = SCu8((u8*)extensions_c); + + { + String_Const_u8 s = string_skip_whitespace(extensions); + for (;s.size > 0;){ + u64 end = string_find_first_whitespace(s); + String_Const_u8 m = string_prefix(s, end); + if (string_match(m, string_u8_litexpr("WGL_EXT_framebuffer_sRGB")) || + string_match(m, string_u8_litexpr("WGL_ARB_framebuffer_sRGB"))){ + srgb_support = true; + } + else if (string_match(m, string_u8_litexpr("WGL_EXT_swap_interval"))){ + b32 wgl_swap_interval_ext = true; + LoadWGL(wglSwapIntervalEXT, wgl_swap_interval_ext); + if (!wgl_swap_interval_ext){ + wglSwapIntervalEXT = 0; + } + } + s = string_skip_whitespace(string_skip(s, end)); + } + } + + // NOTE(allen): Load gl functions + log_os(" loading core GL functions...\n"); + +#define GL_FUNC(f,R,P) LoadWGL(f,load_success); +#include "opengl/4ed_opengl_funcs.h" + + if (!load_success){ + register_success = false; + goto fail_register; + } + + // NOTE(allen): Cleanup the GL bootstrap resources + log_os(" cleaning up boostrap resources...\n"); + + ReleaseDC(wglwindow, wgldc); + DestroyWindow(wglwindow); + wglDeleteContext(wglcontext); + + // NOTE(allen): Register the graphics window class + log_os(" registering graphics class...\n"); + + WNDCLASSW wndclass = {}; + wndclass.style = CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS; + wndclass.lpfnWndProc = win32_proc; + wndclass.hIcon = LoadIconW(GetModuleHandle(0), L"main"); + wndclass.hInstance = this_instance; + wndclass.lpszClassName = L"GRAPHICS-WINDOW-NAME"; + if (RegisterClassW(&wndclass) == 0){ + register_success = false; + goto fail_register; + } + } + fail_register:; + + b32 result = false; + if (register_success){ + // NOTE(allen): Create the graphics window + log_os(" creating graphics window...\n"); + + HWND wnd = CreateWindowExW(0, L"GRAPHICS-WINDOW-NAME", L"GRAPHICS", style, + CW_USEDEFAULT, CW_USEDEFAULT, rect.right - rect.left, rect.bottom - rect.top, + 0, 0, this_instance, 0); + + *wnd_out = 0; + *context_out = 0; + if (wnd != 0){ + log_os(" setting graphics pixel format...\n"); + + HDC dc = GetDC(wnd); + + PIXELFORMATDESCRIPTOR format = {}; + + i32 pixel_attrib_list[] = { + /* 0*/WGL_DRAW_TO_WINDOW_ARB, TRUE, + /* 2*/WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB, + /* 4*/WGL_SUPPORT_OPENGL_ARB, TRUE, + /* 6*/WGL_DOUBLE_BUFFER_ARB, false, + /* 8*/WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB, + /*10*/WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB, GL_TRUE, + /*12*/0, + }; + if (!srgb_support){ + pixel_attrib_list[10] = 0; + } + + i32 suggested_format_index = 0; + u32 ignore = 0; + if (!wglChoosePixelFormatARB(dc, pixel_attrib_list, 0, 1, &suggested_format_index, &ignore)){ + goto fail_window_init; + } + + DescribePixelFormat(dc, suggested_format_index, sizeof(format), &format); + if (!SetPixelFormat(dc, suggested_format_index, &format)){ + goto fail_window_init; + } + + log_os(" setting graphics attributes...\n"); + + i32 context_attrib_list[] = { + /*0*/WGL_CONTEXT_MAJOR_VERSION_ARB, 3, + /*2*/WGL_CONTEXT_MINOR_VERSION_ARB, 2, + /*4*/WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB +#if GL_DEBUG_MODE + |WGL_CONTEXT_DEBUG_BIT_ARB +#endif + , + /*6*/WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB, + /*8*/0 + }; + + log_os(" creating graphics GL context...\n"); + HGLRC context = wglCreateContextAttribsARB(dc, 0, context_attrib_list); + if (context == 0){ + goto fail_window_init; + } + + log_os(" making graphics GL context current...\n"); + wglMakeCurrent(dc, context); + + + if (wglSwapIntervalEXT != 0){ + log_os(" setting swap interval...\n"); + wglSwapIntervalEXT(1); + } + *wnd_out = wnd; + *context_out = context; + result = true; + + if (false){ + fail_window_init:; + DWORD error = GetLastError(); + ReleaseDC(wnd, dc); + DestroyWindow(wnd); + SetLastError(error); + } + } + } + + return(result); +} \ No newline at end of file