#ifdef RAPI_D3D12 #if defined(_WIN32) || defined(_WIN64) #include #include #include #include #include #include #include #include // These are needed when compiling with MinGW #include #include #define __in_ecount_opt(size) #define __in #define __out #define __REQUIRED_RPCNDR_H_VERSION__ 475 #include #include #include #include #include #include #include "dxsdk/d3dx12.h" #ifndef _LANGUAGE_C #define _LANGUAGE_C #endif #include extern "C" { #include "../cliopts.h" #include "../configfile.h" #include "../platform.h" #include "../pc_main.h" } #include "gfx_cc.h" #include "gfx_window_manager_api.h" #include "gfx_rendering_api.h" #include "gfx_direct3d_common.h" #include "gfx_screen_config.h" #define WINCLASS_NAME L"SUPERMARIO64" #define GFX_API_NAME "Direct3D 12" #define DEBUG_D3D 0 #ifdef VERSION_EU #define FRAME_INTERVAL_US_NUMERATOR 40000 #define FRAME_INTERVAL_US_DENOMINATOR 1 #else #define FRAME_INTERVAL_US_NUMERATOR 100000 #define FRAME_INTERVAL_US_DENOMINATOR 3 #endif using namespace Microsoft::WRL; // For ComPtr struct ShaderProgram { uint32_t shader_id; uint8_t num_inputs; bool used_textures[2]; uint8_t num_floats; uint8_t num_attribs; ComPtr vertex_shader; ComPtr pixel_shader; ComPtr root_signature; }; struct PipelineDesc { uint32_t shader_id; bool depth_test; bool depth_mask; bool zmode_decal; bool _padding; bool operator==(const PipelineDesc& o) const { return memcmp(this, &o, sizeof(*this)) == 0; } bool operator<(const PipelineDesc& o) const { return memcmp(this, &o, sizeof(*this)) < 0; } }; struct TextureHeap { ComPtr heap; std::vector free_list; }; struct TextureData { ComPtr resource; struct TextureHeap *heap; uint8_t heap_offset; uint64_t last_frame_counter; uint32_t descriptor_index; int sampler_parameters; }; static struct { struct ShaderProgram shader_program_pool[64]; uint8_t shader_program_pool_size; uint32_t current_width, current_height; ComPtr factory; ComPtr device; ComPtr command_queue; ComPtr copy_command_queue; ComPtr swap_chain; ComPtr rtv_heap; UINT rtv_descriptor_size; ComPtr render_targets[2]; ComPtr command_allocator; ComPtr copy_command_allocator; ComPtr command_list; ComPtr copy_command_list; ComPtr dsv_heap; ComPtr depth_stencil_buffer; ComPtr srv_heap; UINT srv_descriptor_size; ComPtr sampler_heap; UINT sampler_descriptor_size; std::map, std::list> texture_heaps; std::map>> upload_heaps; std::vector>> upload_heaps_in_flight; ComPtr copy_fence; uint64_t copy_fence_value; std::vector textures; int current_tile; uint32_t current_texture_ids[2]; uint32_t srv_pos; int frame_index; ComPtr fence; HANDLE fence_event; HANDLE waitable_object; uint64_t qpc_init, qpc_freq; uint64_t frame_timestamp; // in units of 1/FRAME_INTERVAL_US_DENOMINATOR microseconds std::map frame_stats; std::set> pending_frame_stats; bool dropped_frame; bool sync_interval_means_frames_to_wait; UINT length_in_vsync_frames; uint64_t frame_counter; ComPtr vertex_buffer; void *mapped_vbuf_address; int vbuf_pos; std::vector> resources_to_clean_at_end_of_frame; std::vector> texture_heap_allocations_to_reclaim_at_end_of_frame; std::map> pipeline_states; bool must_reload_pipeline; // Current state: ID3D12PipelineState *pipeline_state; struct ShaderProgram *shader_program; bool depth_test; bool depth_mask; bool zmode_decal; CD3DX12_VIEWPORT viewport; CD3DX12_RECT scissor; void (*run_one_game_iter)(void); bool (*on_key_down)(int scancode); bool (*on_key_up)(int scancode); void (*on_all_keys_up)(void); } d3d; static int texture_uploads = 0; static int max_texture_uploads; static D3D12_CPU_DESCRIPTOR_HANDLE get_cpu_descriptor_handle(ComPtr& heap) { #if __MINGW32__ // We would like to do this: // D3D12_CPU_DESCRIPTOR_HANDLE handle = heap->GetCPUDescriptorHandleForHeapStart(); // but MinGW64 doesn't follow the calling conventions of VC++ for some reason. // Per MS documentation "User-defined types can be returned by value from global functions and static member functions"... // "Otherwise, the caller assumes the responsibility of allocating memory and passing a pointer for the return value as the first argument". // The method here is a non-static member function, and hence we need to pass the address to the return value as a parameter. // MinGW32 has the same issue. auto fn = heap->GetCPUDescriptorHandleForHeapStart; void (STDMETHODCALLTYPE ID3D12DescriptorHeap::*fun)(D3D12_CPU_DESCRIPTOR_HANDLE *out) = (void (STDMETHODCALLTYPE ID3D12DescriptorHeap::*)(D3D12_CPU_DESCRIPTOR_HANDLE *out))fn; D3D12_CPU_DESCRIPTOR_HANDLE handle; (heap.Get()->*fun)(&handle); return handle; #else return heap->GetCPUDescriptorHandleForHeapStart(); #endif } static D3D12_GPU_DESCRIPTOR_HANDLE get_gpu_descriptor_handle(ComPtr& heap) { #ifdef __MINGW32__ // See get_cpu_descriptor_handle auto fn = heap->GetGPUDescriptorHandleForHeapStart; void (STDMETHODCALLTYPE ID3D12DescriptorHeap::*fun)(D3D12_GPU_DESCRIPTOR_HANDLE *out) = (void (STDMETHODCALLTYPE ID3D12DescriptorHeap::*)(D3D12_GPU_DESCRIPTOR_HANDLE *out))fn; D3D12_GPU_DESCRIPTOR_HANDLE handle; (heap.Get()->*fun)(&handle); return handle; #else return heap->GetGPUDescriptorHandleForHeapStart(); #endif } static D3D12_RESOURCE_ALLOCATION_INFO get_resource_allocation_info(const D3D12_RESOURCE_DESC *resource_desc) { #ifdef __MINGW32__ // See get_cpu_descriptor_handle auto fn = d3d.device->GetResourceAllocationInfo; void (STDMETHODCALLTYPE ID3D12Device::*fun)(D3D12_RESOURCE_ALLOCATION_INFO *out, UINT visibleMask, UINT numResourceDescs, const D3D12_RESOURCE_DESC *pResourceDescs) = (void (STDMETHODCALLTYPE ID3D12Device::*)(D3D12_RESOURCE_ALLOCATION_INFO *out, UINT visibleMask, UINT numResourceDescs, const D3D12_RESOURCE_DESC *pResourceDescs))fn; D3D12_RESOURCE_ALLOCATION_INFO out; (d3d.device.Get()->*fun)(&out, 0, 1, resource_desc); return out; #else return d3d.device->GetResourceAllocationInfo(0, 1, resource_desc); #endif } static bool gfx_d3d12_z_is_from_0_to_1(void) { return true; } static void gfx_d3d12_unload_shader(struct ShaderProgram *old_prg) { } static void gfx_d3d12_load_shader(struct ShaderProgram *new_prg) { d3d.shader_program = new_prg; d3d.must_reload_pipeline = true; } static struct ShaderProgram *gfx_d3d12_create_and_load_new_shader(uint32_t shader_id) { /*static FILE *fp; if (!fp) { fp = fopen("shaders.txt", "w"); } fprintf(fp, "0x%08x\n", shader_id); fflush(fp);*/ struct ShaderProgram *prg = &d3d.shader_program_pool[d3d.shader_program_pool_size++]; CCFeatures cc_features; get_cc_features(shader_id, &cc_features); char buf[2048]; size_t len = 0; size_t num_floats = 4; append_str(buf, &len, "#define RS \"RootFlags(ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT | DENY_VERTEX_SHADER_ROOT_ACCESS)"); if (cc_features.used_textures[0]) { append_str(buf, &len, ",DescriptorTable(SRV(t0), visibility = SHADER_VISIBILITY_PIXEL)"); append_str(buf, &len, ",DescriptorTable(Sampler(s0), visibility = SHADER_VISIBILITY_PIXEL)"); } if (cc_features.used_textures[1]) { append_str(buf, &len, ",DescriptorTable(SRV(t1), visibility = SHADER_VISIBILITY_PIXEL)"); append_str(buf, &len, ",DescriptorTable(Sampler(s1), visibility = SHADER_VISIBILITY_PIXEL)"); } append_line(buf, &len, "\""); append_line(buf, &len, "struct PSInput {"); append_line(buf, &len, "float4 position : SV_POSITION;"); if (cc_features.used_textures[0] || cc_features.used_textures[1]) { append_line(buf, &len, "float2 uv : TEXCOORD;"); num_floats += 2; } if (cc_features.opt_fog) { append_line(buf, &len, "float4 fog : FOG;"); num_floats += 4; } for (int i = 0; i < cc_features.num_inputs; i++) { len += sprintf(buf + len, "float%d input%d : INPUT%d;\r\n", cc_features.opt_alpha ? 4 : 3, i + 1, i); num_floats += cc_features.opt_alpha ? 4 : 3; } append_line(buf, &len, "};"); if (cc_features.used_textures[0]) { append_line(buf, &len, "Texture2D g_texture0 : register(t0);"); append_line(buf, &len, "SamplerState g_sampler0 : register(s0);"); } if (cc_features.used_textures[1]) { append_line(buf, &len, "Texture2D g_texture1 : register(t1);"); append_line(buf, &len, "SamplerState g_sampler1 : register(s1);"); } // Vertex shader append_str(buf, &len, "PSInput VSMain(float4 position : POSITION"); if (cc_features.used_textures[0] || cc_features.used_textures[1]) { append_str(buf, &len, ", float2 uv : TEXCOORD"); } if (cc_features.opt_fog) { append_str(buf, &len, ", float4 fog : FOG"); } for (int i = 0; i < cc_features.num_inputs; i++) { len += sprintf(buf + len, ", float%d input%d : INPUT%d", cc_features.opt_alpha ? 4 : 3, i + 1, i); } append_line(buf, &len, ") {"); append_line(buf, &len, "PSInput result;"); append_line(buf, &len, "result.position = position;"); if (cc_features.used_textures[0] || cc_features.used_textures[1]) { append_line(buf, &len, "result.uv = uv;"); } if (cc_features.opt_fog) { append_line(buf, &len, "result.fog = fog;"); } for (int i = 0; i < cc_features.num_inputs; i++) { len += sprintf(buf + len, "result.input%d = input%d;\r\n", i + 1, i + 1); } append_line(buf, &len, "return result;"); append_line(buf, &len, "}"); // Pixel shader append_line(buf, &len, "[RootSignature(RS)]"); append_line(buf, &len, "float4 PSMain(PSInput input) : SV_TARGET {"); if (cc_features.used_textures[0]) { append_line(buf, &len, "float4 texVal0 = g_texture0.Sample(g_sampler0, input.uv);"); } if (cc_features.used_textures[1]) { append_line(buf, &len, "float4 texVal1 = g_texture1.Sample(g_sampler1, input.uv);"); } append_str(buf, &len, cc_features.opt_alpha ? "float4 texel = " : "float3 texel = "); if (!cc_features.color_alpha_same && cc_features.opt_alpha) { append_str(buf, &len, "float4("); append_formula(buf, &len, cc_features.c, cc_features.do_single[0], cc_features.do_multiply[0], cc_features.do_mix[0], false, false, true); append_str(buf, &len, ", "); append_formula(buf, &len, cc_features.c, cc_features.do_single[1], cc_features.do_multiply[1], cc_features.do_mix[1], true, true, true); append_str(buf, &len, ")"); } else { append_formula(buf, &len, cc_features.c, cc_features.do_single[0], cc_features.do_multiply[0], cc_features.do_mix[0], cc_features.opt_alpha, false, cc_features.opt_alpha); } append_line(buf, &len, ";"); if (cc_features.opt_texture_edge && cc_features.opt_alpha) { append_line(buf, &len, "if (texel.a > 0.3) texel.a = 1.0; else discard;"); } // TODO discard if alpha is 0? if (cc_features.opt_fog) { if (cc_features.opt_alpha) { append_line(buf, &len, "texel = float4(lerp(texel.rgb, input.fog.rgb, input.fog.a), texel.a);"); } else { append_line(buf, &len, "texel = lerp(texel, input.fog.rgb, input.fog.a);"); } } if (cc_features.opt_alpha) { append_line(buf, &len, "return texel;"); } else { append_line(buf, &len, "return float4(texel, 1.0);"); } append_line(buf, &len, "}"); //fwrite(buf, 1, len, stdout); ThrowIfFailed(D3DCompile(buf, len, nullptr, nullptr, nullptr, "VSMain", "vs_5_1", /*D3DCOMPILE_OPTIMIZATION_LEVEL3*/0, 0, &prg->vertex_shader, nullptr)); ThrowIfFailed(D3DCompile(buf, len, nullptr, nullptr, nullptr, "PSMain", "ps_5_1", /*D3DCOMPILE_OPTIMIZATION_LEVEL3*/0, 0, &prg->pixel_shader, nullptr)); ThrowIfFailed(d3d.device->CreateRootSignature(0, prg->pixel_shader->GetBufferPointer(), prg->pixel_shader->GetBufferSize(), IID_ID3D12RootSignature, IID_PPV_ARGS_Helper(&prg->root_signature))); prg->shader_id = shader_id; prg->num_inputs = cc_features.num_inputs; prg->used_textures[0] = cc_features.used_textures[0]; prg->used_textures[1] = cc_features.used_textures[1]; prg->num_floats = num_floats; //prg->num_attribs = cnt; d3d.must_reload_pipeline = true; return d3d.shader_program = prg; } static struct ShaderProgram *gfx_d3d12_lookup_shader(uint32_t shader_id) { for (size_t i = 0; i < d3d.shader_program_pool_size; i++) { if (d3d.shader_program_pool[i].shader_id == shader_id) { return &d3d.shader_program_pool[i]; } } return nullptr; } static void gfx_d3d12_shader_get_info(struct ShaderProgram *prg, uint8_t *num_inputs, bool used_textures[2]) { *num_inputs = prg->num_inputs; used_textures[0] = prg->used_textures[0]; used_textures[1] = prg->used_textures[1]; } static uint32_t gfx_d3d12_new_texture(void) { d3d.textures.resize(d3d.textures.size() + 1); return (uint32_t)(d3d.textures.size() - 1); } static void gfx_d3d12_select_texture(int tile, uint32_t texture_id) { d3d.current_tile = tile; d3d.current_texture_ids[tile] = texture_id; } static void gfx_d3d12_upload_texture(uint8_t *rgba32_buf, int width, int height) { texture_uploads++; ComPtr texture_resource; // Describe and create a Texture2D. D3D12_RESOURCE_DESC texture_desc = {}; texture_desc.MipLevels = 1; texture_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; texture_desc.Width = width; texture_desc.Height = height; texture_desc.Flags = D3D12_RESOURCE_FLAG_NONE; texture_desc.DepthOrArraySize = 1; texture_desc.SampleDesc.Count = 1; texture_desc.SampleDesc.Quality = 0; texture_desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; texture_desc.Alignment = D3D12_SMALL_RESOURCE_PLACEMENT_ALIGNMENT; D3D12_RESOURCE_ALLOCATION_INFO alloc_info = get_resource_allocation_info(&texture_desc); std::list& heaps = d3d.texture_heaps[std::pair(alloc_info.SizeInBytes, alloc_info.Alignment)]; struct TextureHeap *found_heap = nullptr; for (struct TextureHeap& heap : heaps) { if (!heap.free_list.empty()) { found_heap = &heap; } } if (found_heap == nullptr) { heaps.resize(heaps.size() + 1); found_heap = &heaps.back(); const int textures_per_heap = 64; D3D12_HEAP_DESC heap_desc = {}; heap_desc.SizeInBytes = alloc_info.SizeInBytes * textures_per_heap; if (alloc_info.Alignment == D3D12_SMALL_RESOURCE_PLACEMENT_ALIGNMENT) { heap_desc.Alignment = D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT; } else { heap_desc.Alignment = alloc_info.Alignment; } heap_desc.Properties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; heap_desc.Properties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; heap_desc.Properties.Type = D3D12_HEAP_TYPE_DEFAULT; heap_desc.Flags = D3D12_HEAP_FLAG_ALLOW_ONLY_NON_RT_DS_TEXTURES; ThrowIfFailed(d3d.device->CreateHeap(&heap_desc, IID_ID3D12Heap, IID_PPV_ARGS_Helper(&found_heap->heap))); for (int i = 0; i < textures_per_heap; i++) { found_heap->free_list.push_back(i); } } uint8_t heap_offset = found_heap->free_list.back(); found_heap->free_list.pop_back(); ThrowIfFailed(d3d.device->CreatePlacedResource(found_heap->heap.Get(), heap_offset * alloc_info.SizeInBytes, &texture_desc, D3D12_RESOURCE_STATE_COPY_DEST, nullptr, IID_ID3D12Resource, IID_PPV_ARGS_Helper(&texture_resource))); D3D12_PLACED_SUBRESOURCE_FOOTPRINT layout; UINT num_rows; UINT64 row_size_in_bytes; UINT64 upload_buffer_size; d3d.device->GetCopyableFootprints(&texture_desc, 0, 1, 0, &layout, &num_rows, &row_size_in_bytes, &upload_buffer_size); std::vector>& upload_heaps = d3d.upload_heaps[upload_buffer_size]; ComPtr upload_heap; if (upload_heaps.empty()) { CD3DX12_HEAP_PROPERTIES hp(D3D12_HEAP_TYPE_UPLOAD); CD3DX12_RESOURCE_DESC rdb = CD3DX12_RESOURCE_DESC::Buffer(upload_buffer_size); ThrowIfFailed(d3d.device->CreateCommittedResource( &hp, D3D12_HEAP_FLAG_NONE, &rdb, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_ID3D12Resource, IID_PPV_ARGS_Helper(&upload_heap))); } else { upload_heap = upload_heaps.back(); upload_heaps.pop_back(); } { D3D12_SUBRESOURCE_DATA texture_data = {}; texture_data.pData = rgba32_buf; texture_data.RowPitch = width * 4; // RGBA texture_data.SlicePitch = texture_data.RowPitch * height; void *data; upload_heap->Map(0, nullptr, &data); D3D12_MEMCPY_DEST dest_data = { (uint8_t *)data + layout.Offset, layout.Footprint.RowPitch, SIZE_T(layout.Footprint.RowPitch) * SIZE_T(num_rows) }; MemcpySubresource(&dest_data, &texture_data, static_cast(row_size_in_bytes), num_rows, layout.Footprint.Depth); upload_heap->Unmap(0, nullptr); CD3DX12_TEXTURE_COPY_LOCATION dst(texture_resource.Get(), 0); CD3DX12_TEXTURE_COPY_LOCATION src(upload_heap.Get(), layout); d3d.copy_command_list->CopyTextureRegion(&dst, 0, 0, 0, &src, nullptr); } CD3DX12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition(texture_resource.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); d3d.command_list->ResourceBarrier(1, &barrier); d3d.upload_heaps_in_flight.push_back(std::make_pair((size_t)upload_buffer_size, std::move(upload_heap))); struct TextureData& td = d3d.textures[d3d.current_texture_ids[d3d.current_tile]]; if (td.resource.Get() != nullptr) { d3d.resources_to_clean_at_end_of_frame.push_back(std::move(td.resource)); d3d.texture_heap_allocations_to_reclaim_at_end_of_frame.push_back(std::make_pair(td.heap, td.heap_offset)); td.last_frame_counter = 0; } td.resource = std::move(texture_resource); td.heap = found_heap; td.heap_offset = heap_offset; } static int gfx_cm_to_index(uint32_t val) { if (val & G_TX_CLAMP) { return 2; } return (val & G_TX_MIRROR) ? 1 : 0; } static void gfx_d3d12_set_sampler_parameters(int tile, bool linear_filter, uint32_t cms, uint32_t cmt) { d3d.textures[d3d.current_texture_ids[tile]].sampler_parameters = linear_filter * 9 + gfx_cm_to_index(cms) * 3 + gfx_cm_to_index(cmt); } static void gfx_d3d12_set_depth_test(bool depth_test) { d3d.depth_test = depth_test; d3d.must_reload_pipeline = true; } static void gfx_d3d12_set_depth_mask(bool z_upd) { d3d.depth_mask = z_upd; d3d.must_reload_pipeline = true; } static void gfx_d3d12_set_zmode_decal(bool zmode_decal) { d3d.zmode_decal = zmode_decal; d3d.must_reload_pipeline = true; } static void gfx_d3d12_set_viewport(int x, int y, int width, int height) { d3d.viewport = CD3DX12_VIEWPORT(x, d3d.current_height - y - height, width, height); } static void gfx_d3d12_set_scissor(int x, int y, int width, int height) { d3d.scissor = CD3DX12_RECT(x, d3d.current_height - y - height, x + width, d3d.current_height - y); } static void gfx_d3d12_set_use_alpha(bool use_alpha) { // Already part of the pipeline state from shader info } static void gfx_d3d12_draw_triangles(float buf_vbo[], size_t buf_vbo_len, size_t buf_vbo_num_tris) { struct ShaderProgram *prg = d3d.shader_program; if (d3d.must_reload_pipeline) { ComPtr& pipeline_state = d3d.pipeline_states[PipelineDesc{ prg->shader_id, d3d.depth_test, d3d.depth_mask, d3d.zmode_decal, 0 }]; if (pipeline_state.Get() == nullptr) { D3D12_INPUT_ELEMENT_DESC ied[7] = { {"POSITION", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 } }; uint32_t ied_pos = 1; if (prg->used_textures[0] || prg->used_textures[1]) { ied[ied_pos++] = D3D12_INPUT_ELEMENT_DESC{"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, D3D12_APPEND_ALIGNED_ELEMENT, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}; } if (prg->shader_id & SHADER_OPT_FOG) { ied[ied_pos++] = D3D12_INPUT_ELEMENT_DESC{"FOG", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, D3D12_APPEND_ALIGNED_ELEMENT, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}; } for (int i = 0; i < prg->num_inputs; i++) { DXGI_FORMAT format = (prg->shader_id & SHADER_OPT_ALPHA) ? DXGI_FORMAT_R32G32B32A32_FLOAT : DXGI_FORMAT_R32G32B32_FLOAT; ied[ied_pos++] = D3D12_INPUT_ELEMENT_DESC{"INPUT", (UINT)i, format, 0, D3D12_APPEND_ALIGNED_ELEMENT, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}; } D3D12_GRAPHICS_PIPELINE_STATE_DESC desc = {}; desc.InputLayout = { ied, ied_pos }; desc.pRootSignature = prg->root_signature.Get(); desc.VS = CD3DX12_SHADER_BYTECODE(prg->vertex_shader.Get()); desc.PS = CD3DX12_SHADER_BYTECODE(prg->pixel_shader.Get()); desc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT); if (d3d.zmode_decal) { desc.RasterizerState.SlopeScaledDepthBias = -2.0f; } desc.RasterizerState.CullMode = D3D12_CULL_MODE_NONE; if (prg->shader_id & SHADER_OPT_ALPHA) { D3D12_BLEND_DESC bd = {}; bd.AlphaToCoverageEnable = FALSE; bd.IndependentBlendEnable = FALSE; static const D3D12_RENDER_TARGET_BLEND_DESC default_rtbd = { TRUE, FALSE, D3D12_BLEND_SRC_ALPHA, D3D12_BLEND_INV_SRC_ALPHA, D3D12_BLEND_OP_ADD, D3D12_BLEND_ONE, D3D12_BLEND_INV_SRC_ALPHA, D3D12_BLEND_OP_ADD, D3D12_LOGIC_OP_NOOP, D3D12_COLOR_WRITE_ENABLE_ALL }; for (UINT i = 0; i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; i++) { bd.RenderTarget[i] = default_rtbd; } desc.BlendState = bd; } else { desc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT); } desc.DepthStencilState.DepthEnable = d3d.depth_test; desc.DepthStencilState.DepthWriteMask = d3d.depth_mask ? D3D12_DEPTH_WRITE_MASK_ALL : D3D12_DEPTH_WRITE_MASK_ZERO; desc.DepthStencilState.DepthFunc = D3D12_COMPARISON_FUNC_LESS_EQUAL; desc.DSVFormat = d3d.depth_test ? DXGI_FORMAT_D32_FLOAT : DXGI_FORMAT_UNKNOWN; desc.SampleMask = UINT_MAX; desc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; desc.NumRenderTargets = 1; desc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM; desc.SampleDesc.Count = 1; ThrowIfFailed(d3d.device->CreateGraphicsPipelineState(&desc, IID_ID3D12PipelineState, IID_PPV_ARGS_Helper(&pipeline_state))); } d3d.pipeline_state = pipeline_state.Get(); d3d.must_reload_pipeline = false; } d3d.command_list->SetGraphicsRootSignature(prg->root_signature.Get()); d3d.command_list->SetPipelineState(d3d.pipeline_state); ID3D12DescriptorHeap *heaps[] = { d3d.srv_heap.Get(), d3d.sampler_heap.Get() }; d3d.command_list->SetDescriptorHeaps(2, heaps); int texture_pos = 0; for (int i = 0; i < 2; i++) { if (prg->used_textures[i]) { struct TextureData& td = d3d.textures[d3d.current_texture_ids[i]]; if (td.last_frame_counter != d3d.frame_counter) { td.descriptor_index = d3d.srv_pos; td.last_frame_counter = d3d.frame_counter; D3D12_SHADER_RESOURCE_VIEW_DESC srv_desc = {}; srv_desc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; srv_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; srv_desc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; srv_desc.Texture2D.MipLevels = 1; CD3DX12_CPU_DESCRIPTOR_HANDLE srv_handle(get_cpu_descriptor_handle(d3d.srv_heap), d3d.srv_pos++, d3d.srv_descriptor_size); d3d.device->CreateShaderResourceView(td.resource.Get(), &srv_desc, srv_handle); } CD3DX12_GPU_DESCRIPTOR_HANDLE srv_gpu_handle(get_gpu_descriptor_handle(d3d.srv_heap), td.descriptor_index, d3d.srv_descriptor_size); d3d.command_list->SetGraphicsRootDescriptorTable(2 * texture_pos, srv_gpu_handle); CD3DX12_GPU_DESCRIPTOR_HANDLE sampler_gpu_handle(get_gpu_descriptor_handle(d3d.sampler_heap), td.sampler_parameters, d3d.sampler_descriptor_size); d3d.command_list->SetGraphicsRootDescriptorTable(2 * texture_pos + 1, sampler_gpu_handle); ++texture_pos; } } CD3DX12_CPU_DESCRIPTOR_HANDLE rtv_handle(get_cpu_descriptor_handle(d3d.rtv_heap), d3d.frame_index, d3d.rtv_descriptor_size); D3D12_CPU_DESCRIPTOR_HANDLE dsv_handle = get_cpu_descriptor_handle(d3d.dsv_heap); d3d.command_list->OMSetRenderTargets(1, &rtv_handle, FALSE, &dsv_handle); d3d.command_list->RSSetViewports(1, &d3d.viewport); d3d.command_list->RSSetScissorRects(1, &d3d.scissor); int current_pos = d3d.vbuf_pos; memcpy((uint8_t *)d3d.mapped_vbuf_address + current_pos, buf_vbo, buf_vbo_len * sizeof(float)); d3d.vbuf_pos += buf_vbo_len * sizeof(float); static int maxpos; if (d3d.vbuf_pos > maxpos) { maxpos = d3d.vbuf_pos; //printf("NEW MAXPOS: %d\n", maxpos); } D3D12_VERTEX_BUFFER_VIEW vertex_buffer_view; vertex_buffer_view.BufferLocation = d3d.vertex_buffer->GetGPUVirtualAddress() + current_pos; vertex_buffer_view.StrideInBytes = buf_vbo_len / (3 * buf_vbo_num_tris) * sizeof(float); vertex_buffer_view.SizeInBytes = buf_vbo_len * sizeof(float); d3d.command_list->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); d3d.command_list->IASetVertexBuffers(0, 1, &vertex_buffer_view); d3d.command_list->DrawInstanced(3 * buf_vbo_num_tris, 1, 0, 0); } static void gfx_d3d12_init(void) { } static void gfx_d3d12_shutdown(void) { } static void gfx_d3d12_start_frame(void) { ++d3d.frame_counter; d3d.srv_pos = 0; texture_uploads = 0; ThrowIfFailed(d3d.command_allocator->Reset()); ThrowIfFailed(d3d.command_list->Reset(d3d.command_allocator.Get(), nullptr)); CD3DX12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition( d3d.render_targets[d3d.frame_index].Get(), D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET); d3d.command_list->ResourceBarrier(1, &barrier); CD3DX12_CPU_DESCRIPTOR_HANDLE rtv_handle(get_cpu_descriptor_handle(d3d.rtv_heap), d3d.frame_index, d3d.rtv_descriptor_size); D3D12_CPU_DESCRIPTOR_HANDLE dsv_handle = get_cpu_descriptor_handle(d3d.dsv_heap); d3d.command_list->OMSetRenderTargets(1, &rtv_handle, FALSE, &dsv_handle); static unsigned char c; const float clear_color[] = { 0.0f, 0.0f, 0.0f, 1.0f }; d3d.command_list->ClearRenderTargetView(rtv_handle, clear_color, 0, nullptr); d3d.command_list->ClearDepthStencilView(dsv_handle, D3D12_CLEAR_FLAG_DEPTH, 1.0f, 0, 0, nullptr); d3d.vbuf_pos = 0; } static void create_render_target_views(void) { D3D12_CPU_DESCRIPTOR_HANDLE rtv_handle = get_cpu_descriptor_handle(d3d.rtv_heap); for (UINT i = 0; i < 2; i++) { ThrowIfFailed(d3d.swap_chain->GetBuffer(i, IID_ID3D12Resource, (void **)&d3d.render_targets[i])); d3d.device->CreateRenderTargetView(d3d.render_targets[i].Get(), nullptr, rtv_handle); rtv_handle.ptr += d3d.rtv_descriptor_size; } } static void create_depth_buffer(void) { DXGI_SWAP_CHAIN_DESC1 desc1; ThrowIfFailed(d3d.swap_chain->GetDesc1(&desc1)); UINT width = desc1.Width; UINT height = desc1.Height; d3d.current_width = width; d3d.current_height = height; D3D12_DEPTH_STENCIL_VIEW_DESC dsv_desc = {}; dsv_desc.Format = DXGI_FORMAT_D32_FLOAT; dsv_desc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D; dsv_desc.Flags = D3D12_DSV_FLAG_NONE; D3D12_CLEAR_VALUE depth_optimized_cv = {}; depth_optimized_cv.Format = DXGI_FORMAT_D32_FLOAT; depth_optimized_cv.DepthStencil.Depth = 1.0f; D3D12_HEAP_PROPERTIES hp = {}; hp.Type = D3D12_HEAP_TYPE_DEFAULT; hp.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; hp.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; hp.CreationNodeMask = 1; hp.VisibleNodeMask = 1; D3D12_RESOURCE_DESC rd = {}; rd.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; rd.Alignment = 0; rd.Width = width; rd.Height = height; rd.DepthOrArraySize = 1; rd.MipLevels = 0; rd.Format = DXGI_FORMAT_D32_FLOAT; rd.SampleDesc.Count = 1; rd.SampleDesc.Quality = 0; rd.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL; rd.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; ThrowIfFailed(d3d.device->CreateCommittedResource(&hp, D3D12_HEAP_FLAG_NONE, &rd, D3D12_RESOURCE_STATE_DEPTH_WRITE, &depth_optimized_cv, IID_ID3D12Resource, IID_PPV_ARGS_Helper(&d3d.depth_stencil_buffer))); d3d.device->CreateDepthStencilView(d3d.depth_stencil_buffer.Get(), &dsv_desc, get_cpu_descriptor_handle(d3d.dsv_heap)); } static void gfx_d3d12_dxgi_on_resize(void) { if (d3d.render_targets[0].Get() != nullptr) { d3d.render_targets[0].Reset(); d3d.render_targets[1].Reset(); ThrowIfFailed(d3d.swap_chain->ResizeBuffers(0, 0, 0, DXGI_FORMAT_UNKNOWN, DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT)); d3d.frame_index = d3d.swap_chain->GetCurrentBackBufferIndex(); create_render_target_views(); create_depth_buffer(); } } static void onkeydown(WPARAM w_param, LPARAM l_param) { int key = ((l_param >> 16) & 0x1ff); if (d3d.on_key_down != nullptr) { d3d.on_key_down(key); } } static void onkeyup(WPARAM w_param, LPARAM l_param) { int key = ((l_param >> 16) & 0x1ff); if (d3d.on_key_up != nullptr) { d3d.on_key_up(key); } } LRESULT CALLBACK gfx_d3d12_dxgi_wnd_proc(HWND h_wnd, UINT message, WPARAM w_param, LPARAM l_param) { switch (message) { case WM_MOVE: { const int x = (short)LOWORD(l_param); const int y = (short)HIWORD(l_param); configWindow.x = (x < 0) ? 0 : x; configWindow.y = (y < 0) ? 0 : y; break; } case WM_SIZE: gfx_d3d12_dxgi_on_resize(); break; case WM_DESTROY: game_exit(); break; case WM_PAINT: if (d3d.run_one_game_iter != nullptr) d3d.run_one_game_iter(); break; case WM_ACTIVATEAPP: if (d3d.on_all_keys_up != nullptr) d3d.on_all_keys_up(); break; case WM_KEYDOWN: onkeydown(w_param, l_param); break; case WM_KEYUP: onkeyup(w_param, l_param); break; default: return DefWindowProcW(h_wnd, message, w_param, l_param); } return 0; } static void gfx_d3d12_dxgi_init(const char *window_title) { LARGE_INTEGER qpc_init, qpc_freq; QueryPerformanceCounter(&qpc_init); QueryPerformanceFrequency(&qpc_freq); d3d.qpc_init = qpc_init.QuadPart; d3d.qpc_freq = qpc_freq.QuadPart; // Prepare window title wchar_t w_title[512]; mbstowcs(w_title, window_title, strlen(window_title) + 1); // Create window WNDCLASSEXW wcex; wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = gfx_d3d12_dxgi_wnd_proc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = nullptr; wcex.hIcon = nullptr; wcex.hCursor = LoadCursor(nullptr, IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wcex.lpszMenuName = nullptr; wcex.lpszClassName = WINCLASS_NAME; wcex.hIconSm = nullptr; ATOM winclass = RegisterClassExW(&wcex); RECT wr = {0, 0, DESIRED_SCREEN_WIDTH, DESIRED_SCREEN_HEIGHT}; AdjustWindowRect(&wr, WS_OVERLAPPEDWINDOW, FALSE); HWND h_wnd = CreateWindowW(WINCLASS_NAME, w_title, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, wr.right - wr.left, wr.bottom - wr.top, nullptr, nullptr, nullptr, nullptr); // Create device { UINT debug_flags = 0; #if DEBUG_D3D ComPtr debug_controller; if (SUCCEEDED(D3D12GetDebugInterface(IID_ID3D12Debug, IID_PPV_ARGS_Helper(&debug_controller)))) { debug_controller->EnableDebugLayer(); debug_flags |= DXGI_CREATE_FACTORY_DEBUG; } #endif ThrowIfFailed(CreateDXGIFactory2(debug_flags, IID_IDXGIFactory4, &d3d.factory)); ComPtr hw_adapter; for (UINT i = 0; d3d.factory->EnumAdapters1(i, &hw_adapter) != DXGI_ERROR_NOT_FOUND; i++) { DXGI_ADAPTER_DESC1 desc; hw_adapter->GetDesc1(&desc); if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) { continue; } if (SUCCEEDED(D3D12CreateDevice(hw_adapter.Get(), D3D_FEATURE_LEVEL_11_0, IID_ID3D12Device, nullptr))) { break; } } ThrowIfFailed(D3D12CreateDevice(hw_adapter.Get(), D3D_FEATURE_LEVEL_11_0, IID_ID3D12Device, IID_PPV_ARGS_Helper(&d3d.device))); } // Create command queues { D3D12_COMMAND_QUEUE_DESC queue_desc = {}; queue_desc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; queue_desc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; ThrowIfFailed(d3d.device->CreateCommandQueue(&queue_desc, IID_ID3D12CommandQueue, IID_PPV_ARGS_Helper(&d3d.command_queue))); } { D3D12_COMMAND_QUEUE_DESC queue_desc = {}; queue_desc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; queue_desc.Type = D3D12_COMMAND_LIST_TYPE_COPY; ThrowIfFailed(d3d.device->CreateCommandQueue(&queue_desc, IID_ID3D12CommandQueue, IID_PPV_ARGS_Helper(&d3d.copy_command_queue))); } // Create swap chain { DXGI_SWAP_CHAIN_DESC1 swap_chain_desc = {}; swap_chain_desc.BufferCount = 2; swap_chain_desc.Width = DESIRED_SCREEN_WIDTH; swap_chain_desc.Height = DESIRED_SCREEN_HEIGHT; swap_chain_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; swap_chain_desc.Scaling = DXGI_SCALING_NONE; swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; swap_chain_desc.Flags = DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT; swap_chain_desc.SampleDesc.Count = 1; ComPtr swap_chain1; ThrowIfFailed(d3d.factory->CreateSwapChainForHwnd(d3d.command_queue.Get(), h_wnd, &swap_chain_desc, nullptr, nullptr, &swap_chain1)); //ThrowIfFailed(factory->MakeWindowAssociation(h_wnd, DXGI_MWA_NO_ALT_ENTER)); ThrowIfFailed(swap_chain1->QueryInterface(IID_IDXGISwapChain3, &d3d.swap_chain)); d3d.frame_index = d3d.swap_chain->GetCurrentBackBufferIndex(); ThrowIfFailed(d3d.swap_chain->SetMaximumFrameLatency(1)); d3d.waitable_object = d3d.swap_chain->GetFrameLatencyWaitableObject(); WaitForSingleObject(d3d.waitable_object, INFINITE); } // Create render target views { D3D12_DESCRIPTOR_HEAP_DESC rtv_heap_desc = {}; rtv_heap_desc.NumDescriptors = 2; rtv_heap_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV; rtv_heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; ThrowIfFailed(d3d.device->CreateDescriptorHeap(&rtv_heap_desc, IID_ID3D12DescriptorHeap, IID_PPV_ARGS_Helper(&d3d.rtv_heap))); d3d.rtv_descriptor_size = d3d.device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV); create_render_target_views(); } // Create Z-buffer { D3D12_DESCRIPTOR_HEAP_DESC dsv_heap_desc = {}; dsv_heap_desc.NumDescriptors = 1; dsv_heap_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV; dsv_heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; ThrowIfFailed(d3d.device->CreateDescriptorHeap(&dsv_heap_desc, IID_ID3D12DescriptorHeap, IID_PPV_ARGS_Helper(&d3d.dsv_heap))); create_depth_buffer(); } // Create SRV heap for texture descriptors { D3D12_DESCRIPTOR_HEAP_DESC srv_heap_desc = {}; srv_heap_desc.NumDescriptors = 1024; // Max unique textures per frame srv_heap_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV; srv_heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; ThrowIfFailed(d3d.device->CreateDescriptorHeap(&srv_heap_desc, IID_ID3D12DescriptorHeap, IID_PPV_ARGS_Helper(&d3d.srv_heap))); d3d.srv_descriptor_size = d3d.device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); } // Create sampler heap and descriptors { D3D12_DESCRIPTOR_HEAP_DESC sampler_heap_desc = {}; sampler_heap_desc.NumDescriptors = 18; sampler_heap_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER; sampler_heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; ThrowIfFailed(d3d.device->CreateDescriptorHeap(&sampler_heap_desc, IID_ID3D12DescriptorHeap, IID_PPV_ARGS_Helper(&d3d.sampler_heap))); d3d.sampler_descriptor_size = d3d.device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER); static const D3D12_TEXTURE_ADDRESS_MODE address_modes[] = { D3D12_TEXTURE_ADDRESS_MODE_WRAP, D3D12_TEXTURE_ADDRESS_MODE_MIRROR, D3D12_TEXTURE_ADDRESS_MODE_CLAMP }; D3D12_CPU_DESCRIPTOR_HANDLE sampler_handle = get_cpu_descriptor_handle(d3d.sampler_heap); int pos = 0; for (int linear_filter = 0; linear_filter < 2; linear_filter++) { for (int cms = 0; cms < 3; cms++) { for (int cmt = 0; cmt < 3; cmt++) { D3D12_SAMPLER_DESC sampler_desc = {}; sampler_desc.Filter = linear_filter ? D3D12_FILTER_MIN_MAG_MIP_LINEAR : D3D12_FILTER_MIN_MAG_MIP_POINT; sampler_desc.AddressU = address_modes[cms]; sampler_desc.AddressV = address_modes[cmt]; sampler_desc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_WRAP; sampler_desc.MinLOD = 0; sampler_desc.MaxLOD = D3D12_FLOAT32_MAX; sampler_desc.MipLODBias = 0.0f; sampler_desc.MaxAnisotropy = 1; sampler_desc.ComparisonFunc = D3D12_COMPARISON_FUNC_NEVER; d3d.device->CreateSampler(&sampler_desc, CD3DX12_CPU_DESCRIPTOR_HANDLE(sampler_handle, pos++, d3d.sampler_descriptor_size)); } } } } ThrowIfFailed(d3d.device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_ID3D12CommandAllocator, IID_PPV_ARGS_Helper(&d3d.command_allocator))); ThrowIfFailed(d3d.device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_COPY, IID_ID3D12CommandAllocator, IID_PPV_ARGS_Helper(&d3d.copy_command_allocator))); ThrowIfFailed(d3d.device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, d3d.command_allocator.Get(), nullptr, IID_ID3D12GraphicsCommandList, IID_PPV_ARGS_Helper(&d3d.command_list))); ThrowIfFailed(d3d.device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_COPY, d3d.copy_command_allocator.Get(), nullptr, IID_ID3D12GraphicsCommandList, IID_PPV_ARGS_Helper(&d3d.copy_command_list))); ThrowIfFailed(d3d.command_list->Close()); ThrowIfFailed(d3d.device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_ID3D12Fence, IID_PPV_ARGS_Helper(&d3d.fence))); d3d.fence_event = CreateEvent(nullptr, FALSE, FALSE, nullptr); if (d3d.fence_event == nullptr) { ThrowIfFailed(HRESULT_FROM_WIN32(GetLastError())); } ThrowIfFailed(d3d.device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_ID3D12Fence, IID_PPV_ARGS_Helper(&d3d.copy_fence))); { // Create a buffer of 1 MB in size. With a 120 star speed run 192 kB seems to be max usage. CD3DX12_HEAP_PROPERTIES hp(D3D12_HEAP_TYPE_UPLOAD); CD3DX12_RESOURCE_DESC rdb = CD3DX12_RESOURCE_DESC::Buffer(256 * 1024 * sizeof(float)); ThrowIfFailed(d3d.device->CreateCommittedResource( &hp, D3D12_HEAP_FLAG_NONE, &rdb, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_ID3D12Resource, IID_PPV_ARGS_Helper(&d3d.vertex_buffer))); CD3DX12_RANGE read_range(0, 0); // Read not possible from CPU ThrowIfFailed(d3d.vertex_buffer->Map(0, &read_range, &d3d.mapped_vbuf_address)); } ShowWindow(h_wnd, SW_SHOW); UpdateWindow(h_wnd); } static void gfx_d3d12_dxgi_shutdown(void) { if (d3d.render_targets[0].Get() != nullptr) { d3d.render_targets[0].Reset(); d3d.render_targets[1].Reset(); } // uhh } static void gfx_d3d12_dxgi_set_keyboard_callbacks(bool (*on_key_down)(int scancode), bool (*on_key_up)(int scancode), void (*on_all_keys_up)(void)) { d3d.on_key_down = on_key_down; d3d.on_key_up = on_key_up; d3d.on_all_keys_up = on_all_keys_up; } static void gfx_d3d12_dxgi_main_loop(void (*run_one_game_iter)(void)) { d3d.run_one_game_iter = run_one_game_iter; MSG msg; while (GetMessage(&msg, nullptr, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } } static void gfx_d3d12_dxgi_get_dimensions(uint32_t *width, uint32_t *height) { *width = d3d.current_width; *height = d3d.current_height; } static void gfx_d3d12_dxgi_handle_events(void) { /*MSG msg; while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); }*/ } static uint64_t qpc_to_us(uint64_t qpc) { return qpc / d3d.qpc_freq * 1000000 + qpc % d3d.qpc_freq * 1000000 / d3d.qpc_freq; } static bool gfx_d3d12_dxgi_start_frame(void) { DXGI_FRAME_STATISTICS stats; if (d3d.swap_chain->GetFrameStatistics(&stats) == S_OK && (stats.SyncRefreshCount != 0 || stats.SyncQPCTime.QuadPart != 0ULL)) { { LARGE_INTEGER t0; QueryPerformanceCounter(&t0); //printf("Get frame stats: %llu\n", (unsigned long long)(t0.QuadPart - d3d.qpc_init)); } //printf("stats: %u %u %u %u %u %.6f\n", d3d.pending_frame_stats.rbegin()->first, d3d.pending_frame_stats.rbegin()->second, stats.PresentCount, stats.PresentRefreshCount, stats.SyncRefreshCount, (double)(stats.SyncQPCTime.QuadPart - d3d.qpc_init) / d3d.qpc_freq); if (d3d.frame_stats.empty() || d3d.frame_stats.rbegin()->second.PresentCount != stats.PresentCount) { d3d.frame_stats.insert(std::make_pair(stats.PresentCount, stats)); } if (d3d.frame_stats.size() > 3) { d3d.frame_stats.erase(d3d.frame_stats.begin()); } } if (!d3d.frame_stats.empty()) { while (!d3d.pending_frame_stats.empty() && d3d.pending_frame_stats.begin()->first < d3d.frame_stats.rbegin()->first) { d3d.pending_frame_stats.erase(d3d.pending_frame_stats.begin()); } } while (d3d.pending_frame_stats.size() > 15) { // Just make sure the list doesn't grow too large if GetFrameStatistics fails. d3d.pending_frame_stats.erase(d3d.pending_frame_stats.begin()); } d3d.frame_timestamp += FRAME_INTERVAL_US_NUMERATOR; if (d3d.frame_stats.size() >= 2) { DXGI_FRAME_STATISTICS *first = &d3d.frame_stats.begin()->second; DXGI_FRAME_STATISTICS *last = &d3d.frame_stats.rbegin()->second; uint64_t sync_qpc_diff = last->SyncQPCTime.QuadPart - first->SyncQPCTime.QuadPart; UINT sync_vsync_diff = last->SyncRefreshCount - first->SyncRefreshCount; UINT present_vsync_diff = last->PresentRefreshCount - first->PresentRefreshCount; UINT present_diff = last->PresentCount - first->PresentCount; if (sync_vsync_diff == 0) { sync_vsync_diff = 1; } double estimated_vsync_interval = (double)sync_qpc_diff / (double)sync_vsync_diff; //printf("Estimated vsync_interval: %f\n", estimated_vsync_interval); uint64_t estimated_vsync_interval_us = qpc_to_us(estimated_vsync_interval); if (estimated_vsync_interval_us < 2 || estimated_vsync_interval_us > 1000000) { // Unreasonable, maybe a monitor change estimated_vsync_interval_us = 16666; estimated_vsync_interval = estimated_vsync_interval_us * d3d.qpc_freq / 1000000; } UINT queued_vsyncs = 0; bool is_first = true; for (const std::pair& p : d3d.pending_frame_stats) { if (is_first && d3d.sync_interval_means_frames_to_wait) { is_first = false; continue; } queued_vsyncs += p.second; } uint64_t last_frame_present_end_qpc = (last->SyncQPCTime.QuadPart - d3d.qpc_init) + estimated_vsync_interval * queued_vsyncs; uint64_t last_end_us = qpc_to_us(last_frame_present_end_qpc); double vsyncs_to_wait = (double)(int64_t)(d3d.frame_timestamp / FRAME_INTERVAL_US_DENOMINATOR - last_end_us) / estimated_vsync_interval_us; //printf("ts: %llu, last_end_us: %llu, Init v: %f\n", d3d.frame_timestamp / 3, last_end_us, vsyncs_to_wait); if (vsyncs_to_wait <= 0) { // Too late if ((int64_t)(d3d.frame_timestamp / FRAME_INTERVAL_US_DENOMINATOR - last_end_us) < -66666) { // The application must have been paused or similar vsyncs_to_wait = round(((double)FRAME_INTERVAL_US_NUMERATOR / FRAME_INTERVAL_US_DENOMINATOR) / estimated_vsync_interval_us); if (vsyncs_to_wait < 1) { vsyncs_to_wait = 1; } d3d.frame_timestamp = FRAME_INTERVAL_US_DENOMINATOR * (last_end_us + vsyncs_to_wait * estimated_vsync_interval_us); } else { // Drop frame //printf("Dropping frame\n"); d3d.dropped_frame = true; return false; } } if (floor(vsyncs_to_wait) != vsyncs_to_wait) { uint64_t left = last_end_us + floor(vsyncs_to_wait) * estimated_vsync_interval_us; uint64_t right = last_end_us + ceil(vsyncs_to_wait) * estimated_vsync_interval_us; uint64_t adjusted_desired_time = d3d.frame_timestamp / FRAME_INTERVAL_US_DENOMINATOR + (last_end_us + (FRAME_INTERVAL_US_NUMERATOR / FRAME_INTERVAL_US_DENOMINATOR) > d3d.frame_timestamp / FRAME_INTERVAL_US_DENOMINATOR ? 2000 : -2000); int64_t diff_left = adjusted_desired_time - left; int64_t diff_right = right - adjusted_desired_time; if (diff_left < 0) { diff_left = -diff_left; } if (diff_right < 0) { diff_right = -diff_right; } if (diff_left < diff_right) { vsyncs_to_wait = floor(vsyncs_to_wait); } else { vsyncs_to_wait = ceil(vsyncs_to_wait); } if (vsyncs_to_wait == 0) { //printf("vsyncs_to_wait became 0 so dropping frame\n"); d3d.dropped_frame = true; return false; } } //printf("v: %d\n", (int)vsyncs_to_wait); if (vsyncs_to_wait > 4) { // Invalid, so change to 4 vsyncs_to_wait = 4; } d3d.length_in_vsync_frames = vsyncs_to_wait; } else { d3d.length_in_vsync_frames = 2; } return true; } static void gfx_d3d12_dxgi_swap_buffers_begin(void) { if (max_texture_uploads < texture_uploads && texture_uploads != 38 && texture_uploads != 34 && texture_uploads != 29) { max_texture_uploads = texture_uploads; } //printf("Texture uploads: %d %d\n", max_texture_uploads, texture_uploads); texture_uploads = 0; ThrowIfFailed(d3d.copy_command_list->Close()); { ID3D12CommandList *lists[] = { d3d.copy_command_list.Get() }; d3d.copy_command_queue->ExecuteCommandLists(1, lists); d3d.copy_command_queue->Signal(d3d.copy_fence.Get(), ++d3d.copy_fence_value); } CD3DX12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition( d3d.render_targets[d3d.frame_index].Get(), D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT); d3d.command_list->ResourceBarrier(1, &barrier); d3d.command_queue->Wait(d3d.copy_fence.Get(), d3d.copy_fence_value); ThrowIfFailed(d3d.command_list->Close()); { ID3D12CommandList *lists[] = { d3d.command_list.Get() }; d3d.command_queue->ExecuteCommandLists(1, lists); } { LARGE_INTEGER t0; QueryPerformanceCounter(&t0); //printf("Present: %llu %u\n", (unsigned long long)(t0.QuadPart - d3d.qpc_init), d3d.length_in_vsync_frames); } //d3d.length_in_vsync_frames = 1; ThrowIfFailed(d3d.swap_chain->Present(d3d.length_in_vsync_frames, 0)); UINT this_present_id; if (d3d.swap_chain->GetLastPresentCount(&this_present_id) == S_OK) { d3d.pending_frame_stats.insert(std::make_pair(this_present_id, d3d.length_in_vsync_frames)); } d3d.dropped_frame = false; } static void gfx_d3d12_dxgi_swap_buffers_end(void) { LARGE_INTEGER t0, t1, t2; QueryPerformanceCounter(&t0); static UINT64 fence_value; ThrowIfFailed(d3d.command_queue->Signal(d3d.fence.Get(), ++fence_value)); if (d3d.fence->GetCompletedValue() < fence_value) { ThrowIfFailed(d3d.fence->SetEventOnCompletion(fence_value, d3d.fence_event)); WaitForSingleObject(d3d.fence_event, INFINITE); } QueryPerformanceCounter(&t1); d3d.resources_to_clean_at_end_of_frame.clear(); for (std::pair>& heap : d3d.upload_heaps_in_flight) { d3d.upload_heaps[heap.first].push_back(std::move(heap.second)); } d3d.upload_heaps_in_flight.clear(); for (std::pair& item : d3d.texture_heap_allocations_to_reclaim_at_end_of_frame) { item.first->free_list.push_back(item.second); } d3d.texture_heap_allocations_to_reclaim_at_end_of_frame.clear(); if (!d3d.dropped_frame) { WaitForSingleObject(d3d.waitable_object, INFINITE); } DXGI_FRAME_STATISTICS stats; d3d.swap_chain->GetFrameStatistics(&stats); QueryPerformanceCounter(&t2); d3d.frame_index = d3d.swap_chain->GetCurrentBackBufferIndex(); ThrowIfFailed(d3d.copy_command_allocator->Reset()); ThrowIfFailed(d3d.copy_command_list->Reset(d3d.copy_command_allocator.Get(), nullptr)); d3d.sync_interval_means_frames_to_wait = d3d.pending_frame_stats.rbegin()->first == stats.PresentCount; //printf("done %llu gpu:%d wait:%d freed:%llu frame:%u %u monitor:%u t:%llu\n", (unsigned long long)(t0.QuadPart - d3d.qpc_init), (int)(t1.QuadPart - t0.QuadPart), (int)(t2.QuadPart - t0.QuadPart), (unsigned long long)(t2.QuadPart - d3d.qpc_init), d3d.pending_frame_stats.rbegin()->first, stats.PresentCount, stats.SyncRefreshCount, (unsigned long long)(stats.SyncQPCTime.QuadPart - d3d.qpc_init)); } double gfx_d3d12_dxgi_get_time(void) { LARGE_INTEGER t; QueryPerformanceCounter(&t); return (double)(t.QuadPart - d3d.qpc_init) / d3d.qpc_freq; } struct GfxRenderingAPI gfx_d3d12_api = { gfx_d3d12_z_is_from_0_to_1, gfx_d3d12_unload_shader, gfx_d3d12_load_shader, gfx_d3d12_create_and_load_new_shader, gfx_d3d12_lookup_shader, gfx_d3d12_shader_get_info, gfx_d3d12_new_texture, gfx_d3d12_select_texture, gfx_d3d12_upload_texture, gfx_d3d12_set_sampler_parameters, gfx_d3d12_set_depth_test, gfx_d3d12_set_depth_mask, gfx_d3d12_set_zmode_decal, gfx_d3d12_set_viewport, gfx_d3d12_set_scissor, gfx_d3d12_set_use_alpha, gfx_d3d12_draw_triangles, gfx_d3d12_init, gfx_d3d12_start_frame, gfx_d3d12_shutdown, }; struct GfxWindowManagerAPI gfx_dxgi = { gfx_d3d12_dxgi_init, gfx_d3d12_dxgi_set_keyboard_callbacks, gfx_d3d12_dxgi_main_loop, gfx_d3d12_dxgi_get_dimensions, gfx_d3d12_dxgi_handle_events, gfx_d3d12_dxgi_start_frame, gfx_d3d12_dxgi_swap_buffers_begin, gfx_d3d12_dxgi_swap_buffers_end, gfx_d3d12_dxgi_get_time, gfx_d3d12_dxgi_shutdown, }; #else #error "D3D12 is only supported on Windows" #endif // _WIN32 #endif // RAPI_D3D12