├── LICENSE.txt ├── README.md ├── dxsdk ├── d3d12.h ├── d3d12sdklayers.h ├── d3dcommon.h └── d3dx12.h ├── gfx_cc.c ├── gfx_cc.h ├── gfx_direct3d11.cpp ├── gfx_direct3d11.h ├── gfx_direct3d12.cpp ├── gfx_direct3d12.h ├── gfx_direct3d12_guids.h ├── gfx_direct3d_common.cpp ├── gfx_direct3d_common.h ├── gfx_dxgi.cpp ├── gfx_dxgi.h ├── gfx_glx.c ├── gfx_glx.h ├── gfx_opengl.c ├── gfx_opengl.h ├── gfx_pc.c ├── gfx_pc.h ├── gfx_rendering_api.h ├── gfx_screen_config.h ├── gfx_sdl.h ├── gfx_sdl2.c └── gfx_window_manager_api.h /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Emill, MaikelChan 2 | 3 | Redistribution and use in source forms, with or without modification, 4 | are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this 7 | list of conditions and the following disclaimer. 8 | 2. Redistributions in binary form are not allowed except in cases where the binary contains no assets you do not have the right to distribute. 9 | 10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 11 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 12 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 13 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 14 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 15 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 16 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 17 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 18 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 19 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nintendo 64 Fast3D renderer 2 | 3 | Implementation of a Fast3D renderer for games built originally for the Nintendo 64 platform. 4 | 5 | For rendering OpenGL, Direct3D 11 and Direct3D 12 are supported. 6 | 7 | Supported windowing systems are GLX (used on Linux), DXGI (used on Windows) and SDL (generic). 8 | 9 | # Usage 10 | 11 | See `gfx_pc.h`. You will also need a copy of `PR/gbi.h`, found in libultra. 12 | 13 | First call `gfx_init(struct GfxWindowManagerAPI *wapi, struct GfxRenderingAPI *rapi, const char *game_name, bool start_in_fullscreen)` and supply the desired backends at program start. 14 | 15 | Some callbacks can be set on `wapi`. See `gfx_window_manager_api.h` for more info. 16 | 17 | Each game main loop iteration should look like this: 18 | 19 | ```C 20 | gfx_start_frame(); // Handles input events such as keyboard and window events 21 | // perform game logic here 22 | gfx_run(cmds); // submit display list and render a frame 23 | // do more expensive work here, such as play audio 24 | gfx_end_frame(); // this just waits until the frame is shown on the screen (vsync), to provide correct game timing 25 | ``` 26 | 27 | When you are ready to start the main loop, call `wapi->main_loop(one_iteration_func)`. 28 | 29 | For the best experience, please change the Vtx and Mtx structures to use floats instead of fixed point arithmetic (`GBI_FLOATS`). 30 | 31 | # License 32 | 33 | See LICENSE.txt. Redistributions are allowed only in source form, not in binary form. 34 | -------------------------------------------------------------------------------- /gfx_cc.c: -------------------------------------------------------------------------------- 1 | #include "gfx_cc.h" 2 | 3 | void gfx_cc_get_features(uint32_t shader_id, struct CCFeatures *cc_features) { 4 | for (int i = 0; i < 4; i++) { 5 | cc_features->c[0][i] = (shader_id >> (i * 3)) & 7; 6 | cc_features->c[1][i] = (shader_id >> (12 + i * 3)) & 7; 7 | } 8 | 9 | cc_features->opt_alpha = (shader_id & SHADER_OPT_ALPHA) != 0; 10 | cc_features->opt_fog = (shader_id & SHADER_OPT_FOG) != 0; 11 | cc_features->opt_texture_edge = (shader_id & SHADER_OPT_TEXTURE_EDGE) != 0; 12 | cc_features->opt_noise = (shader_id & SHADER_OPT_NOISE) != 0; 13 | 14 | cc_features->used_textures[0] = false; 15 | cc_features->used_textures[1] = false; 16 | cc_features->num_inputs = 0; 17 | 18 | for (int i = 0; i < 2; i++) { 19 | for (int j = 0; j < 4; j++) { 20 | if (cc_features->c[i][j] >= SHADER_INPUT_1 && cc_features->c[i][j] <= SHADER_INPUT_4) { 21 | if (cc_features->c[i][j] > cc_features->num_inputs) { 22 | cc_features->num_inputs = cc_features->c[i][j]; 23 | } 24 | } 25 | if (cc_features->c[i][j] == SHADER_TEXEL0 || cc_features->c[i][j] == SHADER_TEXEL0A) { 26 | cc_features->used_textures[0] = true; 27 | } 28 | if (cc_features->c[i][j] == SHADER_TEXEL1) { 29 | cc_features->used_textures[1] = true; 30 | } 31 | } 32 | } 33 | 34 | cc_features->do_single[0] = cc_features->c[0][2] == 0; 35 | cc_features->do_single[1] = cc_features->c[1][2] == 0; 36 | cc_features->do_multiply[0] = cc_features->c[0][1] == 0 && cc_features->c[0][3] == 0; 37 | cc_features->do_multiply[1] = cc_features->c[1][1] == 0 && cc_features->c[1][3] == 0; 38 | cc_features->do_mix[0] = cc_features->c[0][1] == cc_features->c[0][3]; 39 | cc_features->do_mix[1] = cc_features->c[1][1] == cc_features->c[1][3]; 40 | cc_features->color_alpha_same = (shader_id & 0xfff) == ((shader_id >> 12) & 0xfff); 41 | } 42 | -------------------------------------------------------------------------------- /gfx_cc.h: -------------------------------------------------------------------------------- 1 | #ifndef GFX_CC_H 2 | #define GFX_CC_H 3 | 4 | #include 5 | #include 6 | 7 | enum { 8 | CC_0, 9 | CC_TEXEL0, 10 | CC_TEXEL1, 11 | CC_PRIM, 12 | CC_SHADE, 13 | CC_ENV, 14 | CC_TEXEL0A, 15 | CC_LOD 16 | }; 17 | 18 | enum { 19 | SHADER_0, 20 | SHADER_INPUT_1, 21 | SHADER_INPUT_2, 22 | SHADER_INPUT_3, 23 | SHADER_INPUT_4, 24 | SHADER_TEXEL0, 25 | SHADER_TEXEL0A, 26 | SHADER_TEXEL1 27 | }; 28 | 29 | #define SHADER_OPT_ALPHA (1 << 24) 30 | #define SHADER_OPT_FOG (1 << 25) 31 | #define SHADER_OPT_TEXTURE_EDGE (1 << 26) 32 | #define SHADER_OPT_NOISE (1 << 27) 33 | 34 | struct CCFeatures { 35 | uint8_t c[2][4]; 36 | bool opt_alpha; 37 | bool opt_fog; 38 | bool opt_texture_edge; 39 | bool opt_noise; 40 | bool used_textures[2]; 41 | int num_inputs; 42 | bool do_single[2]; 43 | bool do_multiply[2]; 44 | bool do_mix[2]; 45 | bool color_alpha_same; 46 | }; 47 | 48 | #ifdef __cplusplus 49 | extern "C" { 50 | #endif 51 | 52 | void gfx_cc_get_features(uint32_t shader_id, struct CCFeatures *cc_features); 53 | 54 | #ifdef __cplusplus 55 | } 56 | #endif 57 | 58 | #endif 59 | -------------------------------------------------------------------------------- /gfx_direct3d11.cpp: -------------------------------------------------------------------------------- 1 | #ifdef ENABLE_DX11 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #ifndef _LANGUAGE_C 16 | #define _LANGUAGE_C 17 | #endif 18 | #include 19 | 20 | #include "gfx_cc.h" 21 | #include "gfx_window_manager_api.h" 22 | #include "gfx_rendering_api.h" 23 | #include "gfx_direct3d_common.h" 24 | 25 | #define DECLARE_GFX_DXGI_FUNCTIONS 26 | #include "gfx_dxgi.h" 27 | 28 | #include "gfx_screen_config.h" 29 | 30 | #define THREE_POINT_FILTERING 0 31 | #define DEBUG_D3D 0 32 | 33 | using namespace Microsoft::WRL; // For ComPtr 34 | 35 | namespace { 36 | 37 | struct PerFrameCB { 38 | uint32_t noise_frame; 39 | float noise_scale_x; 40 | float noise_scale_y; 41 | uint32_t padding; 42 | }; 43 | 44 | struct PerDrawCB { 45 | struct Texture { 46 | uint32_t width; 47 | uint32_t height; 48 | uint32_t linear_filtering; 49 | uint32_t padding; 50 | } textures[2]; 51 | }; 52 | 53 | struct TextureData { 54 | ComPtr resource_view; 55 | ComPtr sampler_state; 56 | uint32_t width; 57 | uint32_t height; 58 | bool linear_filtering; 59 | }; 60 | 61 | struct ShaderProgramD3D11 { 62 | ComPtr vertex_shader; 63 | ComPtr pixel_shader; 64 | ComPtr input_layout; 65 | ComPtr blend_state; 66 | 67 | uint32_t shader_id; 68 | uint8_t num_inputs; 69 | uint8_t num_floats; 70 | bool used_textures[2]; 71 | }; 72 | 73 | static struct { 74 | HMODULE d3d11_module; 75 | PFN_D3D11_CREATE_DEVICE D3D11CreateDevice; 76 | 77 | HMODULE d3dcompiler_module; 78 | pD3DCompile D3DCompile; 79 | 80 | D3D_FEATURE_LEVEL feature_level; 81 | 82 | ComPtr device; 83 | ComPtr swap_chain; 84 | ComPtr context; 85 | ComPtr backbuffer_view; 86 | ComPtr depth_stencil_view; 87 | ComPtr rasterizer_state; 88 | ComPtr depth_stencil_state; 89 | ComPtr vertex_buffer; 90 | ComPtr per_frame_cb; 91 | ComPtr per_draw_cb; 92 | 93 | #if DEBUG_D3D 94 | ComPtr debug; 95 | #endif 96 | 97 | DXGI_SAMPLE_DESC sample_description; 98 | 99 | PerFrameCB per_frame_cb_data; 100 | PerDrawCB per_draw_cb_data; 101 | 102 | struct ShaderProgramD3D11 shader_program_pool[64]; 103 | uint8_t shader_program_pool_size; 104 | 105 | std::vector textures; 106 | int current_tile; 107 | uint32_t current_texture_ids[2]; 108 | 109 | // Current state 110 | 111 | struct ShaderProgramD3D11 *shader_program; 112 | 113 | uint32_t current_width, current_height; 114 | 115 | int8_t depth_test; 116 | int8_t depth_mask; 117 | int8_t zmode_decal; 118 | 119 | // Previous states (to prevent setting states needlessly) 120 | 121 | struct ShaderProgramD3D11 *last_shader_program = nullptr; 122 | uint32_t last_vertex_buffer_stride = 0; 123 | ComPtr last_blend_state = nullptr; 124 | ComPtr last_resource_views[2] = { nullptr, nullptr }; 125 | ComPtr last_sampler_states[2] = { nullptr, nullptr }; 126 | int8_t last_depth_test = -1; 127 | int8_t last_depth_mask = -1; 128 | int8_t last_zmode_decal = -1; 129 | D3D_PRIMITIVE_TOPOLOGY last_primitive_topology = D3D_PRIMITIVE_TOPOLOGY_UNDEFINED; 130 | } d3d; 131 | 132 | static LARGE_INTEGER last_time, accumulated_time, frequency; 133 | 134 | static void create_render_target_views(bool is_resize) { 135 | DXGI_SWAP_CHAIN_DESC1 desc1; 136 | 137 | if (is_resize) { 138 | // Release previous stuff (if any) 139 | 140 | d3d.backbuffer_view.Reset(); 141 | d3d.depth_stencil_view.Reset(); 142 | 143 | // Resize swap chain buffers 144 | 145 | ThrowIfFailed(d3d.swap_chain->GetDesc1(&desc1)); 146 | ThrowIfFailed(d3d.swap_chain->ResizeBuffers(0, 0, 0, DXGI_FORMAT_UNKNOWN, desc1.Flags), 147 | gfx_dxgi_get_h_wnd(), "Failed to resize IDXGISwapChain buffers."); 148 | } 149 | 150 | // Get new size 151 | 152 | ThrowIfFailed(d3d.swap_chain->GetDesc1(&desc1)); 153 | 154 | // Create back buffer 155 | 156 | ComPtr backbuffer_texture; 157 | ThrowIfFailed(d3d.swap_chain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID *) backbuffer_texture.GetAddressOf()), 158 | gfx_dxgi_get_h_wnd(), "Failed to get backbuffer from IDXGISwapChain."); 159 | 160 | ThrowIfFailed(d3d.device->CreateRenderTargetView(backbuffer_texture.Get(), nullptr, d3d.backbuffer_view.GetAddressOf()), 161 | gfx_dxgi_get_h_wnd(), "Failed to create render target view."); 162 | 163 | // Create depth buffer 164 | 165 | D3D11_TEXTURE2D_DESC depth_stencil_texture_desc; 166 | ZeroMemory(&depth_stencil_texture_desc, sizeof(D3D11_TEXTURE2D_DESC)); 167 | 168 | depth_stencil_texture_desc.Width = desc1.Width; 169 | depth_stencil_texture_desc.Height = desc1.Height; 170 | depth_stencil_texture_desc.MipLevels = 1; 171 | depth_stencil_texture_desc.ArraySize = 1; 172 | depth_stencil_texture_desc.Format = d3d.feature_level >= D3D_FEATURE_LEVEL_10_0 ? 173 | DXGI_FORMAT_D32_FLOAT : DXGI_FORMAT_D24_UNORM_S8_UINT; 174 | depth_stencil_texture_desc.SampleDesc = d3d.sample_description; 175 | depth_stencil_texture_desc.Usage = D3D11_USAGE_DEFAULT; 176 | depth_stencil_texture_desc.BindFlags = D3D11_BIND_DEPTH_STENCIL; 177 | depth_stencil_texture_desc.CPUAccessFlags = 0; 178 | depth_stencil_texture_desc.MiscFlags = 0; 179 | 180 | ComPtr depth_stencil_texture; 181 | ThrowIfFailed(d3d.device->CreateTexture2D(&depth_stencil_texture_desc, nullptr, depth_stencil_texture.GetAddressOf())); 182 | ThrowIfFailed(d3d.device->CreateDepthStencilView(depth_stencil_texture.Get(), nullptr, d3d.depth_stencil_view.GetAddressOf())); 183 | 184 | // Save resolution 185 | 186 | d3d.current_width = desc1.Width; 187 | d3d.current_height = desc1.Height; 188 | } 189 | 190 | static void gfx_d3d11_init(void) { 191 | // Load d3d11.dll 192 | d3d.d3d11_module = LoadLibraryW(L"d3d11.dll"); 193 | if (d3d.d3d11_module == nullptr) { 194 | ThrowIfFailed(HRESULT_FROM_WIN32(GetLastError()), gfx_dxgi_get_h_wnd(), "d3d11.dll not found"); 195 | } 196 | d3d.D3D11CreateDevice = (PFN_D3D11_CREATE_DEVICE)GetProcAddress(d3d.d3d11_module, "D3D11CreateDevice"); 197 | 198 | // Load D3DCompiler_47.dll 199 | d3d.d3dcompiler_module = LoadLibraryW(L"D3DCompiler_47.dll"); 200 | if (d3d.d3dcompiler_module == nullptr) { 201 | ThrowIfFailed(HRESULT_FROM_WIN32(GetLastError()), gfx_dxgi_get_h_wnd(), "D3DCompiler_47.dll not found"); 202 | } 203 | d3d.D3DCompile = (pD3DCompile)GetProcAddress(d3d.d3dcompiler_module, "D3DCompile"); 204 | 205 | // Create D3D11 device 206 | 207 | gfx_dxgi_create_factory_and_device(DEBUG_D3D, 11, [](IDXGIAdapter1 *adapter, bool test_only) { 208 | #if DEBUG_D3D 209 | UINT device_creation_flags = D3D11_CREATE_DEVICE_DEBUG; 210 | #else 211 | UINT device_creation_flags = 0; 212 | #endif 213 | D3D_FEATURE_LEVEL FeatureLevels[] = { 214 | D3D_FEATURE_LEVEL_11_0, 215 | D3D_FEATURE_LEVEL_10_1, 216 | D3D_FEATURE_LEVEL_10_0, 217 | D3D_FEATURE_LEVEL_9_3, 218 | D3D_FEATURE_LEVEL_9_2, 219 | D3D_FEATURE_LEVEL_9_1 220 | }; 221 | 222 | HRESULT res = d3d.D3D11CreateDevice( 223 | adapter, 224 | D3D_DRIVER_TYPE_UNKNOWN, // since we use a specific adapter 225 | nullptr, 226 | device_creation_flags, 227 | FeatureLevels, 228 | ARRAYSIZE(FeatureLevels), 229 | D3D11_SDK_VERSION, 230 | test_only ? nullptr : d3d.device.GetAddressOf(), 231 | &d3d.feature_level, 232 | test_only ? nullptr : d3d.context.GetAddressOf()); 233 | 234 | if (test_only) { 235 | return SUCCEEDED(res); 236 | } else { 237 | ThrowIfFailed(res, gfx_dxgi_get_h_wnd(), "Failed to create D3D11 device."); 238 | return true; 239 | } 240 | }); 241 | 242 | // Sample description to be used in back buffer and depth buffer 243 | 244 | d3d.sample_description.Count = 1; 245 | d3d.sample_description.Quality = 0; 246 | 247 | // Create the swap chain 248 | d3d.swap_chain = gfx_dxgi_create_swap_chain(d3d.device.Get()); 249 | 250 | // Create D3D Debug device if in debug mode 251 | 252 | #if DEBUG_D3D 253 | ThrowIfFailed(d3d.device->QueryInterface(__uuidof(ID3D11Debug), (void **) d3d.debug.GetAddressOf()), 254 | gfx_dxgi_get_h_wnd(), "Failed to get ID3D11Debug device."); 255 | #endif 256 | 257 | // Create views 258 | 259 | create_render_target_views(false); 260 | 261 | // Create main vertex buffer 262 | 263 | D3D11_BUFFER_DESC vertex_buffer_desc; 264 | ZeroMemory(&vertex_buffer_desc, sizeof(D3D11_BUFFER_DESC)); 265 | 266 | vertex_buffer_desc.Usage = D3D11_USAGE_DYNAMIC; 267 | vertex_buffer_desc.ByteWidth = 256 * 26 * 3 * sizeof(float); // Same as buf_vbo size in gfx_pc 268 | vertex_buffer_desc.BindFlags = D3D11_BIND_VERTEX_BUFFER; 269 | vertex_buffer_desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; 270 | vertex_buffer_desc.MiscFlags = 0; 271 | 272 | ThrowIfFailed(d3d.device->CreateBuffer(&vertex_buffer_desc, nullptr, d3d.vertex_buffer.GetAddressOf()), 273 | gfx_dxgi_get_h_wnd(), "Failed to create vertex buffer."); 274 | 275 | // Create per-frame constant buffer 276 | 277 | D3D11_BUFFER_DESC constant_buffer_desc; 278 | ZeroMemory(&constant_buffer_desc, sizeof(D3D11_BUFFER_DESC)); 279 | 280 | constant_buffer_desc.Usage = D3D11_USAGE_DYNAMIC; 281 | constant_buffer_desc.ByteWidth = sizeof(PerFrameCB); 282 | constant_buffer_desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; 283 | constant_buffer_desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; 284 | constant_buffer_desc.MiscFlags = 0; 285 | 286 | ThrowIfFailed(d3d.device->CreateBuffer(&constant_buffer_desc, nullptr, d3d.per_frame_cb.GetAddressOf()), 287 | gfx_dxgi_get_h_wnd(), "Failed to create per-frame constant buffer."); 288 | 289 | d3d.context->PSSetConstantBuffers(0, 1, d3d.per_frame_cb.GetAddressOf()); 290 | 291 | // Create per-draw constant buffer 292 | 293 | constant_buffer_desc.Usage = D3D11_USAGE_DYNAMIC; 294 | constant_buffer_desc.ByteWidth = sizeof(PerDrawCB); 295 | constant_buffer_desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; 296 | constant_buffer_desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; 297 | constant_buffer_desc.MiscFlags = 0; 298 | 299 | ThrowIfFailed(d3d.device->CreateBuffer(&constant_buffer_desc, nullptr, d3d.per_draw_cb.GetAddressOf()), 300 | gfx_dxgi_get_h_wnd(), "Failed to create per-draw constant buffer."); 301 | 302 | d3d.context->PSSetConstantBuffers(1, 1, d3d.per_draw_cb.GetAddressOf()); 303 | } 304 | 305 | 306 | static bool gfx_d3d11_z_is_from_0_to_1(void) { 307 | return true; 308 | } 309 | 310 | static void gfx_d3d11_unload_shader(struct ShaderProgram *old_prg) { 311 | } 312 | 313 | static void gfx_d3d11_load_shader(struct ShaderProgram *new_prg) { 314 | d3d.shader_program = (struct ShaderProgramD3D11 *)new_prg; 315 | } 316 | 317 | static struct ShaderProgram *gfx_d3d11_create_and_load_new_shader(uint32_t shader_id) { 318 | CCFeatures cc_features; 319 | gfx_cc_get_features(shader_id, &cc_features); 320 | 321 | char buf[4096]; 322 | size_t len, num_floats; 323 | 324 | gfx_direct3d_common_build_shader(buf, len, num_floats, cc_features, false, THREE_POINT_FILTERING); 325 | 326 | ComPtr vs, ps; 327 | ComPtr error_blob; 328 | 329 | #if DEBUG_D3D 330 | UINT compile_flags = D3DCOMPILE_DEBUG; 331 | #else 332 | UINT compile_flags = D3DCOMPILE_OPTIMIZATION_LEVEL2; 333 | #endif 334 | 335 | HRESULT hr = d3d.D3DCompile(buf, len, nullptr, nullptr, nullptr, "VSMain", "vs_4_0_level_9_1", compile_flags, 0, vs.GetAddressOf(), error_blob.GetAddressOf()); 336 | 337 | if (FAILED(hr)) { 338 | MessageBox(gfx_dxgi_get_h_wnd(), (char *) error_blob->GetBufferPointer(), "Error", MB_OK | MB_ICONERROR); 339 | throw hr; 340 | } 341 | 342 | hr = d3d.D3DCompile(buf, len, nullptr, nullptr, nullptr, "PSMain", "ps_4_0_level_9_1", compile_flags, 0, ps.GetAddressOf(), error_blob.GetAddressOf()); 343 | 344 | if (FAILED(hr)) { 345 | MessageBox(gfx_dxgi_get_h_wnd(), (char *) error_blob->GetBufferPointer(), "Error", MB_OK | MB_ICONERROR); 346 | throw hr; 347 | } 348 | 349 | struct ShaderProgramD3D11 *prg = &d3d.shader_program_pool[d3d.shader_program_pool_size++]; 350 | 351 | ThrowIfFailed(d3d.device->CreateVertexShader(vs->GetBufferPointer(), vs->GetBufferSize(), nullptr, prg->vertex_shader.GetAddressOf())); 352 | ThrowIfFailed(d3d.device->CreatePixelShader(ps->GetBufferPointer(), ps->GetBufferSize(), nullptr, prg->pixel_shader.GetAddressOf())); 353 | 354 | // Input Layout 355 | 356 | D3D11_INPUT_ELEMENT_DESC ied[7]; 357 | uint8_t ied_index = 0; 358 | ied[ied_index++] = { "POSITION", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 }; 359 | if (cc_features.used_textures[0] || cc_features.used_textures[1]) { 360 | ied[ied_index++] = { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 }; 361 | } 362 | if (cc_features.opt_fog) { 363 | ied[ied_index++] = { "FOG", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 }; 364 | } 365 | for (unsigned int i = 0; i < cc_features.num_inputs; i++) { 366 | DXGI_FORMAT format = cc_features.opt_alpha ? DXGI_FORMAT_R32G32B32A32_FLOAT : DXGI_FORMAT_R32G32B32_FLOAT; 367 | ied[ied_index++] = { "INPUT", i, format, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 }; 368 | } 369 | 370 | ThrowIfFailed(d3d.device->CreateInputLayout(ied, ied_index, vs->GetBufferPointer(), vs->GetBufferSize(), prg->input_layout.GetAddressOf())); 371 | 372 | // Blend state 373 | 374 | D3D11_BLEND_DESC blend_desc; 375 | ZeroMemory(&blend_desc, sizeof(D3D11_BLEND_DESC)); 376 | 377 | if (cc_features.opt_alpha) { 378 | blend_desc.RenderTarget[0].BlendEnable = true; 379 | blend_desc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA; 380 | blend_desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA; 381 | blend_desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; 382 | blend_desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE; 383 | blend_desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO; 384 | blend_desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; 385 | blend_desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; 386 | } else { 387 | blend_desc.RenderTarget[0].BlendEnable = false; 388 | blend_desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; 389 | } 390 | 391 | ThrowIfFailed(d3d.device->CreateBlendState(&blend_desc, prg->blend_state.GetAddressOf())); 392 | 393 | // Save some values 394 | 395 | prg->shader_id = shader_id; 396 | prg->num_inputs = cc_features.num_inputs; 397 | prg->num_floats = num_floats; 398 | prg->used_textures[0] = cc_features.used_textures[0]; 399 | prg->used_textures[1] = cc_features.used_textures[1]; 400 | 401 | return (struct ShaderProgram *)(d3d.shader_program = prg); 402 | } 403 | 404 | static struct ShaderProgram *gfx_d3d11_lookup_shader(uint32_t shader_id) { 405 | for (size_t i = 0; i < d3d.shader_program_pool_size; i++) { 406 | if (d3d.shader_program_pool[i].shader_id == shader_id) { 407 | return (struct ShaderProgram *)&d3d.shader_program_pool[i]; 408 | } 409 | } 410 | return nullptr; 411 | } 412 | 413 | static void gfx_d3d11_shader_get_info(struct ShaderProgram *prg, uint8_t *num_inputs, bool used_textures[2]) { 414 | struct ShaderProgramD3D11 *p = (struct ShaderProgramD3D11 *)prg; 415 | 416 | *num_inputs = p->num_inputs; 417 | used_textures[0] = p->used_textures[0]; 418 | used_textures[1] = p->used_textures[1]; 419 | } 420 | 421 | static uint32_t gfx_d3d11_new_texture(void) { 422 | d3d.textures.resize(d3d.textures.size() + 1); 423 | return (uint32_t)(d3d.textures.size() - 1); 424 | } 425 | 426 | static void gfx_d3d11_select_texture(int tile, uint32_t texture_id) { 427 | d3d.current_tile = tile; 428 | d3d.current_texture_ids[tile] = texture_id; 429 | } 430 | 431 | static D3D11_TEXTURE_ADDRESS_MODE gfx_cm_to_d3d11(uint32_t val) { 432 | if (val & G_TX_CLAMP) { 433 | return D3D11_TEXTURE_ADDRESS_CLAMP; 434 | } 435 | return (val & G_TX_MIRROR) ? D3D11_TEXTURE_ADDRESS_MIRROR : D3D11_TEXTURE_ADDRESS_WRAP; 436 | } 437 | 438 | static void gfx_d3d11_upload_texture(const uint8_t *rgba32_buf, int width, int height) { 439 | // Create texture 440 | 441 | D3D11_TEXTURE2D_DESC texture_desc; 442 | ZeroMemory(&texture_desc, sizeof(D3D11_TEXTURE2D_DESC)); 443 | 444 | texture_desc.Width = width; 445 | texture_desc.Height = height; 446 | texture_desc.Usage = D3D11_USAGE_IMMUTABLE; 447 | texture_desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; 448 | texture_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; 449 | texture_desc.CPUAccessFlags = 0; 450 | texture_desc.MiscFlags = 0; // D3D11_RESOURCE_MISC_GENERATE_MIPS ? 451 | texture_desc.ArraySize = 1; 452 | texture_desc.MipLevels = 1; 453 | texture_desc.SampleDesc.Count = 1; 454 | texture_desc.SampleDesc.Quality = 0; 455 | 456 | D3D11_SUBRESOURCE_DATA resource_data; 457 | resource_data.pSysMem = rgba32_buf; 458 | resource_data.SysMemPitch = width * 4; 459 | resource_data.SysMemSlicePitch = resource_data.SysMemPitch * height; 460 | 461 | ComPtr texture; 462 | ThrowIfFailed(d3d.device->CreateTexture2D(&texture_desc, &resource_data, texture.GetAddressOf())); 463 | 464 | // Create shader resource view from texture 465 | 466 | D3D11_SHADER_RESOURCE_VIEW_DESC resource_view_desc; 467 | ZeroMemory(&resource_view_desc, sizeof(D3D11_SHADER_RESOURCE_VIEW_DESC)); 468 | 469 | resource_view_desc.Format = texture_desc.Format; 470 | resource_view_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; 471 | resource_view_desc.Texture2D.MostDetailedMip = 0; 472 | resource_view_desc.Texture2D.MipLevels = -1; 473 | 474 | TextureData *texture_data = &d3d.textures[d3d.current_texture_ids[d3d.current_tile]]; 475 | texture_data->width = width; 476 | texture_data->height = height; 477 | 478 | if (texture_data->resource_view.Get() != nullptr) { 479 | // Free the previous texture in this slot 480 | texture_data->resource_view.Reset(); 481 | } 482 | 483 | ThrowIfFailed(d3d.device->CreateShaderResourceView(texture.Get(), &resource_view_desc, texture_data->resource_view.GetAddressOf())); 484 | } 485 | 486 | static void gfx_d3d11_set_sampler_parameters(int tile, bool linear_filter, uint32_t cms, uint32_t cmt) { 487 | D3D11_SAMPLER_DESC sampler_desc; 488 | ZeroMemory(&sampler_desc, sizeof(D3D11_SAMPLER_DESC)); 489 | 490 | #if THREE_POINT_FILTERING 491 | sampler_desc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT; 492 | #else 493 | sampler_desc.Filter = linear_filter ? D3D11_FILTER_MIN_MAG_MIP_LINEAR : D3D11_FILTER_MIN_MAG_MIP_POINT; 494 | #endif 495 | sampler_desc.AddressU = gfx_cm_to_d3d11(cms); 496 | sampler_desc.AddressV = gfx_cm_to_d3d11(cmt); 497 | sampler_desc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP; 498 | sampler_desc.MinLOD = 0; 499 | sampler_desc.MaxLOD = D3D11_FLOAT32_MAX; 500 | 501 | TextureData *texture_data = &d3d.textures[d3d.current_texture_ids[tile]]; 502 | texture_data->linear_filtering = linear_filter; 503 | 504 | // This function is called twice per texture, the first one only to set default values. 505 | // Maybe that could be skipped? Anyway, make sure to release the first default sampler 506 | // state before setting the actual one. 507 | texture_data->sampler_state.Reset(); 508 | 509 | ThrowIfFailed(d3d.device->CreateSamplerState(&sampler_desc, texture_data->sampler_state.GetAddressOf())); 510 | } 511 | 512 | static void gfx_d3d11_set_depth_test(bool depth_test) { 513 | d3d.depth_test = depth_test; 514 | } 515 | 516 | static void gfx_d3d11_set_depth_mask(bool depth_mask) { 517 | d3d.depth_mask = depth_mask; 518 | } 519 | 520 | static void gfx_d3d11_set_zmode_decal(bool zmode_decal) { 521 | d3d.zmode_decal = zmode_decal; 522 | } 523 | 524 | static void gfx_d3d11_set_viewport(int x, int y, int width, int height) { 525 | D3D11_VIEWPORT viewport; 526 | viewport.TopLeftX = x; 527 | viewport.TopLeftY = d3d.current_height - y - height; 528 | viewport.Width = width; 529 | viewport.Height = height; 530 | viewport.MinDepth = 0.0f; 531 | viewport.MaxDepth = 1.0f; 532 | 533 | d3d.context->RSSetViewports(1, &viewport); 534 | } 535 | 536 | static void gfx_d3d11_set_scissor(int x, int y, int width, int height) { 537 | D3D11_RECT rect; 538 | rect.left = x; 539 | rect.top = d3d.current_height - y - height; 540 | rect.right = x + width; 541 | rect.bottom = d3d.current_height - y; 542 | 543 | d3d.context->RSSetScissorRects(1, &rect); 544 | } 545 | 546 | static void gfx_d3d11_set_use_alpha(bool use_alpha) { 547 | // Already part of the pipeline state from shader info 548 | } 549 | 550 | static void gfx_d3d11_draw_triangles(float buf_vbo[], size_t buf_vbo_len, size_t buf_vbo_num_tris) { 551 | 552 | if (d3d.last_depth_test != d3d.depth_test || d3d.last_depth_mask != d3d.depth_mask) { 553 | d3d.last_depth_test = d3d.depth_test; 554 | d3d.last_depth_mask = d3d.depth_mask; 555 | 556 | d3d.depth_stencil_state.Reset(); 557 | 558 | D3D11_DEPTH_STENCIL_DESC depth_stencil_desc; 559 | ZeroMemory(&depth_stencil_desc, sizeof(D3D11_DEPTH_STENCIL_DESC)); 560 | 561 | depth_stencil_desc.DepthEnable = d3d.depth_test; 562 | depth_stencil_desc.DepthWriteMask = d3d.depth_mask ? D3D11_DEPTH_WRITE_MASK_ALL : D3D11_DEPTH_WRITE_MASK_ZERO; 563 | depth_stencil_desc.DepthFunc = D3D11_COMPARISON_LESS_EQUAL; 564 | depth_stencil_desc.StencilEnable = false; 565 | 566 | ThrowIfFailed(d3d.device->CreateDepthStencilState(&depth_stencil_desc, d3d.depth_stencil_state.GetAddressOf())); 567 | d3d.context->OMSetDepthStencilState(d3d.depth_stencil_state.Get(), 0); 568 | } 569 | 570 | if (d3d.last_zmode_decal != d3d.zmode_decal) { 571 | d3d.last_zmode_decal = d3d.zmode_decal; 572 | 573 | d3d.rasterizer_state.Reset(); 574 | 575 | D3D11_RASTERIZER_DESC rasterizer_desc; 576 | ZeroMemory(&rasterizer_desc, sizeof(D3D11_RASTERIZER_DESC)); 577 | 578 | rasterizer_desc.FillMode = D3D11_FILL_SOLID; 579 | rasterizer_desc.CullMode = D3D11_CULL_NONE; 580 | rasterizer_desc.FrontCounterClockwise = true; 581 | rasterizer_desc.DepthBias = 0; 582 | rasterizer_desc.SlopeScaledDepthBias = d3d.zmode_decal ? -2.0f : 0.0f; 583 | rasterizer_desc.DepthBiasClamp = 0.0f; 584 | rasterizer_desc.DepthClipEnable = true; 585 | rasterizer_desc.ScissorEnable = true; 586 | rasterizer_desc.MultisampleEnable = false; 587 | rasterizer_desc.AntialiasedLineEnable = false; 588 | 589 | ThrowIfFailed(d3d.device->CreateRasterizerState(&rasterizer_desc, d3d.rasterizer_state.GetAddressOf())); 590 | d3d.context->RSSetState(d3d.rasterizer_state.Get()); 591 | } 592 | 593 | bool textures_changed = false; 594 | 595 | for (int i = 0; i < 2; i++) { 596 | if (d3d.shader_program->used_textures[i]) { 597 | if (d3d.last_resource_views[i].Get() != d3d.textures[d3d.current_texture_ids[i]].resource_view.Get()) { 598 | d3d.last_resource_views[i] = d3d.textures[d3d.current_texture_ids[i]].resource_view.Get(); 599 | d3d.context->PSSetShaderResources(i, 1, d3d.textures[d3d.current_texture_ids[i]].resource_view.GetAddressOf()); 600 | 601 | #if THREE_POINT_FILTERING 602 | d3d.per_draw_cb_data.textures[i].width = d3d.textures[d3d.current_texture_ids[i]].width; 603 | d3d.per_draw_cb_data.textures[i].height = d3d.textures[d3d.current_texture_ids[i]].height; 604 | d3d.per_draw_cb_data.textures[i].linear_filtering = d3d.textures[d3d.current_texture_ids[i]].linear_filtering; 605 | textures_changed = true; 606 | #endif 607 | 608 | if (d3d.last_sampler_states[i].Get() != d3d.textures[d3d.current_texture_ids[i]].sampler_state.Get()) { 609 | d3d.last_sampler_states[i] = d3d.textures[d3d.current_texture_ids[i]].sampler_state.Get(); 610 | d3d.context->PSSetSamplers(i, 1, d3d.textures[d3d.current_texture_ids[i]].sampler_state.GetAddressOf()); 611 | } 612 | } 613 | } 614 | } 615 | 616 | // Set per-draw constant buffer 617 | 618 | if (textures_changed) { 619 | D3D11_MAPPED_SUBRESOURCE ms; 620 | ZeroMemory(&ms, sizeof(D3D11_MAPPED_SUBRESOURCE)); 621 | d3d.context->Map(d3d.per_draw_cb.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &ms); 622 | memcpy(ms.pData, &d3d.per_draw_cb_data, sizeof(PerDrawCB)); 623 | d3d.context->Unmap(d3d.per_draw_cb.Get(), 0); 624 | } 625 | 626 | // Set vertex buffer data 627 | 628 | D3D11_MAPPED_SUBRESOURCE ms; 629 | ZeroMemory(&ms, sizeof(D3D11_MAPPED_SUBRESOURCE)); 630 | d3d.context->Map(d3d.vertex_buffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &ms); 631 | memcpy(ms.pData, buf_vbo, buf_vbo_len * sizeof(float)); 632 | d3d.context->Unmap(d3d.vertex_buffer.Get(), 0); 633 | 634 | uint32_t stride = d3d.shader_program->num_floats * sizeof(float); 635 | uint32_t offset = 0; 636 | 637 | if (d3d.last_vertex_buffer_stride != stride) { 638 | d3d.last_vertex_buffer_stride = stride; 639 | d3d.context->IASetVertexBuffers(0, 1, d3d.vertex_buffer.GetAddressOf(), &stride, &offset); 640 | } 641 | 642 | if (d3d.last_shader_program != d3d.shader_program) { 643 | d3d.last_shader_program = d3d.shader_program; 644 | d3d.context->IASetInputLayout(d3d.shader_program->input_layout.Get()); 645 | d3d.context->VSSetShader(d3d.shader_program->vertex_shader.Get(), 0, 0); 646 | d3d.context->PSSetShader(d3d.shader_program->pixel_shader.Get(), 0, 0); 647 | 648 | if (d3d.last_blend_state.Get() != d3d.shader_program->blend_state.Get()) { 649 | d3d.last_blend_state = d3d.shader_program->blend_state.Get(); 650 | d3d.context->OMSetBlendState(d3d.shader_program->blend_state.Get(), 0, 0xFFFFFFFF); 651 | } 652 | } 653 | 654 | if (d3d.last_primitive_topology != D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST) { 655 | d3d.last_primitive_topology = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST; 656 | d3d.context->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); 657 | } 658 | 659 | d3d.context->Draw(buf_vbo_num_tris * 3, 0); 660 | } 661 | 662 | static void gfx_d3d11_on_resize(void) { 663 | create_render_target_views(true); 664 | } 665 | 666 | static void gfx_d3d11_start_frame(void) { 667 | // Set render targets 668 | 669 | d3d.context->OMSetRenderTargets(1, d3d.backbuffer_view.GetAddressOf(), d3d.depth_stencil_view.Get()); 670 | 671 | // Clear render targets 672 | 673 | const float clearColor[] = { 0.0f, 0.0f, 0.0f, 1.0f }; 674 | d3d.context->ClearRenderTargetView(d3d.backbuffer_view.Get(), clearColor); 675 | d3d.context->ClearDepthStencilView(d3d.depth_stencil_view.Get(), D3D11_CLEAR_DEPTH, 1.0f, 0); 676 | 677 | // Set per-frame constant buffer 678 | 679 | d3d.per_frame_cb_data.noise_frame++; 680 | if (d3d.per_frame_cb_data.noise_frame > 150) { 681 | // No high values, as noise starts to look ugly 682 | d3d.per_frame_cb_data.noise_frame = 0; 683 | } 684 | float aspect_ratio = (float) d3d.current_width / (float) d3d.current_height; 685 | d3d.per_frame_cb_data.noise_scale_x = 120 * aspect_ratio; // 120 = N64 height resolution (240) / 2 686 | d3d.per_frame_cb_data.noise_scale_y = 120; 687 | 688 | D3D11_MAPPED_SUBRESOURCE ms; 689 | ZeroMemory(&ms, sizeof(D3D11_MAPPED_SUBRESOURCE)); 690 | d3d.context->Map(d3d.per_frame_cb.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &ms); 691 | memcpy(ms.pData, &d3d.per_frame_cb_data, sizeof(PerFrameCB)); 692 | d3d.context->Unmap(d3d.per_frame_cb.Get(), 0); 693 | } 694 | 695 | static void gfx_d3d11_end_frame(void) { 696 | } 697 | 698 | static void gfx_d3d11_finish_render(void) { 699 | } 700 | 701 | } // namespace 702 | 703 | struct GfxRenderingAPI gfx_direct3d11_api = { 704 | gfx_d3d11_z_is_from_0_to_1, 705 | gfx_d3d11_unload_shader, 706 | gfx_d3d11_load_shader, 707 | gfx_d3d11_create_and_load_new_shader, 708 | gfx_d3d11_lookup_shader, 709 | gfx_d3d11_shader_get_info, 710 | gfx_d3d11_new_texture, 711 | gfx_d3d11_select_texture, 712 | gfx_d3d11_upload_texture, 713 | gfx_d3d11_set_sampler_parameters, 714 | gfx_d3d11_set_depth_test, 715 | gfx_d3d11_set_depth_mask, 716 | gfx_d3d11_set_zmode_decal, 717 | gfx_d3d11_set_viewport, 718 | gfx_d3d11_set_scissor, 719 | gfx_d3d11_set_use_alpha, 720 | gfx_d3d11_draw_triangles, 721 | gfx_d3d11_init, 722 | gfx_d3d11_on_resize, 723 | gfx_d3d11_start_frame, 724 | gfx_d3d11_end_frame, 725 | gfx_d3d11_finish_render 726 | }; 727 | 728 | #endif 729 | -------------------------------------------------------------------------------- /gfx_direct3d11.h: -------------------------------------------------------------------------------- 1 | #ifdef ENABLE_DX11 2 | 3 | #ifndef GFX_DIRECT3D11_H 4 | #define GFX_DIRECT3D11_H 5 | 6 | #include "gfx_rendering_api.h" 7 | 8 | extern struct GfxRenderingAPI gfx_direct3d11_api; 9 | 10 | #endif 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /gfx_direct3d12.cpp: -------------------------------------------------------------------------------- 1 | #ifdef ENABLE_DX12 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | // This is needed when compiling with MinGW, used in d3d12.h 16 | #define __in_ecount_opt(size) 17 | 18 | #include 19 | #include 20 | #include "dxsdk/d3d12.h" 21 | #include 22 | 23 | #include "gfx_direct3d12_guids.h" 24 | 25 | #include "dxsdk/d3dx12.h" 26 | 27 | #ifndef _LANGUAGE_C 28 | #define _LANGUAGE_C 29 | #endif 30 | #include 31 | 32 | #define DECLARE_GFX_DXGI_FUNCTIONS 33 | #include "gfx_dxgi.h" 34 | 35 | #include "gfx_cc.h" 36 | #include "gfx_window_manager_api.h" 37 | #include "gfx_rendering_api.h" 38 | #include "gfx_direct3d_common.h" 39 | 40 | #include "gfx_screen_config.h" 41 | 42 | #define DEBUG_D3D 0 43 | 44 | using namespace Microsoft::WRL; // For ComPtr 45 | 46 | namespace { 47 | 48 | struct ShaderProgramD3D12 { 49 | uint32_t shader_id; 50 | uint8_t num_inputs; 51 | bool used_textures[2]; 52 | uint8_t num_floats; 53 | uint8_t num_attribs; 54 | 55 | ComPtr vertex_shader; 56 | ComPtr pixel_shader; 57 | ComPtr root_signature; 58 | }; 59 | 60 | struct PipelineDesc { 61 | uint32_t shader_id; 62 | bool depth_test; 63 | bool depth_mask; 64 | bool zmode_decal; 65 | bool _padding; 66 | 67 | bool operator==(const PipelineDesc& o) const { 68 | return memcmp(this, &o, sizeof(*this)) == 0; 69 | } 70 | 71 | bool operator<(const PipelineDesc& o) const { 72 | return memcmp(this, &o, sizeof(*this)) < 0; 73 | } 74 | }; 75 | 76 | struct TextureHeap { 77 | ComPtr heap; 78 | std::vector free_list; 79 | }; 80 | 81 | struct TextureData { 82 | ComPtr resource; 83 | struct TextureHeap *heap; 84 | uint8_t heap_offset; 85 | 86 | uint64_t last_frame_counter; 87 | uint32_t descriptor_index; 88 | int sampler_parameters; 89 | }; 90 | 91 | struct NoiseCB { 92 | uint32_t noise_frame; 93 | float noise_scale_x; 94 | float noise_scale_y; 95 | uint32_t padding; 96 | }; 97 | 98 | static struct { 99 | HMODULE d3d12_module; 100 | PFN_D3D12_CREATE_DEVICE D3D12CreateDevice; 101 | PFN_D3D12_GET_DEBUG_INTERFACE D3D12GetDebugInterface; 102 | 103 | HMODULE d3dcompiler_module; 104 | pD3DCompile D3DCompile; 105 | 106 | struct ShaderProgramD3D12 shader_program_pool[64]; 107 | uint8_t shader_program_pool_size; 108 | 109 | uint32_t current_width, current_height; 110 | 111 | ComPtr device; 112 | ComPtr command_queue; 113 | ComPtr copy_command_queue; 114 | ComPtr swap_chain; 115 | ComPtr rtv_heap; 116 | UINT rtv_descriptor_size; 117 | ComPtr render_targets[2]; 118 | ComPtr command_allocator; 119 | ComPtr copy_command_allocator; 120 | ComPtr command_list; 121 | ComPtr copy_command_list; 122 | ComPtr dsv_heap; 123 | ComPtr depth_stencil_buffer; 124 | ComPtr srv_heap; 125 | UINT srv_descriptor_size; 126 | ComPtr sampler_heap; 127 | UINT sampler_descriptor_size; 128 | 129 | std::map, std::list> texture_heaps; 130 | 131 | std::map>> upload_heaps; 132 | std::vector>> upload_heaps_in_flight; 133 | ComPtr copy_fence; 134 | uint64_t copy_fence_value; 135 | 136 | std::vector textures; 137 | int current_tile; 138 | uint32_t current_texture_ids[2]; 139 | uint32_t srv_pos; 140 | 141 | int frame_index; 142 | ComPtr fence; 143 | HANDLE fence_event; 144 | 145 | uint64_t frame_counter; 146 | 147 | ComPtr noise_cb; 148 | void *mapped_noise_cb_address; 149 | struct NoiseCB noise_cb_data; 150 | 151 | ComPtr vertex_buffer; 152 | void *mapped_vbuf_address; 153 | int vbuf_pos; 154 | 155 | std::vector> resources_to_clean_at_end_of_frame; 156 | std::vector> texture_heap_allocations_to_reclaim_at_end_of_frame; 157 | 158 | std::map> pipeline_states; 159 | bool must_reload_pipeline; 160 | 161 | // Current state: 162 | ID3D12PipelineState *pipeline_state; 163 | struct ShaderProgramD3D12 *shader_program; 164 | bool depth_test; 165 | bool depth_mask; 166 | bool zmode_decal; 167 | 168 | CD3DX12_VIEWPORT viewport; 169 | CD3DX12_RECT scissor; 170 | } d3d; 171 | 172 | static int texture_uploads = 0; 173 | static int max_texture_uploads; 174 | 175 | static D3D12_CPU_DESCRIPTOR_HANDLE get_cpu_descriptor_handle(ComPtr& heap) { 176 | #ifdef __MINGW32__ 177 | // We would like to do this: 178 | // D3D12_CPU_DESCRIPTOR_HANDLE handle = heap->GetCPUDescriptorHandleForHeapStart(); 179 | // but MinGW64 doesn't follow the calling conventions of VC++ for some reason. 180 | // Per MS documentation "User-defined types can be returned by value from global functions and static member functions"... 181 | // "Otherwise, the caller assumes the responsibility of allocating memory and passing a pointer for the return value as the first argument". 182 | // The method here is a non-static member function, and hence we need to pass the address to the return value as a parameter. 183 | // MinGW32 has the same issue. 184 | auto fn = heap->GetCPUDescriptorHandleForHeapStart; 185 | void (STDMETHODCALLTYPE ID3D12DescriptorHeap::*fun)(D3D12_CPU_DESCRIPTOR_HANDLE *out) = (void (STDMETHODCALLTYPE ID3D12DescriptorHeap::*)(D3D12_CPU_DESCRIPTOR_HANDLE *out))fn; 186 | D3D12_CPU_DESCRIPTOR_HANDLE handle; 187 | (heap.Get()->*fun)(&handle); 188 | return handle; 189 | #else 190 | return heap->GetCPUDescriptorHandleForHeapStart(); 191 | #endif 192 | } 193 | 194 | static D3D12_GPU_DESCRIPTOR_HANDLE get_gpu_descriptor_handle(ComPtr& heap) { 195 | #ifdef __MINGW32__ 196 | // See get_cpu_descriptor_handle 197 | auto fn = heap->GetGPUDescriptorHandleForHeapStart; 198 | void (STDMETHODCALLTYPE ID3D12DescriptorHeap::*fun)(D3D12_GPU_DESCRIPTOR_HANDLE *out) = (void (STDMETHODCALLTYPE ID3D12DescriptorHeap::*)(D3D12_GPU_DESCRIPTOR_HANDLE *out))fn; 199 | D3D12_GPU_DESCRIPTOR_HANDLE handle; 200 | (heap.Get()->*fun)(&handle); 201 | return handle; 202 | #else 203 | return heap->GetGPUDescriptorHandleForHeapStart(); 204 | #endif 205 | } 206 | 207 | static D3D12_RESOURCE_ALLOCATION_INFO get_resource_allocation_info(const D3D12_RESOURCE_DESC *resource_desc) { 208 | #ifdef __MINGW32__ 209 | // See get_cpu_descriptor_handle 210 | auto fn = d3d.device->GetResourceAllocationInfo; 211 | void (STDMETHODCALLTYPE ID3D12Device::*fun)(D3D12_RESOURCE_ALLOCATION_INFO *out, UINT visibleMask, UINT numResourceDescs, const D3D12_RESOURCE_DESC *pResourceDescs) = 212 | (void (STDMETHODCALLTYPE ID3D12Device::*)(D3D12_RESOURCE_ALLOCATION_INFO *out, UINT visibleMask, UINT numResourceDescs, const D3D12_RESOURCE_DESC *pResourceDescs))fn; 213 | D3D12_RESOURCE_ALLOCATION_INFO out; 214 | (d3d.device.Get()->*fun)(&out, 0, 1, resource_desc); 215 | return out; 216 | #else 217 | return d3d.device->GetResourceAllocationInfo(0, 1, resource_desc); 218 | #endif 219 | } 220 | 221 | static bool gfx_direct3d12_z_is_from_0_to_1(void) { 222 | return true; 223 | } 224 | 225 | static void gfx_direct3d12_unload_shader(struct ShaderProgram *old_prg) { 226 | } 227 | 228 | static void gfx_direct3d12_load_shader(struct ShaderProgram *new_prg) { 229 | d3d.shader_program = (struct ShaderProgramD3D12 *)new_prg; 230 | d3d.must_reload_pipeline = true; 231 | } 232 | 233 | static struct ShaderProgram *gfx_direct3d12_create_and_load_new_shader(uint32_t shader_id) { 234 | /*static FILE *fp; 235 | if (!fp) { 236 | fp = fopen("shaders.txt", "w"); 237 | } 238 | fprintf(fp, "0x%08x\n", shader_id); 239 | fflush(fp);*/ 240 | 241 | struct ShaderProgramD3D12 *prg = &d3d.shader_program_pool[d3d.shader_program_pool_size++]; 242 | 243 | CCFeatures cc_features; 244 | gfx_cc_get_features(shader_id, &cc_features); 245 | 246 | char buf[2048]; 247 | size_t len, num_floats; 248 | 249 | gfx_direct3d_common_build_shader(buf, len, num_floats, cc_features, true, false); 250 | 251 | //fwrite(buf, 1, len, stdout); 252 | 253 | ThrowIfFailed(d3d.D3DCompile(buf, len, nullptr, nullptr, nullptr, "VSMain", "vs_5_1", D3DCOMPILE_OPTIMIZATION_LEVEL3, 0, &prg->vertex_shader, nullptr)); 254 | ThrowIfFailed(d3d.D3DCompile(buf, len, nullptr, nullptr, nullptr, "PSMain", "ps_5_1", D3DCOMPILE_OPTIMIZATION_LEVEL3, 0, &prg->pixel_shader, nullptr)); 255 | 256 | ThrowIfFailed(d3d.device->CreateRootSignature(0, prg->pixel_shader->GetBufferPointer(), prg->pixel_shader->GetBufferSize(), IID_PPV_ARGS(&prg->root_signature))); 257 | 258 | prg->shader_id = shader_id; 259 | prg->num_inputs = cc_features.num_inputs; 260 | prg->used_textures[0] = cc_features.used_textures[0]; 261 | prg->used_textures[1] = cc_features.used_textures[1]; 262 | prg->num_floats = num_floats; 263 | //prg->num_attribs = cnt; 264 | 265 | d3d.must_reload_pipeline = true; 266 | return (struct ShaderProgram *)(d3d.shader_program = prg); 267 | } 268 | 269 | static struct ShaderProgram *gfx_direct3d12_lookup_shader(uint32_t shader_id) { 270 | for (size_t i = 0; i < d3d.shader_program_pool_size; i++) { 271 | if (d3d.shader_program_pool[i].shader_id == shader_id) { 272 | return (struct ShaderProgram *)&d3d.shader_program_pool[i]; 273 | } 274 | } 275 | return nullptr; 276 | } 277 | 278 | static void gfx_direct3d12_shader_get_info(struct ShaderProgram *prg, uint8_t *num_inputs, bool used_textures[2]) { 279 | struct ShaderProgramD3D12 *p = (struct ShaderProgramD3D12 *)prg; 280 | 281 | *num_inputs = p->num_inputs; 282 | used_textures[0] = p->used_textures[0]; 283 | used_textures[1] = p->used_textures[1]; 284 | } 285 | 286 | static uint32_t gfx_direct3d12_new_texture(void) { 287 | d3d.textures.resize(d3d.textures.size() + 1); 288 | return (uint32_t)(d3d.textures.size() - 1); 289 | } 290 | 291 | static void gfx_direct3d12_select_texture(int tile, uint32_t texture_id) { 292 | d3d.current_tile = tile; 293 | d3d.current_texture_ids[tile] = texture_id; 294 | } 295 | 296 | static void gfx_direct3d12_upload_texture(const uint8_t *rgba32_buf, int width, int height) { 297 | texture_uploads++; 298 | 299 | ComPtr texture_resource; 300 | 301 | // Describe and create a Texture2D. 302 | D3D12_RESOURCE_DESC texture_desc = {}; 303 | texture_desc.MipLevels = 1; 304 | texture_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; 305 | texture_desc.Width = width; 306 | texture_desc.Height = height; 307 | texture_desc.Flags = D3D12_RESOURCE_FLAG_NONE; 308 | texture_desc.DepthOrArraySize = 1; 309 | texture_desc.SampleDesc.Count = 1; 310 | texture_desc.SampleDesc.Quality = 0; 311 | texture_desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; 312 | texture_desc.Alignment = ((width + 31) / 32) * ((height + 31) / 32) > 16 ? 0 : D3D12_SMALL_RESOURCE_PLACEMENT_ALIGNMENT; 313 | 314 | D3D12_RESOURCE_ALLOCATION_INFO alloc_info = get_resource_allocation_info(&texture_desc); 315 | 316 | std::list& heaps = d3d.texture_heaps[std::pair(alloc_info.SizeInBytes, alloc_info.Alignment)]; 317 | 318 | struct TextureHeap *found_heap = nullptr; 319 | for (struct TextureHeap& heap : heaps) { 320 | if (!heap.free_list.empty()) { 321 | found_heap = &heap; 322 | } 323 | } 324 | if (found_heap == nullptr) { 325 | heaps.resize(heaps.size() + 1); 326 | found_heap = &heaps.back(); 327 | 328 | // In case of HD textures, make sure too much memory isn't wasted 329 | int textures_per_heap = 524288 / alloc_info.SizeInBytes; 330 | if (textures_per_heap < 1) { 331 | textures_per_heap = 1; 332 | } else if (textures_per_heap > 64) { 333 | textures_per_heap = 64; 334 | } 335 | 336 | D3D12_HEAP_DESC heap_desc = {}; 337 | heap_desc.SizeInBytes = alloc_info.SizeInBytes * textures_per_heap; 338 | if (alloc_info.Alignment == D3D12_SMALL_RESOURCE_PLACEMENT_ALIGNMENT) { 339 | heap_desc.Alignment = D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT; 340 | } else { 341 | heap_desc.Alignment = alloc_info.Alignment; 342 | } 343 | heap_desc.Properties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; 344 | heap_desc.Properties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; 345 | heap_desc.Properties.Type = D3D12_HEAP_TYPE_DEFAULT; 346 | heap_desc.Flags = D3D12_HEAP_FLAG_ALLOW_ONLY_NON_RT_DS_TEXTURES; 347 | ThrowIfFailed(d3d.device->CreateHeap(&heap_desc, IID_PPV_ARGS(&found_heap->heap))); 348 | for (int i = 0; i < textures_per_heap; i++) { 349 | found_heap->free_list.push_back(i); 350 | } 351 | } 352 | 353 | uint8_t heap_offset = found_heap->free_list.back(); 354 | found_heap->free_list.pop_back(); 355 | ThrowIfFailed(d3d.device->CreatePlacedResource(found_heap->heap.Get(), heap_offset * alloc_info.SizeInBytes, &texture_desc, D3D12_RESOURCE_STATE_COPY_DEST, nullptr, IID_PPV_ARGS(&texture_resource))); 356 | 357 | D3D12_PLACED_SUBRESOURCE_FOOTPRINT layout; 358 | UINT num_rows; 359 | UINT64 row_size_in_bytes; 360 | UINT64 upload_buffer_size; 361 | d3d.device->GetCopyableFootprints(&texture_desc, 0, 1, 0, &layout, &num_rows, &row_size_in_bytes, &upload_buffer_size); 362 | 363 | std::vector>& upload_heaps = d3d.upload_heaps[upload_buffer_size]; 364 | ComPtr upload_heap; 365 | if (upload_heaps.empty()) { 366 | CD3DX12_HEAP_PROPERTIES hp(D3D12_HEAP_TYPE_UPLOAD); 367 | CD3DX12_RESOURCE_DESC rdb = CD3DX12_RESOURCE_DESC::Buffer(upload_buffer_size); 368 | ThrowIfFailed(d3d.device->CreateCommittedResource( 369 | &hp, 370 | D3D12_HEAP_FLAG_NONE, 371 | &rdb, 372 | D3D12_RESOURCE_STATE_GENERIC_READ, 373 | nullptr, 374 | IID_PPV_ARGS(&upload_heap))); 375 | } else { 376 | upload_heap = upload_heaps.back(); 377 | upload_heaps.pop_back(); 378 | } 379 | 380 | { 381 | D3D12_SUBRESOURCE_DATA texture_data = {}; 382 | texture_data.pData = rgba32_buf; 383 | texture_data.RowPitch = width * 4; // RGBA 384 | texture_data.SlicePitch = texture_data.RowPitch * height; 385 | 386 | void *data; 387 | upload_heap->Map(0, nullptr, &data); 388 | D3D12_MEMCPY_DEST dest_data = { (uint8_t *)data + layout.Offset, layout.Footprint.RowPitch, SIZE_T(layout.Footprint.RowPitch) * SIZE_T(num_rows) }; 389 | MemcpySubresource(&dest_data, &texture_data, static_cast(row_size_in_bytes), num_rows, layout.Footprint.Depth); 390 | upload_heap->Unmap(0, nullptr); 391 | 392 | CD3DX12_TEXTURE_COPY_LOCATION dst(texture_resource.Get(), 0); 393 | CD3DX12_TEXTURE_COPY_LOCATION src(upload_heap.Get(), layout); 394 | d3d.copy_command_list->CopyTextureRegion(&dst, 0, 0, 0, &src, nullptr); 395 | } 396 | 397 | CD3DX12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition(texture_resource.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); 398 | d3d.command_list->ResourceBarrier(1, &barrier); 399 | 400 | d3d.upload_heaps_in_flight.push_back(std::make_pair((size_t)upload_buffer_size, std::move(upload_heap))); 401 | 402 | struct TextureData& td = d3d.textures[d3d.current_texture_ids[d3d.current_tile]]; 403 | if (td.resource.Get() != nullptr) { 404 | d3d.resources_to_clean_at_end_of_frame.push_back(std::move(td.resource)); 405 | d3d.texture_heap_allocations_to_reclaim_at_end_of_frame.push_back(std::make_pair(td.heap, td.heap_offset)); 406 | td.last_frame_counter = 0; 407 | } 408 | td.resource = std::move(texture_resource); 409 | td.heap = found_heap; 410 | td.heap_offset = heap_offset; 411 | } 412 | 413 | static int gfx_cm_to_index(uint32_t val) { 414 | if (val & G_TX_CLAMP) { 415 | return 2; 416 | } 417 | return (val & G_TX_MIRROR) ? 1 : 0; 418 | } 419 | 420 | static void gfx_direct3d12_set_sampler_parameters(int tile, bool linear_filter, uint32_t cms, uint32_t cmt) { 421 | d3d.textures[d3d.current_texture_ids[tile]].sampler_parameters = linear_filter * 9 + gfx_cm_to_index(cms) * 3 + gfx_cm_to_index(cmt); 422 | } 423 | 424 | static void gfx_direct3d12_set_depth_test(bool depth_test) { 425 | d3d.depth_test = depth_test; 426 | d3d.must_reload_pipeline = true; 427 | } 428 | 429 | static void gfx_direct3d12_set_depth_mask(bool z_upd) { 430 | d3d.depth_mask = z_upd; 431 | d3d.must_reload_pipeline = true; 432 | } 433 | 434 | static void gfx_direct3d12_set_zmode_decal(bool zmode_decal) { 435 | d3d.zmode_decal = zmode_decal; 436 | d3d.must_reload_pipeline = true; 437 | } 438 | 439 | static void gfx_direct3d12_set_viewport(int x, int y, int width, int height) { 440 | d3d.viewport = CD3DX12_VIEWPORT(x, d3d.current_height - y - height, width, height); 441 | } 442 | 443 | static void gfx_direct3d12_set_scissor(int x, int y, int width, int height) { 444 | d3d.scissor = CD3DX12_RECT(x, d3d.current_height - y - height, x + width, d3d.current_height - y); 445 | } 446 | 447 | static void gfx_direct3d12_set_use_alpha(bool use_alpha) { 448 | // Already part of the pipeline state from shader info 449 | } 450 | 451 | static void gfx_direct3d12_draw_triangles(float buf_vbo[], size_t buf_vbo_len, size_t buf_vbo_num_tris) { 452 | struct ShaderProgramD3D12 *prg = d3d.shader_program; 453 | 454 | if (d3d.must_reload_pipeline) { 455 | ComPtr& pipeline_state = d3d.pipeline_states[PipelineDesc{ 456 | prg->shader_id, 457 | d3d.depth_test, 458 | d3d.depth_mask, 459 | d3d.zmode_decal, 460 | 0 461 | }]; 462 | if (pipeline_state.Get() == nullptr) { 463 | D3D12_INPUT_ELEMENT_DESC ied[7] = { 464 | {"POSITION", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 } 465 | }; 466 | uint32_t ied_pos = 1; 467 | if (prg->used_textures[0] || prg->used_textures[1]) { 468 | 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}; 469 | } 470 | if (prg->shader_id & SHADER_OPT_FOG) { 471 | 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}; 472 | } 473 | for (int i = 0; i < prg->num_inputs; i++) { 474 | DXGI_FORMAT format = (prg->shader_id & SHADER_OPT_ALPHA) ? DXGI_FORMAT_R32G32B32A32_FLOAT : DXGI_FORMAT_R32G32B32_FLOAT; 475 | ied[ied_pos++] = D3D12_INPUT_ELEMENT_DESC{"INPUT", (UINT)i, format, 0, D3D12_APPEND_ALIGNED_ELEMENT, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}; 476 | } 477 | 478 | D3D12_GRAPHICS_PIPELINE_STATE_DESC desc = {}; 479 | desc.InputLayout = { ied, ied_pos }; 480 | desc.pRootSignature = prg->root_signature.Get(); 481 | desc.VS = CD3DX12_SHADER_BYTECODE(prg->vertex_shader.Get()); 482 | desc.PS = CD3DX12_SHADER_BYTECODE(prg->pixel_shader.Get()); 483 | desc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT); 484 | if (d3d.zmode_decal) { 485 | desc.RasterizerState.SlopeScaledDepthBias = -2.0f; 486 | } 487 | desc.RasterizerState.CullMode = D3D12_CULL_MODE_NONE; 488 | if (prg->shader_id & SHADER_OPT_ALPHA) { 489 | D3D12_BLEND_DESC bd = {}; 490 | bd.AlphaToCoverageEnable = FALSE; 491 | bd.IndependentBlendEnable = FALSE; 492 | static const D3D12_RENDER_TARGET_BLEND_DESC default_rtbd = { 493 | TRUE, FALSE, 494 | D3D12_BLEND_SRC_ALPHA, D3D12_BLEND_INV_SRC_ALPHA, D3D12_BLEND_OP_ADD, 495 | D3D12_BLEND_ONE, D3D12_BLEND_INV_SRC_ALPHA, D3D12_BLEND_OP_ADD, 496 | D3D12_LOGIC_OP_NOOP, 497 | D3D12_COLOR_WRITE_ENABLE_ALL 498 | }; 499 | for (UINT i = 0; i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; i++) { 500 | bd.RenderTarget[i] = default_rtbd; 501 | } 502 | desc.BlendState = bd; 503 | } else { 504 | desc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT); 505 | } 506 | desc.DepthStencilState.DepthEnable = d3d.depth_test; 507 | desc.DepthStencilState.DepthWriteMask = d3d.depth_mask ? D3D12_DEPTH_WRITE_MASK_ALL : D3D12_DEPTH_WRITE_MASK_ZERO; 508 | desc.DepthStencilState.DepthFunc = D3D12_COMPARISON_FUNC_LESS_EQUAL; 509 | desc.DSVFormat = d3d.depth_test ? DXGI_FORMAT_D32_FLOAT : DXGI_FORMAT_UNKNOWN; 510 | desc.SampleMask = UINT_MAX; 511 | desc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; 512 | desc.NumRenderTargets = 1; 513 | desc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM; 514 | desc.SampleDesc.Count = 1; 515 | ThrowIfFailed(d3d.device->CreateGraphicsPipelineState(&desc, IID_PPV_ARGS(&pipeline_state))); 516 | } 517 | d3d.pipeline_state = pipeline_state.Get(); 518 | d3d.must_reload_pipeline = false; 519 | } 520 | 521 | d3d.command_list->SetGraphicsRootSignature(prg->root_signature.Get()); 522 | d3d.command_list->SetPipelineState(d3d.pipeline_state); 523 | 524 | ID3D12DescriptorHeap *heaps[] = { d3d.srv_heap.Get(), d3d.sampler_heap.Get() }; 525 | d3d.command_list->SetDescriptorHeaps(2, heaps); 526 | 527 | int root_param_index = 0; 528 | 529 | if ((prg->shader_id & (SHADER_OPT_ALPHA | SHADER_OPT_NOISE)) == (SHADER_OPT_ALPHA | SHADER_OPT_NOISE)) { 530 | d3d.command_list->SetGraphicsRootConstantBufferView(root_param_index++, d3d.noise_cb->GetGPUVirtualAddress()); 531 | } 532 | 533 | for (int i = 0; i < 2; i++) { 534 | if (prg->used_textures[i]) { 535 | struct TextureData& td = d3d.textures[d3d.current_texture_ids[i]]; 536 | if (td.last_frame_counter != d3d.frame_counter) { 537 | td.descriptor_index = d3d.srv_pos; 538 | td.last_frame_counter = d3d.frame_counter; 539 | 540 | D3D12_SHADER_RESOURCE_VIEW_DESC srv_desc = {}; 541 | srv_desc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; 542 | srv_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; 543 | srv_desc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; 544 | srv_desc.Texture2D.MipLevels = 1; 545 | 546 | CD3DX12_CPU_DESCRIPTOR_HANDLE srv_handle(get_cpu_descriptor_handle(d3d.srv_heap), d3d.srv_pos++, d3d.srv_descriptor_size); 547 | d3d.device->CreateShaderResourceView(td.resource.Get(), &srv_desc, srv_handle); 548 | } 549 | 550 | CD3DX12_GPU_DESCRIPTOR_HANDLE srv_gpu_handle(get_gpu_descriptor_handle(d3d.srv_heap), td.descriptor_index, d3d.srv_descriptor_size); 551 | d3d.command_list->SetGraphicsRootDescriptorTable(root_param_index++, srv_gpu_handle); 552 | 553 | CD3DX12_GPU_DESCRIPTOR_HANDLE sampler_gpu_handle(get_gpu_descriptor_handle(d3d.sampler_heap), td.sampler_parameters, d3d.sampler_descriptor_size); 554 | d3d.command_list->SetGraphicsRootDescriptorTable(root_param_index++, sampler_gpu_handle); 555 | } 556 | } 557 | 558 | CD3DX12_CPU_DESCRIPTOR_HANDLE rtv_handle(get_cpu_descriptor_handle(d3d.rtv_heap), d3d.frame_index, d3d.rtv_descriptor_size); 559 | D3D12_CPU_DESCRIPTOR_HANDLE dsv_handle = get_cpu_descriptor_handle(d3d.dsv_heap); 560 | d3d.command_list->OMSetRenderTargets(1, &rtv_handle, FALSE, &dsv_handle); 561 | 562 | d3d.command_list->RSSetViewports(1, &d3d.viewport); 563 | d3d.command_list->RSSetScissorRects(1, &d3d.scissor); 564 | 565 | int current_pos = d3d.vbuf_pos; 566 | memcpy((uint8_t *)d3d.mapped_vbuf_address + current_pos, buf_vbo, buf_vbo_len * sizeof(float)); 567 | d3d.vbuf_pos += buf_vbo_len * sizeof(float); 568 | static int maxpos; 569 | if (d3d.vbuf_pos > maxpos) { 570 | maxpos = d3d.vbuf_pos; 571 | //printf("NEW MAXPOS: %d\n", maxpos); 572 | } 573 | 574 | D3D12_VERTEX_BUFFER_VIEW vertex_buffer_view; 575 | vertex_buffer_view.BufferLocation = d3d.vertex_buffer->GetGPUVirtualAddress() + current_pos; 576 | vertex_buffer_view.StrideInBytes = buf_vbo_len / (3 * buf_vbo_num_tris) * sizeof(float); 577 | vertex_buffer_view.SizeInBytes = buf_vbo_len * sizeof(float); 578 | 579 | d3d.command_list->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); 580 | d3d.command_list->IASetVertexBuffers(0, 1, &vertex_buffer_view); 581 | d3d.command_list->DrawInstanced(3 * buf_vbo_num_tris, 1, 0, 0); 582 | } 583 | 584 | static void gfx_direct3d12_start_frame(void) { 585 | ++d3d.frame_counter; 586 | d3d.srv_pos = 0; 587 | texture_uploads = 0; 588 | ThrowIfFailed(d3d.command_allocator->Reset()); 589 | ThrowIfFailed(d3d.command_list->Reset(d3d.command_allocator.Get(), nullptr)); 590 | 591 | CD3DX12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition( 592 | d3d.render_targets[d3d.frame_index].Get(), 593 | D3D12_RESOURCE_STATE_PRESENT, 594 | D3D12_RESOURCE_STATE_RENDER_TARGET); 595 | d3d.command_list->ResourceBarrier(1, &barrier); 596 | 597 | CD3DX12_CPU_DESCRIPTOR_HANDLE rtv_handle(get_cpu_descriptor_handle(d3d.rtv_heap), d3d.frame_index, d3d.rtv_descriptor_size); 598 | D3D12_CPU_DESCRIPTOR_HANDLE dsv_handle = get_cpu_descriptor_handle(d3d.dsv_heap); 599 | d3d.command_list->OMSetRenderTargets(1, &rtv_handle, FALSE, &dsv_handle); 600 | 601 | static unsigned char c; 602 | const float clear_color[] = { 0.0f, 0.0f, 0.0f, 1.0f }; 603 | d3d.command_list->ClearRenderTargetView(rtv_handle, clear_color, 0, nullptr); 604 | d3d.command_list->ClearDepthStencilView(dsv_handle, D3D12_CLEAR_FLAG_DEPTH, 1.0f, 0, 0, nullptr); 605 | 606 | d3d.noise_cb_data.noise_frame++; 607 | if (d3d.noise_cb_data.noise_frame > 150) { 608 | // No high values, as noise starts to look ugly 609 | d3d.noise_cb_data.noise_frame = 0; 610 | } 611 | float aspect_ratio = (float) d3d.current_width / (float) d3d.current_height; 612 | d3d.noise_cb_data.noise_scale_x = 120 * aspect_ratio; // 120 = N64 height resolution (240) / 2 613 | d3d.noise_cb_data.noise_scale_y = 120; 614 | memcpy(d3d.mapped_noise_cb_address, &d3d.noise_cb_data, sizeof(struct NoiseCB)); 615 | 616 | d3d.vbuf_pos = 0; 617 | } 618 | 619 | static void create_render_target_views(void) { 620 | D3D12_CPU_DESCRIPTOR_HANDLE rtv_handle = get_cpu_descriptor_handle(d3d.rtv_heap); 621 | for (UINT i = 0; i < 2; i++) { 622 | ThrowIfFailed(d3d.swap_chain->GetBuffer(i, IID_ID3D12Resource, (void **)&d3d.render_targets[i])); 623 | d3d.device->CreateRenderTargetView(d3d.render_targets[i].Get(), nullptr, rtv_handle); 624 | rtv_handle.ptr += d3d.rtv_descriptor_size; 625 | } 626 | } 627 | 628 | static void create_depth_buffer(void) { 629 | DXGI_SWAP_CHAIN_DESC1 desc1; 630 | ThrowIfFailed(d3d.swap_chain->GetDesc1(&desc1)); 631 | UINT width = desc1.Width; 632 | UINT height = desc1.Height; 633 | 634 | d3d.current_width = width; 635 | d3d.current_height = height; 636 | 637 | D3D12_DEPTH_STENCIL_VIEW_DESC dsv_desc = {}; 638 | dsv_desc.Format = DXGI_FORMAT_D32_FLOAT; 639 | dsv_desc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D; 640 | dsv_desc.Flags = D3D12_DSV_FLAG_NONE; 641 | 642 | D3D12_CLEAR_VALUE depth_optimized_cv = {}; 643 | depth_optimized_cv.Format = DXGI_FORMAT_D32_FLOAT; 644 | depth_optimized_cv.DepthStencil.Depth = 1.0f; 645 | 646 | D3D12_HEAP_PROPERTIES hp = {}; 647 | hp.Type = D3D12_HEAP_TYPE_DEFAULT; 648 | hp.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; 649 | hp.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; 650 | hp.CreationNodeMask = 1; 651 | hp.VisibleNodeMask = 1; 652 | 653 | D3D12_RESOURCE_DESC rd = {}; 654 | rd.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; 655 | rd.Alignment = 0; 656 | rd.Width = width; 657 | rd.Height = height; 658 | rd.DepthOrArraySize = 1; 659 | rd.MipLevels = 0; 660 | rd.Format = DXGI_FORMAT_D32_FLOAT; 661 | rd.SampleDesc.Count = 1; 662 | rd.SampleDesc.Quality = 0; 663 | rd.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL; 664 | rd.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; 665 | ThrowIfFailed(d3d.device->CreateCommittedResource(&hp, D3D12_HEAP_FLAG_NONE, &rd, D3D12_RESOURCE_STATE_DEPTH_WRITE, &depth_optimized_cv, IID_PPV_ARGS(&d3d.depth_stencil_buffer))); 666 | 667 | d3d.device->CreateDepthStencilView(d3d.depth_stencil_buffer.Get(), &dsv_desc, get_cpu_descriptor_handle(d3d.dsv_heap)); 668 | } 669 | 670 | static void gfx_direct3d12_on_resize(void) { 671 | if (d3d.render_targets[0].Get() != nullptr) { 672 | d3d.render_targets[0].Reset(); 673 | d3d.render_targets[1].Reset(); 674 | ThrowIfFailed(d3d.swap_chain->ResizeBuffers(0, 0, 0, DXGI_FORMAT_UNKNOWN, DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT)); 675 | d3d.frame_index = d3d.swap_chain->GetCurrentBackBufferIndex(); 676 | create_render_target_views(); 677 | create_depth_buffer(); 678 | } 679 | } 680 | 681 | static void gfx_direct3d12_init(void ) { 682 | // Load d3d12.dll 683 | d3d.d3d12_module = LoadLibraryW(L"d3d12.dll"); 684 | if (d3d.d3d12_module == nullptr) { 685 | ThrowIfFailed(HRESULT_FROM_WIN32(GetLastError()), gfx_dxgi_get_h_wnd(), "d3d12.dll not found"); 686 | } 687 | d3d.D3D12CreateDevice = (PFN_D3D12_CREATE_DEVICE)GetProcAddress(d3d.d3d12_module, "D3D12CreateDevice"); 688 | #if DEBUG_D3D 689 | d3d.D3D12GetDebugInterface = (PFN_D3D12_GET_DEBUG_INTERFACE)GetProcAddress(d3d.d3d12_module, "D3D12GetDebugInterface"); 690 | #endif 691 | 692 | // Load D3DCompiler_47.dll 693 | d3d.d3dcompiler_module = LoadLibraryW(L"D3DCompiler_47.dll"); 694 | if (d3d.d3dcompiler_module == nullptr) { 695 | ThrowIfFailed(HRESULT_FROM_WIN32(GetLastError()), gfx_dxgi_get_h_wnd(), "D3DCompiler_47.dll not found"); 696 | } 697 | d3d.D3DCompile = (pD3DCompile)GetProcAddress(d3d.d3dcompiler_module, "D3DCompile"); 698 | 699 | // Create device 700 | { 701 | UINT debug_flags = 0; 702 | #if DEBUG_D3D 703 | ComPtr debug_controller; 704 | if (SUCCEEDED(d3d.D3D12GetDebugInterface(IID_PPV_ARGS(&debug_controller)))) { 705 | debug_controller->EnableDebugLayer(); 706 | debug_flags |= DXGI_CREATE_FACTORY_DEBUG; 707 | } 708 | #endif 709 | 710 | gfx_dxgi_create_factory_and_device(DEBUG_D3D, 12, [](IDXGIAdapter1 *adapter, bool test_only) { 711 | HRESULT res = d3d.D3D12CreateDevice( 712 | adapter, 713 | D3D_FEATURE_LEVEL_11_0, 714 | IID_ID3D12Device, 715 | test_only ? nullptr : IID_PPV_ARGS_Helper(&d3d.device)); 716 | 717 | if (test_only) { 718 | return SUCCEEDED(res); 719 | } else { 720 | ThrowIfFailed(res, gfx_dxgi_get_h_wnd(), "Failed to create D3D12 device."); 721 | return true; 722 | } 723 | }); 724 | } 725 | 726 | // Create command queues 727 | { 728 | D3D12_COMMAND_QUEUE_DESC queue_desc = {}; 729 | queue_desc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; 730 | queue_desc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; 731 | ThrowIfFailed(d3d.device->CreateCommandQueue(&queue_desc, IID_PPV_ARGS(&d3d.command_queue))); 732 | } 733 | { 734 | D3D12_COMMAND_QUEUE_DESC queue_desc = {}; 735 | queue_desc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; 736 | queue_desc.Type = D3D12_COMMAND_LIST_TYPE_COPY; 737 | ThrowIfFailed(d3d.device->CreateCommandQueue(&queue_desc, IID_PPV_ARGS(&d3d.copy_command_queue))); 738 | } 739 | 740 | // Create swap chain 741 | { 742 | ComPtr swap_chain1 = gfx_dxgi_create_swap_chain(d3d.command_queue.Get()); 743 | ThrowIfFailed(swap_chain1->QueryInterface(__uuidof(IDXGISwapChain3), &d3d.swap_chain)); 744 | d3d.frame_index = d3d.swap_chain->GetCurrentBackBufferIndex(); 745 | } 746 | 747 | // Create render target views 748 | { 749 | D3D12_DESCRIPTOR_HEAP_DESC rtv_heap_desc = {}; 750 | rtv_heap_desc.NumDescriptors = 2; 751 | rtv_heap_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV; 752 | rtv_heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; 753 | ThrowIfFailed(d3d.device->CreateDescriptorHeap(&rtv_heap_desc, IID_PPV_ARGS(&d3d.rtv_heap))); 754 | d3d.rtv_descriptor_size = d3d.device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV); 755 | 756 | create_render_target_views(); 757 | } 758 | 759 | // Create Z-buffer 760 | { 761 | D3D12_DESCRIPTOR_HEAP_DESC dsv_heap_desc = {}; 762 | dsv_heap_desc.NumDescriptors = 1; 763 | dsv_heap_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV; 764 | dsv_heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; 765 | ThrowIfFailed(d3d.device->CreateDescriptorHeap(&dsv_heap_desc, IID_PPV_ARGS(&d3d.dsv_heap))); 766 | 767 | create_depth_buffer(); 768 | } 769 | 770 | // Create SRV heap for texture descriptors 771 | { 772 | D3D12_DESCRIPTOR_HEAP_DESC srv_heap_desc = {}; 773 | srv_heap_desc.NumDescriptors = 1024; // Max unique textures per frame 774 | srv_heap_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV; 775 | srv_heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; 776 | ThrowIfFailed(d3d.device->CreateDescriptorHeap(&srv_heap_desc, IID_PPV_ARGS(&d3d.srv_heap))); 777 | d3d.srv_descriptor_size = d3d.device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); 778 | } 779 | 780 | // Create sampler heap and descriptors 781 | { 782 | D3D12_DESCRIPTOR_HEAP_DESC sampler_heap_desc = {}; 783 | sampler_heap_desc.NumDescriptors = 18; 784 | sampler_heap_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER; 785 | sampler_heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; 786 | ThrowIfFailed(d3d.device->CreateDescriptorHeap(&sampler_heap_desc, IID_PPV_ARGS(&d3d.sampler_heap))); 787 | d3d.sampler_descriptor_size = d3d.device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER); 788 | 789 | static const D3D12_TEXTURE_ADDRESS_MODE address_modes[] = { 790 | D3D12_TEXTURE_ADDRESS_MODE_WRAP, 791 | D3D12_TEXTURE_ADDRESS_MODE_MIRROR, 792 | D3D12_TEXTURE_ADDRESS_MODE_CLAMP 793 | }; 794 | 795 | D3D12_CPU_DESCRIPTOR_HANDLE sampler_handle = get_cpu_descriptor_handle(d3d.sampler_heap); 796 | int pos = 0; 797 | for (int linear_filter = 0; linear_filter < 2; linear_filter++) { 798 | for (int cms = 0; cms < 3; cms++) { 799 | for (int cmt = 0; cmt < 3; cmt++) { 800 | D3D12_SAMPLER_DESC sampler_desc = {}; 801 | sampler_desc.Filter = linear_filter ? D3D12_FILTER_MIN_MAG_MIP_LINEAR : D3D12_FILTER_MIN_MAG_MIP_POINT; 802 | sampler_desc.AddressU = address_modes[cms]; 803 | sampler_desc.AddressV = address_modes[cmt]; 804 | sampler_desc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_WRAP; 805 | sampler_desc.MinLOD = 0; 806 | sampler_desc.MaxLOD = D3D12_FLOAT32_MAX; 807 | sampler_desc.MipLODBias = 0.0f; 808 | sampler_desc.MaxAnisotropy = 1; 809 | sampler_desc.ComparisonFunc = D3D12_COMPARISON_FUNC_NEVER; 810 | d3d.device->CreateSampler(&sampler_desc, CD3DX12_CPU_DESCRIPTOR_HANDLE(sampler_handle, pos++, d3d.sampler_descriptor_size)); 811 | } 812 | } 813 | } 814 | } 815 | 816 | // Create constant buffer view for noise 817 | { 818 | /*D3D12_DESCRIPTOR_HEAP_DESC cbv_heap_desc = {}; 819 | cbv_heap_desc.NumDescriptors = 1; 820 | cbv_heap_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV; 821 | srv_heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; 822 | ThrowIfFailed(d3d.device->CreateDescriptorHeap*/ 823 | 824 | CD3DX12_HEAP_PROPERTIES hp(D3D12_HEAP_TYPE_UPLOAD); 825 | CD3DX12_RESOURCE_DESC rdb = CD3DX12_RESOURCE_DESC::Buffer(256); 826 | ThrowIfFailed(d3d.device->CreateCommittedResource( 827 | &hp, 828 | D3D12_HEAP_FLAG_NONE, 829 | &rdb, 830 | D3D12_RESOURCE_STATE_GENERIC_READ, 831 | nullptr, 832 | IID_PPV_ARGS(&d3d.noise_cb))); 833 | 834 | CD3DX12_RANGE read_range(0, 0); // Read not possible from CPU 835 | ThrowIfFailed(d3d.noise_cb->Map(0, &read_range, &d3d.mapped_noise_cb_address)); 836 | } 837 | 838 | ThrowIfFailed(d3d.device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&d3d.command_allocator))); 839 | ThrowIfFailed(d3d.device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_COPY, IID_PPV_ARGS(&d3d.copy_command_allocator))); 840 | 841 | ThrowIfFailed(d3d.device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, d3d.command_allocator.Get(), nullptr, IID_PPV_ARGS(&d3d.command_list))); 842 | ThrowIfFailed(d3d.device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_COPY, d3d.copy_command_allocator.Get(), nullptr, IID_PPV_ARGS(&d3d.copy_command_list))); 843 | 844 | ThrowIfFailed(d3d.command_list->Close()); 845 | 846 | ThrowIfFailed(d3d.device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&d3d.fence))); 847 | d3d.fence_event = CreateEvent(nullptr, FALSE, FALSE, nullptr); 848 | if (d3d.fence_event == nullptr) { 849 | ThrowIfFailed(HRESULT_FROM_WIN32(GetLastError())); 850 | } 851 | 852 | ThrowIfFailed(d3d.device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&d3d.copy_fence))); 853 | 854 | { 855 | // Create a buffer of 1 MB in size. With a 120 star speed run 192 kB seems to be max usage. 856 | CD3DX12_HEAP_PROPERTIES hp(D3D12_HEAP_TYPE_UPLOAD); 857 | CD3DX12_RESOURCE_DESC rdb = CD3DX12_RESOURCE_DESC::Buffer(256 * 1024 * sizeof(float)); 858 | ThrowIfFailed(d3d.device->CreateCommittedResource( 859 | &hp, 860 | D3D12_HEAP_FLAG_NONE, 861 | &rdb, 862 | D3D12_RESOURCE_STATE_GENERIC_READ, 863 | nullptr, 864 | IID_PPV_ARGS(&d3d.vertex_buffer))); 865 | 866 | CD3DX12_RANGE read_range(0, 0); // Read not possible from CPU 867 | ThrowIfFailed(d3d.vertex_buffer->Map(0, &read_range, &d3d.mapped_vbuf_address)); 868 | } 869 | } 870 | 871 | static void gfx_direct3d12_end_frame(void) { 872 | if (max_texture_uploads < texture_uploads && texture_uploads != 38 && texture_uploads != 34 && texture_uploads != 29) { 873 | max_texture_uploads = texture_uploads; 874 | } 875 | //printf("Texture uploads: %d %d\n", max_texture_uploads, texture_uploads); 876 | texture_uploads = 0; 877 | 878 | ThrowIfFailed(d3d.copy_command_list->Close()); 879 | { 880 | ID3D12CommandList *lists[] = { d3d.copy_command_list.Get() }; 881 | d3d.copy_command_queue->ExecuteCommandLists(1, lists); 882 | d3d.copy_command_queue->Signal(d3d.copy_fence.Get(), ++d3d.copy_fence_value); 883 | } 884 | 885 | CD3DX12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition( 886 | d3d.render_targets[d3d.frame_index].Get(), 887 | D3D12_RESOURCE_STATE_RENDER_TARGET, 888 | D3D12_RESOURCE_STATE_PRESENT); 889 | d3d.command_list->ResourceBarrier(1, &barrier); 890 | 891 | d3d.command_queue->Wait(d3d.copy_fence.Get(), d3d.copy_fence_value); 892 | 893 | ThrowIfFailed(d3d.command_list->Close()); 894 | 895 | { 896 | ID3D12CommandList *lists[] = { d3d.command_list.Get() }; 897 | d3d.command_queue->ExecuteCommandLists(1, lists); 898 | } 899 | 900 | { 901 | LARGE_INTEGER t0; 902 | QueryPerformanceCounter(&t0); 903 | //printf("Present: %llu %u\n", (unsigned long long)(t0.QuadPart - d3d.qpc_init), d3d.length_in_vsync_frames); 904 | } 905 | } 906 | 907 | static void gfx_direct3d12_finish_render(void) { 908 | LARGE_INTEGER t0, t1, t2; 909 | QueryPerformanceCounter(&t0); 910 | 911 | static UINT64 fence_value; 912 | ThrowIfFailed(d3d.command_queue->Signal(d3d.fence.Get(), ++fence_value)); 913 | if (d3d.fence->GetCompletedValue() < fence_value) { 914 | ThrowIfFailed(d3d.fence->SetEventOnCompletion(fence_value, d3d.fence_event)); 915 | WaitForSingleObject(d3d.fence_event, INFINITE); 916 | } 917 | QueryPerformanceCounter(&t1); 918 | 919 | d3d.resources_to_clean_at_end_of_frame.clear(); 920 | for (std::pair>& heap : d3d.upload_heaps_in_flight) { 921 | d3d.upload_heaps[heap.first].push_back(std::move(heap.second)); 922 | } 923 | d3d.upload_heaps_in_flight.clear(); 924 | for (std::pair& item : d3d.texture_heap_allocations_to_reclaim_at_end_of_frame) { 925 | item.first->free_list.push_back(item.second); 926 | } 927 | d3d.texture_heap_allocations_to_reclaim_at_end_of_frame.clear(); 928 | 929 | QueryPerformanceCounter(&t2); 930 | 931 | d3d.frame_index = d3d.swap_chain->GetCurrentBackBufferIndex(); 932 | 933 | ThrowIfFailed(d3d.copy_command_allocator->Reset()); 934 | ThrowIfFailed(d3d.copy_command_list->Reset(d3d.copy_command_allocator.Get(), nullptr)); 935 | 936 | //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)); 937 | } 938 | 939 | } // namespace 940 | 941 | struct GfxRenderingAPI gfx_direct3d12_api = { 942 | gfx_direct3d12_z_is_from_0_to_1, 943 | gfx_direct3d12_unload_shader, 944 | gfx_direct3d12_load_shader, 945 | gfx_direct3d12_create_and_load_new_shader, 946 | gfx_direct3d12_lookup_shader, 947 | gfx_direct3d12_shader_get_info, 948 | gfx_direct3d12_new_texture, 949 | gfx_direct3d12_select_texture, 950 | gfx_direct3d12_upload_texture, 951 | gfx_direct3d12_set_sampler_parameters, 952 | gfx_direct3d12_set_depth_test, 953 | gfx_direct3d12_set_depth_mask, 954 | gfx_direct3d12_set_zmode_decal, 955 | gfx_direct3d12_set_viewport, 956 | gfx_direct3d12_set_scissor, 957 | gfx_direct3d12_set_use_alpha, 958 | gfx_direct3d12_draw_triangles, 959 | gfx_direct3d12_init, 960 | gfx_direct3d12_on_resize, 961 | gfx_direct3d12_start_frame, 962 | gfx_direct3d12_end_frame, 963 | gfx_direct3d12_finish_render 964 | }; 965 | 966 | #endif 967 | -------------------------------------------------------------------------------- /gfx_direct3d12.h: -------------------------------------------------------------------------------- 1 | #ifdef ENABLE_DX12 2 | 3 | #ifndef GFX_DIRECT3D12_H 4 | #define GFX_DIRECT3D12_H 5 | 6 | #include "gfx_rendering_api.h" 7 | 8 | extern struct GfxRenderingAPI gfx_direct3d12_api; 9 | 10 | #endif 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /gfx_direct3d12_guids.h: -------------------------------------------------------------------------------- 1 | #ifndef GFX_DIRECT3D12_GUIDS_H 2 | #define GFX_DIRECT3D12_GUIDS_H 3 | 4 | #ifdef __MINGW32__ 5 | 6 | // This file is only needed due to missing MinGW-specific headers for d3d12.h. 7 | // It will define IID_* symbols having the "selectany" attribute (assuming 8 | // d3d12.h was earlier included), as well as make __uuidof(...) work. 9 | 10 | #define DEF_GUID(type,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) \ 11 | __CRT_UUID_DECL(type,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) \ 12 | const GUID IID_##type = __uuidof(type) 13 | 14 | DEF_GUID(ID3D12Object,0xc4fec28f,0x7966,0x4e95,0x9f,0x94,0xf4,0x31,0xcb,0x56,0xc3,0xb8); 15 | DEF_GUID(ID3D12DeviceChild,0x905db94b,0xa00c,0x4140,0x9d,0xf5,0x2b,0x64,0xca,0x9e,0xa3,0x57); 16 | DEF_GUID(ID3D12RootSignature,0xc54a6b66,0x72df,0x4ee8,0x8b,0xe5,0xa9,0x46,0xa1,0x42,0x92,0x14); 17 | DEF_GUID(ID3D12RootSignatureDeserializer,0x34AB647B,0x3CC8,0x46AC,0x84,0x1B,0xC0,0x96,0x56,0x45,0xC0,0x46); 18 | DEF_GUID(ID3D12VersionedRootSignatureDeserializer,0x7F91CE67,0x090C,0x4BB7,0xB7,0x8E,0xED,0x8F,0xF2,0xE3,0x1D,0xA0); 19 | DEF_GUID(ID3D12Pageable,0x63ee58fb,0x1268,0x4835,0x86,0xda,0xf0,0x08,0xce,0x62,0xf0,0xd6); 20 | DEF_GUID(ID3D12Heap,0x6b3b2502,0x6e51,0x45b3,0x90,0xee,0x98,0x84,0x26,0x5e,0x8d,0xf3); 21 | DEF_GUID(ID3D12Resource,0x696442be,0xa72e,0x4059,0xbc,0x79,0x5b,0x5c,0x98,0x04,0x0f,0xad); 22 | DEF_GUID(ID3D12CommandAllocator,0x6102dee4,0xaf59,0x4b09,0xb9,0x99,0xb4,0x4d,0x73,0xf0,0x9b,0x24); 23 | DEF_GUID(ID3D12Fence,0x0a753dcf,0xc4d8,0x4b91,0xad,0xf6,0xbe,0x5a,0x60,0xd9,0x5a,0x76); 24 | DEF_GUID(ID3D12Fence1,0x433685fe,0xe22b,0x4ca0,0xa8,0xdb,0xb5,0xb4,0xf4,0xdd,0x0e,0x4a); 25 | DEF_GUID(ID3D12PipelineState,0x765a30f3,0xf624,0x4c6f,0xa8,0x28,0xac,0xe9,0x48,0x62,0x24,0x45); 26 | DEF_GUID(ID3D12DescriptorHeap,0x8efb471d,0x616c,0x4f49,0x90,0xf7,0x12,0x7b,0xb7,0x63,0xfa,0x51); 27 | DEF_GUID(ID3D12QueryHeap,0x0d9658ae,0xed45,0x469e,0xa6,0x1d,0x97,0x0e,0xc5,0x83,0xca,0xb4); 28 | DEF_GUID(ID3D12CommandSignature,0xc36a797c,0xec80,0x4f0a,0x89,0x85,0xa7,0xb2,0x47,0x50,0x82,0xd1); 29 | DEF_GUID(ID3D12CommandList,0x7116d91c,0xe7e4,0x47ce,0xb8,0xc6,0xec,0x81,0x68,0xf4,0x37,0xe5); 30 | DEF_GUID(ID3D12GraphicsCommandList,0x5b160d0f,0xac1b,0x4185,0x8b,0xa8,0xb3,0xae,0x42,0xa5,0xa4,0x55); 31 | DEF_GUID(ID3D12GraphicsCommandList1,0x553103fb,0x1fe7,0x4557,0xbb,0x38,0x94,0x6d,0x7d,0x0e,0x7c,0xa7); 32 | DEF_GUID(ID3D12GraphicsCommandList2,0x38C3E585,0xFF17,0x412C,0x91,0x50,0x4F,0xC6,0xF9,0xD7,0x2A,0x28); 33 | DEF_GUID(ID3D12CommandQueue,0x0ec870a6,0x5d7e,0x4c22,0x8c,0xfc,0x5b,0xaa,0xe0,0x76,0x16,0xed); 34 | DEF_GUID(ID3D12Device,0x189819f1,0x1db6,0x4b57,0xbe,0x54,0x18,0x21,0x33,0x9b,0x85,0xf7); 35 | DEF_GUID(ID3D12PipelineLibrary,0xc64226a8,0x9201,0x46af,0xb4,0xcc,0x53,0xfb,0x9f,0xf7,0x41,0x4f); 36 | DEF_GUID(ID3D12PipelineLibrary1,0x80eabf42,0x2568,0x4e5e,0xbd,0x82,0xc3,0x7f,0x86,0x96,0x1d,0xc3); 37 | DEF_GUID(ID3D12Device1,0x77acce80,0x638e,0x4e65,0x88,0x95,0xc1,0xf2,0x33,0x86,0x86,0x3e); 38 | DEF_GUID(ID3D12Device2,0x30baa41e,0xb15b,0x475c,0xa0,0xbb,0x1a,0xf5,0xc5,0xb6,0x43,0x28); 39 | DEF_GUID(ID3D12Device3,0x81dadc15,0x2bad,0x4392,0x93,0xc5,0x10,0x13,0x45,0xc4,0xaa,0x98); 40 | DEF_GUID(ID3D12ProtectedSession,0xA1533D18,0x0AC1,0x4084,0x85,0xB9,0x89,0xA9,0x61,0x16,0x80,0x6B); 41 | DEF_GUID(ID3D12ProtectedResourceSession,0x6CD696F4,0xF289,0x40CC,0x80,0x91,0x5A,0x6C,0x0A,0x09,0x9C,0x3D); 42 | DEF_GUID(ID3D12Device4,0xe865df17,0xa9ee,0x46f9,0xa4,0x63,0x30,0x98,0x31,0x5a,0xa2,0xe5); 43 | DEF_GUID(ID3D12LifetimeOwner,0xe667af9f,0xcd56,0x4f46,0x83,0xce,0x03,0x2e,0x59,0x5d,0x70,0xa8); 44 | DEF_GUID(ID3D12SwapChainAssistant,0xf1df64b6,0x57fd,0x49cd,0x88,0x07,0xc0,0xeb,0x88,0xb4,0x5c,0x8f); 45 | DEF_GUID(ID3D12LifetimeTracker,0x3fd03d36,0x4eb1,0x424a,0xa5,0x82,0x49,0x4e,0xcb,0x8b,0xa8,0x13); 46 | DEF_GUID(ID3D12StateObject,0x47016943,0xfca8,0x4594,0x93,0xea,0xaf,0x25,0x8b,0x55,0x34,0x6d); 47 | DEF_GUID(ID3D12StateObjectProperties,0xde5fa827,0x9bf9,0x4f26,0x89,0xff,0xd7,0xf5,0x6f,0xde,0x38,0x60); 48 | DEF_GUID(ID3D12Device5,0x8b4f173b,0x2fea,0x4b80,0x8f,0x58,0x43,0x07,0x19,0x1a,0xb9,0x5d); 49 | DEF_GUID(ID3D12DeviceRemovedExtendedDataSettings,0x82BC481C,0x6B9B,0x4030,0xAE,0xDB,0x7E,0xE3,0xD1,0xDF,0x1E,0x63); 50 | DEF_GUID(ID3D12DeviceRemovedExtendedData,0x98931D33,0x5AE8,0x4791,0xAA,0x3C,0x1A,0x73,0xA2,0x93,0x4E,0x71); 51 | DEF_GUID(ID3D12Device6,0xc70b221b,0x40e4,0x4a17,0x89,0xaf,0x02,0x5a,0x07,0x27,0xa6,0xdc); 52 | DEF_GUID(ID3D12Resource1,0x9D5E227A,0x4430,0x4161,0x88,0xB3,0x3E,0xCA,0x6B,0xB1,0x6E,0x19); 53 | DEF_GUID(ID3D12Heap1,0x572F7389,0x2168,0x49E3,0x96,0x93,0xD6,0xDF,0x58,0x71,0xBF,0x6D); 54 | DEF_GUID(ID3D12GraphicsCommandList3,0x6FDA83A7,0xB84C,0x4E38,0x9A,0xC8,0xC7,0xBD,0x22,0x01,0x6B,0x3D); 55 | DEF_GUID(ID3D12MetaCommand,0xDBB84C27,0x36CE,0x4FC9,0xB8,0x01,0xF0,0x48,0xC4,0x6A,0xC5,0x70); 56 | DEF_GUID(ID3D12GraphicsCommandList4,0x8754318e,0xd3a9,0x4541,0x98,0xcf,0x64,0x5b,0x50,0xdc,0x48,0x74); 57 | DEF_GUID(ID3D12Tools,0x7071e1f0,0xe84b,0x4b33,0x97,0x4f,0x12,0xfa,0x49,0xde,0x65,0xc5); 58 | DEF_GUID(ID3D12GraphicsCommandList5,0x55050859,0x4024,0x474c,0x87,0xf5,0x64,0x72,0xea,0xee,0x44,0xea); 59 | 60 | #endif 61 | 62 | #endif 63 | -------------------------------------------------------------------------------- /gfx_direct3d_common.cpp: -------------------------------------------------------------------------------- 1 | #if defined(ENABLE_DX11) || defined(ENABLE_DX12) 2 | 3 | #include 4 | 5 | #include "gfx_direct3d_common.h" 6 | #include "gfx_cc.h" 7 | 8 | void get_cc_features(uint32_t shader_id, CCFeatures *cc_features) { 9 | for (int i = 0; i < 4; i++) { 10 | cc_features->c[0][i] = (shader_id >> (i * 3)) & 7; 11 | cc_features->c[1][i] = (shader_id >> (12 + i * 3)) & 7; 12 | } 13 | 14 | cc_features->opt_alpha = (shader_id & SHADER_OPT_ALPHA) != 0; 15 | cc_features->opt_fog = (shader_id & SHADER_OPT_FOG) != 0; 16 | cc_features->opt_texture_edge = (shader_id & SHADER_OPT_TEXTURE_EDGE) != 0; 17 | cc_features->opt_noise = (shader_id & SHADER_OPT_NOISE) != 0; 18 | 19 | cc_features->used_textures[0] = false; 20 | cc_features->used_textures[1] = false; 21 | cc_features->num_inputs = 0; 22 | 23 | for (int i = 0; i < 2; i++) { 24 | for (int j = 0; j < 4; j++) { 25 | if (cc_features->c[i][j] >= SHADER_INPUT_1 && cc_features->c[i][j] <= SHADER_INPUT_4) { 26 | if (cc_features->c[i][j] > cc_features->num_inputs) { 27 | cc_features->num_inputs = cc_features->c[i][j]; 28 | } 29 | } 30 | if (cc_features->c[i][j] == SHADER_TEXEL0 || cc_features->c[i][j] == SHADER_TEXEL0A) { 31 | cc_features->used_textures[0] = true; 32 | } 33 | if (cc_features->c[i][j] == SHADER_TEXEL1) { 34 | cc_features->used_textures[1] = true; 35 | } 36 | } 37 | } 38 | 39 | cc_features->do_single[0] = cc_features->c[0][2] == 0; 40 | cc_features->do_single[1] = cc_features->c[1][2] == 0; 41 | cc_features->do_multiply[0] = cc_features->c[0][1] == 0 && cc_features->c[0][3] == 0; 42 | cc_features->do_multiply[1] = cc_features->c[1][1] == 0 && cc_features->c[1][3] == 0; 43 | cc_features->do_mix[0] = cc_features->c[0][1] == cc_features->c[0][3]; 44 | cc_features->do_mix[1] = cc_features->c[1][1] == cc_features->c[1][3]; 45 | cc_features->color_alpha_same = (shader_id & 0xfff) == ((shader_id >> 12) & 0xfff); 46 | } 47 | 48 | static void append_str(char *buf, size_t *len, const char *str) { 49 | while (*str != '\0') buf[(*len)++] = *str++; 50 | } 51 | 52 | static void append_line(char *buf, size_t *len, const char *str) { 53 | while (*str != '\0') buf[(*len)++] = *str++; 54 | buf[(*len)++] = '\r'; 55 | buf[(*len)++] = '\n'; 56 | } 57 | 58 | static const char *shader_item_to_str(uint32_t item, bool with_alpha, bool only_alpha, bool inputs_have_alpha, bool hint_single_element) { 59 | if (!only_alpha) { 60 | switch (item) { 61 | default: 62 | case SHADER_0: 63 | return with_alpha ? "float4(0.0, 0.0, 0.0, 0.0)" : "float3(0.0, 0.0, 0.0)"; 64 | case SHADER_INPUT_1: 65 | return with_alpha || !inputs_have_alpha ? "input.input1" : "input.input1.rgb"; 66 | case SHADER_INPUT_2: 67 | return with_alpha || !inputs_have_alpha ? "input.input2" : "input.input2.rgb"; 68 | case SHADER_INPUT_3: 69 | return with_alpha || !inputs_have_alpha ? "input.input3" : "input.input3.rgb"; 70 | case SHADER_INPUT_4: 71 | return with_alpha || !inputs_have_alpha ? "input.input4" : "input.input4.rgb"; 72 | case SHADER_TEXEL0: 73 | return with_alpha ? "texVal0" : "texVal0.rgb"; 74 | case SHADER_TEXEL0A: 75 | return hint_single_element ? "texVal0.a" : (with_alpha ? "float4(texVal0.a, texVal0.a, texVal0.a, texVal0.a)" : "float3(texVal0.a, texVal0.a, texVal0.a)"); 76 | case SHADER_TEXEL1: 77 | return with_alpha ? "texVal1" : "texVal1.rgb"; 78 | } 79 | } else { 80 | switch (item) { 81 | default: 82 | case SHADER_0: 83 | return "0.0"; 84 | case SHADER_INPUT_1: 85 | return "input.input1.a"; 86 | case SHADER_INPUT_2: 87 | return "input.input2.a"; 88 | case SHADER_INPUT_3: 89 | return "input.input3.a"; 90 | case SHADER_INPUT_4: 91 | return "input.input4.a"; 92 | case SHADER_TEXEL0: 93 | return "texVal0.a"; 94 | case SHADER_TEXEL0A: 95 | return "texVal0.a"; 96 | case SHADER_TEXEL1: 97 | return "texVal1.a"; 98 | } 99 | } 100 | } 101 | 102 | static void append_formula(char *buf, size_t *len, const uint8_t c[2][4], bool do_single, bool do_multiply, bool do_mix, bool with_alpha, bool only_alpha, bool opt_alpha) { 103 | if (do_single) { 104 | append_str(buf, len, shader_item_to_str(c[only_alpha][3], with_alpha, only_alpha, opt_alpha, false)); 105 | } else if (do_multiply) { 106 | append_str(buf, len, shader_item_to_str(c[only_alpha][0], with_alpha, only_alpha, opt_alpha, false)); 107 | append_str(buf, len, " * "); 108 | append_str(buf, len, shader_item_to_str(c[only_alpha][2], with_alpha, only_alpha, opt_alpha, true)); 109 | } else if (do_mix) { 110 | append_str(buf, len, "lerp("); 111 | append_str(buf, len, shader_item_to_str(c[only_alpha][1], with_alpha, only_alpha, opt_alpha, false)); 112 | append_str(buf, len, ", "); 113 | append_str(buf, len, shader_item_to_str(c[only_alpha][0], with_alpha, only_alpha, opt_alpha, false)); 114 | append_str(buf, len, ", "); 115 | append_str(buf, len, shader_item_to_str(c[only_alpha][2], with_alpha, only_alpha, opt_alpha, true)); 116 | append_str(buf, len, ")"); 117 | } else { 118 | append_str(buf, len, "("); 119 | append_str(buf, len, shader_item_to_str(c[only_alpha][0], with_alpha, only_alpha, opt_alpha, false)); 120 | append_str(buf, len, " - "); 121 | append_str(buf, len, shader_item_to_str(c[only_alpha][1], with_alpha, only_alpha, opt_alpha, false)); 122 | append_str(buf, len, ") * "); 123 | append_str(buf, len, shader_item_to_str(c[only_alpha][2], with_alpha, only_alpha, opt_alpha, true)); 124 | append_str(buf, len, " + "); 125 | append_str(buf, len, shader_item_to_str(c[only_alpha][3], with_alpha, only_alpha, opt_alpha, false)); 126 | } 127 | } 128 | 129 | void gfx_direct3d_common_build_shader(char buf[4096], size_t& len, size_t& num_floats, const CCFeatures& cc_features, bool include_root_signature, bool three_point_filtering) { 130 | len = 0; 131 | num_floats = 4; 132 | 133 | // Pixel shader input struct 134 | 135 | if (include_root_signature) { 136 | append_str(buf, &len, "#define RS \"RootFlags(ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT | DENY_VERTEX_SHADER_ROOT_ACCESS)"); 137 | if (cc_features.opt_alpha && cc_features.opt_noise) { 138 | append_str(buf, &len, ",CBV(b0, visibility = SHADER_VISIBILITY_PIXEL)"); 139 | } 140 | if (cc_features.used_textures[0]) { 141 | append_str(buf, &len, ",DescriptorTable(SRV(t0), visibility = SHADER_VISIBILITY_PIXEL)"); 142 | append_str(buf, &len, ",DescriptorTable(Sampler(s0), visibility = SHADER_VISIBILITY_PIXEL)"); 143 | } 144 | if (cc_features.used_textures[1]) { 145 | append_str(buf, &len, ",DescriptorTable(SRV(t1), visibility = SHADER_VISIBILITY_PIXEL)"); 146 | append_str(buf, &len, ",DescriptorTable(Sampler(s1), visibility = SHADER_VISIBILITY_PIXEL)"); 147 | } 148 | append_line(buf, &len, "\""); 149 | } 150 | 151 | append_line(buf, &len, "struct PSInput {"); 152 | append_line(buf, &len, " float4 position : SV_POSITION;"); 153 | if (cc_features.used_textures[0] || cc_features.used_textures[1]) { 154 | append_line(buf, &len, " float2 uv : TEXCOORD;"); 155 | num_floats += 2; 156 | } 157 | if (cc_features.opt_alpha && cc_features.opt_noise) { 158 | append_line(buf, &len, " float4 screenPos : TEXCOORD1;"); 159 | } 160 | if (cc_features.opt_fog) { 161 | append_line(buf, &len, " float4 fog : FOG;"); 162 | num_floats += 4; 163 | } 164 | for (int i = 0; i < cc_features.num_inputs; i++) { 165 | len += sprintf(buf + len, " float%d input%d : INPUT%d;\r\n", cc_features.opt_alpha ? 4 : 3, i + 1, i); 166 | num_floats += cc_features.opt_alpha ? 4 : 3; 167 | } 168 | append_line(buf, &len, "};"); 169 | 170 | // Textures and samplers 171 | 172 | if (cc_features.used_textures[0]) { 173 | append_line(buf, &len, "Texture2D g_texture0 : register(t0);"); 174 | append_line(buf, &len, "SamplerState g_sampler0 : register(s0);"); 175 | } 176 | if (cc_features.used_textures[1]) { 177 | append_line(buf, &len, "Texture2D g_texture1 : register(t1);"); 178 | append_line(buf, &len, "SamplerState g_sampler1 : register(s1);"); 179 | } 180 | 181 | // Constant buffer and random function 182 | 183 | if (cc_features.opt_alpha && cc_features.opt_noise) { 184 | append_line(buf, &len, "cbuffer PerFrameCB : register(b0) {"); 185 | append_line(buf, &len, " uint noise_frame;"); 186 | append_line(buf, &len, " float2 noise_scale;"); 187 | append_line(buf, &len, "}"); 188 | 189 | append_line(buf, &len, "float random(in float3 value) {"); 190 | append_line(buf, &len, " float random = dot(value, float3(12.9898, 78.233, 37.719));"); 191 | append_line(buf, &len, " return frac(sin(random) * 143758.5453);"); 192 | append_line(buf, &len, "}"); 193 | } 194 | 195 | // 3 point texture filtering 196 | // Original author: ArthurCarvalho 197 | // Based on GLSL implementation by twinaphex, mupen64plus-libretro project. 198 | 199 | if (three_point_filtering && (cc_features.used_textures[0] || cc_features.used_textures[1])) { 200 | append_line(buf, &len, "cbuffer PerDrawCB : register(b1) {"); 201 | append_line(buf, &len, " struct {"); 202 | append_line(buf, &len, " uint width;"); 203 | append_line(buf, &len, " uint height;"); 204 | append_line(buf, &len, " bool linear_filtering;"); 205 | append_line(buf, &len, " } textures[2];"); 206 | append_line(buf, &len, "}"); 207 | append_line(buf, &len, "#define TEX_OFFSET(tex, tSampler, texCoord, off, texSize) tex.Sample(tSampler, texCoord - off / texSize)"); 208 | append_line(buf, &len, "float4 tex2D3PointFilter(in Texture2D tex, in SamplerState tSampler, in float2 texCoord, in float2 texSize) {"); 209 | append_line(buf, &len, " float2 offset = frac(texCoord * texSize - float2(0.5, 0.5));"); 210 | append_line(buf, &len, " offset -= step(1.0, offset.x + offset.y);"); 211 | append_line(buf, &len, " float4 c0 = TEX_OFFSET(tex, tSampler, texCoord, offset, texSize);"); 212 | append_line(buf, &len, " float4 c1 = TEX_OFFSET(tex, tSampler, texCoord, float2(offset.x - sign(offset.x), offset.y), texSize);"); 213 | append_line(buf, &len, " float4 c2 = TEX_OFFSET(tex, tSampler, texCoord, float2(offset.x, offset.y - sign(offset.y)), texSize);"); 214 | append_line(buf, &len, " return c0 + abs(offset.x)*(c1-c0) + abs(offset.y)*(c2-c0);"); 215 | append_line(buf, &len, "}"); 216 | } 217 | 218 | // Vertex shader 219 | 220 | append_str(buf, &len, "PSInput VSMain(float4 position : POSITION"); 221 | if (cc_features.used_textures[0] || cc_features.used_textures[1]) { 222 | append_str(buf, &len, ", float2 uv : TEXCOORD"); 223 | } 224 | if (cc_features.opt_fog) { 225 | append_str(buf, &len, ", float4 fog : FOG"); 226 | } 227 | for (int i = 0; i < cc_features.num_inputs; i++) { 228 | len += sprintf(buf + len, ", float%d input%d : INPUT%d", cc_features.opt_alpha ? 4 : 3, i + 1, i); 229 | } 230 | append_line(buf, &len, ") {"); 231 | append_line(buf, &len, " PSInput result;"); 232 | append_line(buf, &len, " result.position = position;"); 233 | if (cc_features.opt_alpha && cc_features.opt_noise) { 234 | append_line(buf, &len, " result.screenPos = position;"); 235 | } 236 | if (cc_features.used_textures[0] || cc_features.used_textures[1]) { 237 | append_line(buf, &len, " result.uv = uv;"); 238 | } 239 | if (cc_features.opt_fog) { 240 | append_line(buf, &len, " result.fog = fog;"); 241 | } 242 | for (int i = 0; i < cc_features.num_inputs; i++) { 243 | len += sprintf(buf + len, " result.input%d = input%d;\r\n", i + 1, i + 1); 244 | } 245 | append_line(buf, &len, " return result;"); 246 | append_line(buf, &len, "}"); 247 | 248 | // Pixel shader 249 | if (include_root_signature) { 250 | append_line(buf, &len, "[RootSignature(RS)]"); 251 | } 252 | append_line(buf, &len, "float4 PSMain(PSInput input) : SV_TARGET {"); 253 | if (cc_features.used_textures[0]) { 254 | if (three_point_filtering) { 255 | append_line(buf, &len, " float4 texVal0;"); 256 | append_line(buf, &len, " if (textures[0].linear_filtering)"); 257 | append_line(buf, &len, " texVal0 = tex2D3PointFilter(g_texture0, g_sampler0, input.uv, float2(textures[0].width, textures[0].height));"); 258 | append_line(buf, &len, " else"); 259 | append_line(buf, &len, " texVal0 = g_texture0.Sample(g_sampler0, input.uv);"); 260 | } else { 261 | append_line(buf, &len, " float4 texVal0 = g_texture0.Sample(g_sampler0, input.uv);"); 262 | } 263 | } 264 | if (cc_features.used_textures[1]) { 265 | if (three_point_filtering) { 266 | append_line(buf, &len, " float4 texVal1;"); 267 | append_line(buf, &len, " if (textures[1].linear_filtering)"); 268 | append_line(buf, &len, " texVal1 = tex2D3PointFilter(g_texture1, g_sampler1, input.uv, float2(textures[1].width, textures[1].height));"); 269 | append_line(buf, &len, " else"); 270 | append_line(buf, &len, " texVal1 = g_texture1.Sample(g_sampler1, input.uv);"); 271 | } else { 272 | append_line(buf, &len, " float4 texVal1 = g_texture1.Sample(g_sampler1, input.uv);"); 273 | } 274 | } 275 | 276 | append_str(buf, &len, cc_features.opt_alpha ? " float4 texel = " : " float3 texel = "); 277 | if (!cc_features.color_alpha_same && cc_features.opt_alpha) { 278 | append_str(buf, &len, "float4("); 279 | 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); 280 | append_str(buf, &len, ", "); 281 | 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); 282 | append_str(buf, &len, ")"); 283 | } else { 284 | 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); 285 | } 286 | append_line(buf, &len, ";"); 287 | 288 | if (cc_features.opt_texture_edge && cc_features.opt_alpha) { 289 | append_line(buf, &len, " if (texel.a > 0.3) texel.a = 1.0; else discard;"); 290 | } 291 | // TODO discard if alpha is 0? 292 | if (cc_features.opt_fog) { 293 | if (cc_features.opt_alpha) { 294 | append_line(buf, &len, " texel = float4(lerp(texel.rgb, input.fog.rgb, input.fog.a), texel.a);"); 295 | } else { 296 | append_line(buf, &len, " texel = lerp(texel, input.fog.rgb, input.fog.a);"); 297 | } 298 | } 299 | 300 | if (cc_features.opt_alpha && cc_features.opt_noise) { 301 | append_line(buf, &len, " float2 coords = (input.screenPos.xy / input.screenPos.w) * noise_scale;"); 302 | append_line(buf, &len, " texel.a *= round(saturate(random(float3(floor(coords), noise_frame)) + texel.a - 0.5));"); 303 | } 304 | 305 | if (cc_features.opt_alpha) { 306 | append_line(buf, &len, " return texel;"); 307 | } else { 308 | append_line(buf, &len, " return float4(texel, 1.0);"); 309 | } 310 | append_line(buf, &len, "}"); 311 | } 312 | 313 | #endif 314 | -------------------------------------------------------------------------------- /gfx_direct3d_common.h: -------------------------------------------------------------------------------- 1 | #if defined(ENABLE_DX11) || defined(ENABLE_DX12) 2 | 3 | #ifndef GFX_DIRECT3D_COMMON_H 4 | #define GFX_DIRECT3D_COMMON_H 5 | 6 | #include 7 | 8 | #include "gfx_cc.h" 9 | 10 | void gfx_direct3d_common_build_shader(char buf[4096], size_t& len, size_t& num_floats, const CCFeatures& cc_features, bool include_root_signature, bool three_point_filtering); 11 | 12 | #endif 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /gfx_dxgi.cpp: -------------------------------------------------------------------------------- 1 | #if defined(ENABLE_DX11) || defined(ENABLE_DX12) 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | 18 | #ifndef _LANGUAGE_C 19 | #define _LANGUAGE_C 20 | #endif 21 | #include 22 | 23 | #include "gfx_window_manager_api.h" 24 | #include "gfx_rendering_api.h" 25 | #include "gfx_direct3d_common.h" 26 | #include "gfx_screen_config.h" 27 | #include "gfx_pc.h" 28 | 29 | #define DECLARE_GFX_DXGI_FUNCTIONS 30 | #include "gfx_dxgi.h" 31 | 32 | #define WINCLASS_NAME L"N64GAME" 33 | #define GFX_API_NAME "DirectX" 34 | 35 | #ifdef VERSION_EU 36 | #define FRAME_INTERVAL_US_NUMERATOR 40000 37 | #define FRAME_INTERVAL_US_DENOMINATOR 1 38 | #else 39 | #define FRAME_INTERVAL_US_NUMERATOR 100000 40 | #define FRAME_INTERVAL_US_DENOMINATOR 3 41 | #endif 42 | 43 | using namespace Microsoft::WRL; // For ComPtr 44 | 45 | static struct { 46 | HWND h_wnd; 47 | bool showing_error; 48 | uint32_t current_width, current_height; 49 | std::string game_name; 50 | 51 | HMODULE dxgi_module; 52 | HRESULT (__stdcall *CreateDXGIFactory1)(REFIID riid, void **factory); 53 | HRESULT (__stdcall *CreateDXGIFactory2)(UINT flags, REFIID iid, void **factory); 54 | 55 | bool process_dpi_awareness_done; 56 | 57 | RECT last_window_rect; 58 | bool is_full_screen, last_maximized_state; 59 | 60 | ComPtr factory; 61 | ComPtr swap_chain; 62 | HANDLE waitable_object; 63 | uint64_t qpc_init, qpc_freq; 64 | uint64_t frame_timestamp; // in units of 1/FRAME_INTERVAL_US_DENOMINATOR microseconds 65 | std::map frame_stats; 66 | std::set> pending_frame_stats; 67 | bool dropped_frame; 68 | bool sync_interval_means_frames_to_wait; 69 | UINT length_in_vsync_frames; 70 | 71 | void (*on_fullscreen_changed)(bool is_now_fullscreen); 72 | void (*run_one_game_iter)(void); 73 | bool (*on_key_down)(int scancode); 74 | bool (*on_key_up)(int scancode); 75 | void (*on_all_keys_up)(void); 76 | } dxgi; 77 | 78 | static void load_dxgi_library(void) { 79 | dxgi.dxgi_module = LoadLibraryW(L"dxgi.dll"); 80 | *(FARPROC *)&dxgi.CreateDXGIFactory1 = GetProcAddress(dxgi.dxgi_module, "CreateDXGIFactory1"); 81 | *(FARPROC *)&dxgi.CreateDXGIFactory2 = GetProcAddress(dxgi.dxgi_module, "CreateDXGIFactory2"); 82 | } 83 | 84 | template 85 | static void run_as_dpi_aware(Fun f) { 86 | // Make sure Windows 8.1 or newer doesn't upscale/downscale the rendered images. 87 | // This is an issue on Windows 8.1 and newer where moving around the window 88 | // between different monitors having different scaling settings will 89 | // by default result in the DirectX image will also be scaled accordingly. 90 | // The resulting scale factor is the curent monitor's scale factor divided by 91 | // the initial monitor's scale factor. Setting per-monitor aware disables scaling. 92 | 93 | // On Windows 10 1607 and later, that is solved by setting the awarenenss per window, 94 | // which is done by using SetThreadDpiAwarenessContext before and after creating 95 | // any window. When the message handler runs, the corresponding context also applies. 96 | 97 | // From windef.h, missing in MinGW. 98 | DECLARE_HANDLE(DPI_AWARENESS_CONTEXT); 99 | #define DPI_AWARENESS_CONTEXT_UNAWARE ((DPI_AWARENESS_CONTEXT)-1) 100 | #define DPI_AWARENESS_CONTEXT_SYSTEM_AWARE ((DPI_AWARENESS_CONTEXT)-2) 101 | #define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE ((DPI_AWARENESS_CONTEXT)-3) 102 | #define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((DPI_AWARENESS_CONTEXT)-4) 103 | #define DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED ((DPI_AWARENESS_CONTEXT)-5) 104 | 105 | DPI_AWARENESS_CONTEXT (WINAPI *SetThreadDpiAwarenessContext)(DPI_AWARENESS_CONTEXT dpiContext); 106 | *(FARPROC *)&SetThreadDpiAwarenessContext = GetProcAddress(GetModuleHandleW(L"user32.dll"), "SetThreadDpiAwarenessContext"); 107 | DPI_AWARENESS_CONTEXT old_awareness_context; 108 | if (SetThreadDpiAwarenessContext != nullptr) { 109 | old_awareness_context = SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); 110 | } else { 111 | // Solution for Windows 8.1 and newer, but before Windows 10 1607. 112 | // SetProcessDpiAwareness must be called before any drawing related API is called. 113 | if (!dxgi.process_dpi_awareness_done) { 114 | HMODULE shcore_module = LoadLibraryW(L"SHCore.dll"); 115 | if (shcore_module != nullptr) { 116 | HRESULT (WINAPI *SetProcessDpiAwareness)(PROCESS_DPI_AWARENESS value); 117 | *(FARPROC *)&SetProcessDpiAwareness = GetProcAddress(shcore_module, "SetProcessDpiAwareness"); 118 | if (SetProcessDpiAwareness != nullptr) { 119 | SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE); 120 | // Ignore result, will fail if already called or manifest already specifies dpi awareness. 121 | } 122 | FreeLibrary(shcore_module); 123 | } 124 | dxgi.process_dpi_awareness_done = true; 125 | } 126 | } 127 | 128 | f(); 129 | 130 | // Restore the old context 131 | if (SetThreadDpiAwarenessContext != nullptr && old_awareness_context != nullptr) { 132 | SetThreadDpiAwarenessContext(old_awareness_context); 133 | } 134 | } 135 | 136 | static void toggle_borderless_window_full_screen(bool enable, bool call_callback) { 137 | // Windows 7 + flip mode + waitable object can't go to exclusive fullscreen, 138 | // so do borderless instead. If DWM is enabled, this means we get one monitor 139 | // sync interval of latency extra. On Win 10 however (maybe Win 8 too), due to 140 | // "fullscreen optimizations" the latency is eliminated. 141 | 142 | if (enable == dxgi.is_full_screen) { 143 | return; 144 | } 145 | 146 | if (!enable) { 147 | RECT r = dxgi.last_window_rect; 148 | 149 | // Set in window mode with the last saved position and size 150 | SetWindowLongPtr(dxgi.h_wnd, GWL_STYLE, WS_VISIBLE | WS_OVERLAPPEDWINDOW); 151 | 152 | if (dxgi.last_maximized_state) { 153 | SetWindowPos(dxgi.h_wnd, NULL, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE); 154 | ShowWindow(dxgi.h_wnd, SW_MAXIMIZE); 155 | } else { 156 | SetWindowPos(dxgi.h_wnd, NULL, r.left, r.top, r.right - r.left, r.bottom - r.top, SWP_FRAMECHANGED); 157 | ShowWindow(dxgi.h_wnd, SW_RESTORE); 158 | } 159 | 160 | ShowCursor(TRUE); 161 | 162 | dxgi.is_full_screen = false; 163 | } else { 164 | // Save if window is maximized or not 165 | WINDOWPLACEMENT window_placement; 166 | window_placement.length = sizeof(WINDOWPLACEMENT); 167 | GetWindowPlacement(dxgi.h_wnd, &window_placement); 168 | dxgi.last_maximized_state = window_placement.showCmd == SW_SHOWMAXIMIZED; 169 | 170 | // Save window position and size if the window is not maximized 171 | GetWindowRect(dxgi.h_wnd, &dxgi.last_window_rect); 172 | 173 | // Get in which monitor the window is 174 | HMONITOR h_monitor = MonitorFromWindow(dxgi.h_wnd, MONITOR_DEFAULTTONEAREST); 175 | 176 | // Get info from that monitor 177 | MONITORINFOEX monitor_info; 178 | monitor_info.cbSize = sizeof(MONITORINFOEX); 179 | GetMonitorInfo(h_monitor, &monitor_info); 180 | RECT r = monitor_info.rcMonitor; 181 | 182 | // Set borderless full screen to that monitor 183 | SetWindowLongPtr(dxgi.h_wnd, GWL_STYLE, WS_VISIBLE | WS_POPUP); 184 | SetWindowPos(dxgi.h_wnd, HWND_TOP, r.left, r.top, r.right - r.left, r.bottom - r.top, SWP_FRAMECHANGED); 185 | 186 | ShowCursor(FALSE); 187 | 188 | dxgi.is_full_screen = true; 189 | } 190 | 191 | if (dxgi.on_fullscreen_changed != nullptr && call_callback) { 192 | dxgi.on_fullscreen_changed(enable); 193 | } 194 | } 195 | 196 | static void gfx_dxgi_on_resize(void) { 197 | if (dxgi.swap_chain.Get() != nullptr) { 198 | gfx_get_current_rendering_api()->on_resize(); 199 | 200 | DXGI_SWAP_CHAIN_DESC1 desc1; 201 | ThrowIfFailed(dxgi.swap_chain->GetDesc1(&desc1)); 202 | dxgi.current_width = desc1.Width; 203 | dxgi.current_height = desc1.Height; 204 | } 205 | } 206 | 207 | static void onkeydown(WPARAM w_param, LPARAM l_param) { 208 | int key = ((l_param >> 16) & 0x1ff); 209 | if (dxgi.on_key_down != nullptr) { 210 | dxgi.on_key_down(key); 211 | } 212 | } 213 | static void onkeyup(WPARAM w_param, LPARAM l_param) { 214 | int key = ((l_param >> 16) & 0x1ff); 215 | if (dxgi.on_key_up != nullptr) { 216 | dxgi.on_key_up(key); 217 | } 218 | } 219 | 220 | static LRESULT CALLBACK gfx_dxgi_wnd_proc(HWND h_wnd, UINT message, WPARAM w_param, LPARAM l_param) { 221 | switch (message) { 222 | case WM_SIZE: 223 | gfx_dxgi_on_resize(); 224 | break; 225 | case WM_DESTROY: 226 | exit(0); 227 | case WM_PAINT: 228 | if (dxgi.showing_error) { 229 | return DefWindowProcW(h_wnd, message, w_param, l_param); 230 | } else { 231 | if (dxgi.run_one_game_iter != nullptr) { 232 | dxgi.run_one_game_iter(); 233 | } 234 | } 235 | break; 236 | case WM_ACTIVATEAPP: 237 | if (dxgi.on_all_keys_up != nullptr) { 238 | dxgi.on_all_keys_up(); 239 | } 240 | break; 241 | case WM_KEYDOWN: 242 | onkeydown(w_param, l_param); 243 | break; 244 | case WM_KEYUP: 245 | onkeyup(w_param, l_param); 246 | break; 247 | case WM_SYSKEYDOWN: 248 | if ((w_param == VK_RETURN) && ((l_param & 1 << 30) == 0)) { 249 | toggle_borderless_window_full_screen(!dxgi.is_full_screen, true); 250 | break; 251 | } else { 252 | return DefWindowProcW(h_wnd, message, w_param, l_param); 253 | } 254 | default: 255 | return DefWindowProcW(h_wnd, message, w_param, l_param); 256 | } 257 | return 0; 258 | } 259 | 260 | static void gfx_dxgi_init(const char *game_name, bool start_in_fullscreen) { 261 | LARGE_INTEGER qpc_init, qpc_freq; 262 | QueryPerformanceCounter(&qpc_init); 263 | QueryPerformanceFrequency(&qpc_freq); 264 | dxgi.qpc_init = qpc_init.QuadPart; 265 | dxgi.qpc_freq = qpc_freq.QuadPart; 266 | 267 | // Prepare window title 268 | 269 | char title[512]; 270 | wchar_t w_title[512]; 271 | int len = sprintf(title, "%s (%s)", game_name, GFX_API_NAME); 272 | mbstowcs(w_title, title, len + 1); 273 | dxgi.game_name = game_name; 274 | 275 | // Create window 276 | WNDCLASSEXW wcex; 277 | 278 | wcex.cbSize = sizeof(WNDCLASSEX); 279 | 280 | wcex.style = CS_HREDRAW | CS_VREDRAW; 281 | wcex.lpfnWndProc = gfx_dxgi_wnd_proc; 282 | wcex.cbClsExtra = 0; 283 | wcex.cbWndExtra = 0; 284 | wcex.hInstance = nullptr; 285 | wcex.hIcon = nullptr; 286 | wcex.hCursor = LoadCursor(nullptr, IDC_ARROW); 287 | wcex.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); 288 | wcex.lpszMenuName = nullptr; 289 | wcex.lpszClassName = WINCLASS_NAME; 290 | wcex.hIconSm = nullptr; 291 | 292 | ATOM winclass = RegisterClassExW(&wcex); 293 | 294 | 295 | run_as_dpi_aware([&] () { 296 | // We need to be dpi aware when calculating the size 297 | RECT wr = {0, 0, DESIRED_SCREEN_WIDTH, DESIRED_SCREEN_HEIGHT}; 298 | AdjustWindowRect(&wr, WS_OVERLAPPEDWINDOW, FALSE); 299 | 300 | dxgi.h_wnd = CreateWindowW(WINCLASS_NAME, w_title, WS_OVERLAPPEDWINDOW, 301 | CW_USEDEFAULT, 0, wr.right - wr.left, wr.bottom - wr.top, nullptr, nullptr, nullptr, nullptr); 302 | }); 303 | 304 | load_dxgi_library(); 305 | 306 | ShowWindow(dxgi.h_wnd, SW_SHOW); 307 | UpdateWindow(dxgi.h_wnd); 308 | 309 | if (start_in_fullscreen) { 310 | toggle_borderless_window_full_screen(true, false); 311 | } 312 | } 313 | 314 | static void gfx_dxgi_set_fullscreen_changed_callback(void (*on_fullscreen_changed)(bool is_now_fullscreen)) { 315 | dxgi.on_fullscreen_changed = on_fullscreen_changed; 316 | } 317 | 318 | static void gfx_dxgi_set_fullscreen(bool enable) { 319 | toggle_borderless_window_full_screen(enable, true); 320 | } 321 | 322 | static void gfx_dxgi_set_keyboard_callbacks(bool (*on_key_down)(int scancode), bool (*on_key_up)(int scancode), void (*on_all_keys_up)(void)) { 323 | dxgi.on_key_down = on_key_down; 324 | dxgi.on_key_up = on_key_up; 325 | dxgi.on_all_keys_up = on_all_keys_up; 326 | } 327 | 328 | static void gfx_dxgi_main_loop(void (*run_one_game_iter)(void)) { 329 | dxgi.run_one_game_iter = run_one_game_iter; 330 | 331 | MSG msg; 332 | while (GetMessage(&msg, nullptr, 0, 0)) { 333 | TranslateMessage(&msg); 334 | DispatchMessage(&msg); 335 | } 336 | } 337 | 338 | static void gfx_dxgi_get_dimensions(uint32_t *width, uint32_t *height) { 339 | *width = dxgi.current_width; 340 | *height = dxgi.current_height; 341 | } 342 | 343 | static void gfx_dxgi_handle_events(void) { 344 | /*MSG msg; 345 | while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) { 346 | TranslateMessage(&msg); 347 | DispatchMessage(&msg); 348 | }*/ 349 | } 350 | 351 | static uint64_t qpc_to_us(uint64_t qpc) { 352 | return qpc / dxgi.qpc_freq * 1000000 + qpc % dxgi.qpc_freq * 1000000 / dxgi.qpc_freq; 353 | } 354 | 355 | static bool gfx_dxgi_start_frame(void) { 356 | DXGI_FRAME_STATISTICS stats; 357 | if (dxgi.swap_chain->GetFrameStatistics(&stats) == S_OK && (stats.SyncRefreshCount != 0 || stats.SyncQPCTime.QuadPart != 0ULL)) { 358 | { 359 | LARGE_INTEGER t0; 360 | QueryPerformanceCounter(&t0); 361 | //printf("Get frame stats: %llu\n", (unsigned long long)(t0.QuadPart - dxgi.qpc_init)); 362 | } 363 | //printf("stats: %u %u %u %u %u %.6f\n", dxgi.pending_frame_stats.rbegin()->first, dxgi.pending_frame_stats.rbegin()->second, stats.PresentCount, stats.PresentRefreshCount, stats.SyncRefreshCount, (double)(stats.SyncQPCTime.QuadPart - dxgi.qpc_init) / dxgi.qpc_freq); 364 | if (dxgi.frame_stats.empty() || dxgi.frame_stats.rbegin()->second.PresentCount != stats.PresentCount) { 365 | dxgi.frame_stats.insert(std::make_pair(stats.PresentCount, stats)); 366 | } 367 | if (dxgi.frame_stats.size() > 3) { 368 | dxgi.frame_stats.erase(dxgi.frame_stats.begin()); 369 | } 370 | } 371 | if (!dxgi.frame_stats.empty()) { 372 | while (!dxgi.pending_frame_stats.empty() && dxgi.pending_frame_stats.begin()->first < dxgi.frame_stats.rbegin()->first) { 373 | dxgi.pending_frame_stats.erase(dxgi.pending_frame_stats.begin()); 374 | } 375 | } 376 | while (dxgi.pending_frame_stats.size() > 15) { 377 | // Just make sure the list doesn't grow too large if GetFrameStatistics fails. 378 | dxgi.pending_frame_stats.erase(dxgi.pending_frame_stats.begin()); 379 | } 380 | 381 | dxgi.frame_timestamp += FRAME_INTERVAL_US_NUMERATOR; 382 | 383 | if (dxgi.frame_stats.size() >= 2) { 384 | DXGI_FRAME_STATISTICS *first = &dxgi.frame_stats.begin()->second; 385 | DXGI_FRAME_STATISTICS *last = &dxgi.frame_stats.rbegin()->second; 386 | uint64_t sync_qpc_diff = last->SyncQPCTime.QuadPart - first->SyncQPCTime.QuadPart; 387 | UINT sync_vsync_diff = last->SyncRefreshCount - first->SyncRefreshCount; 388 | UINT present_vsync_diff = last->PresentRefreshCount - first->PresentRefreshCount; 389 | UINT present_diff = last->PresentCount - first->PresentCount; 390 | 391 | if (sync_vsync_diff == 0) { 392 | sync_vsync_diff = 1; 393 | } 394 | 395 | double estimated_vsync_interval = (double)sync_qpc_diff / (double)sync_vsync_diff; 396 | uint64_t estimated_vsync_interval_us = qpc_to_us(estimated_vsync_interval); 397 | //printf("Estimated vsync_interval: %d\n", (int)estimated_vsync_interval_us); 398 | if (estimated_vsync_interval_us < 2 || estimated_vsync_interval_us > 1000000) { 399 | // Unreasonable, maybe a monitor change 400 | estimated_vsync_interval_us = 16666; 401 | estimated_vsync_interval = estimated_vsync_interval_us * dxgi.qpc_freq / 1000000; 402 | } 403 | 404 | UINT queued_vsyncs = 0; 405 | bool is_first = true; 406 | for (const std::pair& p : dxgi.pending_frame_stats) { 407 | if (is_first && dxgi.sync_interval_means_frames_to_wait) { 408 | is_first = false; 409 | continue; 410 | } 411 | queued_vsyncs += p.second; 412 | } 413 | 414 | uint64_t last_frame_present_end_qpc = (last->SyncQPCTime.QuadPart - dxgi.qpc_init) + estimated_vsync_interval * queued_vsyncs; 415 | uint64_t last_end_us = qpc_to_us(last_frame_present_end_qpc); 416 | 417 | double vsyncs_to_wait = (double)(int64_t)(dxgi.frame_timestamp / FRAME_INTERVAL_US_DENOMINATOR - last_end_us) / estimated_vsync_interval_us; 418 | //printf("ts: %llu, last_end_us: %llu, Init v: %f\n", dxgi.frame_timestamp / 3, last_end_us, vsyncs_to_wait); 419 | 420 | if (vsyncs_to_wait <= 0) { 421 | // Too late 422 | 423 | if ((int64_t)(dxgi.frame_timestamp / FRAME_INTERVAL_US_DENOMINATOR - last_end_us) < -66666) { 424 | // The application must have been paused or similar 425 | vsyncs_to_wait = round(((double)FRAME_INTERVAL_US_NUMERATOR / FRAME_INTERVAL_US_DENOMINATOR) / estimated_vsync_interval_us); 426 | if (vsyncs_to_wait < 1) { 427 | vsyncs_to_wait = 1; 428 | } 429 | dxgi.frame_timestamp = FRAME_INTERVAL_US_DENOMINATOR * (last_end_us + vsyncs_to_wait * estimated_vsync_interval_us); 430 | } else { 431 | // Drop frame 432 | //printf("Dropping frame\n"); 433 | dxgi.dropped_frame = true; 434 | return false; 435 | } 436 | } 437 | if (floor(vsyncs_to_wait) != vsyncs_to_wait) { 438 | uint64_t left = last_end_us + floor(vsyncs_to_wait) * estimated_vsync_interval_us; 439 | uint64_t right = last_end_us + ceil(vsyncs_to_wait) * estimated_vsync_interval_us; 440 | uint64_t adjusted_desired_time = dxgi.frame_timestamp / FRAME_INTERVAL_US_DENOMINATOR + (last_end_us + (FRAME_INTERVAL_US_NUMERATOR / FRAME_INTERVAL_US_DENOMINATOR) > dxgi.frame_timestamp / FRAME_INTERVAL_US_DENOMINATOR ? 2000 : -2000); 441 | int64_t diff_left = adjusted_desired_time - left; 442 | int64_t diff_right = right - adjusted_desired_time; 443 | if (diff_left < 0) { 444 | diff_left = -diff_left; 445 | } 446 | if (diff_right < 0) { 447 | diff_right = -diff_right; 448 | } 449 | if (diff_left < diff_right) { 450 | vsyncs_to_wait = floor(vsyncs_to_wait); 451 | } else { 452 | vsyncs_to_wait = ceil(vsyncs_to_wait); 453 | } 454 | if (vsyncs_to_wait == 0) { 455 | //printf("vsyncs_to_wait became 0 so dropping frame\n"); 456 | dxgi.dropped_frame = true; 457 | return false; 458 | } 459 | } 460 | //printf("v: %d\n", (int)vsyncs_to_wait); 461 | if (vsyncs_to_wait > 4) { 462 | // Invalid, so change to 4 463 | vsyncs_to_wait = 4; 464 | } 465 | dxgi.length_in_vsync_frames = vsyncs_to_wait; 466 | } else { 467 | dxgi.length_in_vsync_frames = 2; 468 | } 469 | 470 | return true; 471 | } 472 | 473 | static void gfx_dxgi_swap_buffers_begin(void) { 474 | //dxgi.length_in_vsync_frames = 1; 475 | ThrowIfFailed(dxgi.swap_chain->Present(dxgi.length_in_vsync_frames, 0)); 476 | UINT this_present_id; 477 | if (dxgi.swap_chain->GetLastPresentCount(&this_present_id) == S_OK) { 478 | dxgi.pending_frame_stats.insert(std::make_pair(this_present_id, dxgi.length_in_vsync_frames)); 479 | } 480 | dxgi.dropped_frame = false; 481 | } 482 | 483 | static void gfx_dxgi_swap_buffers_end(void) { 484 | LARGE_INTEGER t0, t1, t2; 485 | QueryPerformanceCounter(&t0); 486 | QueryPerformanceCounter(&t1); 487 | 488 | if (!dxgi.dropped_frame) { 489 | if (dxgi.waitable_object != nullptr) { 490 | WaitForSingleObject(dxgi.waitable_object, INFINITE); 491 | } 492 | // else TODO: maybe sleep until some estimated time the frame will be shown to reduce lag 493 | } 494 | 495 | DXGI_FRAME_STATISTICS stats; 496 | dxgi.swap_chain->GetFrameStatistics(&stats); 497 | 498 | QueryPerformanceCounter(&t2); 499 | 500 | dxgi.sync_interval_means_frames_to_wait = dxgi.pending_frame_stats.rbegin()->first == stats.PresentCount; 501 | 502 | //printf("done %llu gpu:%d wait:%d freed:%llu frame:%u %u monitor:%u t:%llu\n", (unsigned long long)(t0.QuadPart - dxgi.qpc_init), (int)(t1.QuadPart - t0.QuadPart), (int)(t2.QuadPart - t0.QuadPart), (unsigned long long)(t2.QuadPart - dxgi.qpc_init), dxgi.pending_frame_stats.rbegin()->first, stats.PresentCount, stats.SyncRefreshCount, (unsigned long long)(stats.SyncQPCTime.QuadPart - dxgi.qpc_init)); 503 | } 504 | 505 | static double gfx_dxgi_get_time(void) { 506 | LARGE_INTEGER t; 507 | QueryPerformanceCounter(&t); 508 | return (double)(t.QuadPart - dxgi.qpc_init) / dxgi.qpc_freq; 509 | } 510 | 511 | void gfx_dxgi_create_factory_and_device(bool debug, int d3d_version, bool (*create_device_fn)(IDXGIAdapter1 *adapter, bool test_only)) { 512 | if (dxgi.CreateDXGIFactory2 != nullptr) { 513 | ThrowIfFailed(dxgi.CreateDXGIFactory2(debug ? DXGI_CREATE_FACTORY_DEBUG : 0, __uuidof(IDXGIFactory2), &dxgi.factory)); 514 | } else { 515 | ThrowIfFailed(dxgi.CreateDXGIFactory1(__uuidof(IDXGIFactory2), &dxgi.factory)); 516 | } 517 | 518 | ComPtr adapter; 519 | for (UINT i = 0; dxgi.factory->EnumAdapters1(i, &adapter) != DXGI_ERROR_NOT_FOUND; i++) { 520 | DXGI_ADAPTER_DESC1 desc; 521 | adapter->GetDesc1(&desc); 522 | if (desc.Flags & 2/*DXGI_ADAPTER_FLAG_SOFTWARE*/) { // declaration missing in mingw headers 523 | continue; 524 | } 525 | if (create_device_fn(adapter.Get(), true)) { 526 | break; 527 | } 528 | } 529 | create_device_fn(adapter.Get(), false); 530 | 531 | char title[512]; 532 | wchar_t w_title[512]; 533 | int len = sprintf(title, "%s (Direct3D %d)", dxgi.game_name.c_str(), d3d_version); 534 | mbstowcs(w_title, title, len + 1); 535 | SetWindowTextW(dxgi.h_wnd, w_title); 536 | } 537 | 538 | ComPtr gfx_dxgi_create_swap_chain(IUnknown *device) { 539 | bool win8 = IsWindows8OrGreater(); // DXGI_SCALING_NONE is only supported on Win8 and beyond 540 | bool dxgi_13 = dxgi.CreateDXGIFactory2 != nullptr; // DXGI 1.3 introduced waitable object 541 | 542 | DXGI_SWAP_CHAIN_DESC1 swap_chain_desc = {}; 543 | swap_chain_desc.BufferCount = 2; 544 | swap_chain_desc.Width = 0; 545 | swap_chain_desc.Height = 0; 546 | swap_chain_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; 547 | swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; 548 | swap_chain_desc.Scaling = win8 ? DXGI_SCALING_NONE : DXGI_SCALING_STRETCH; 549 | swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; // Apparently this was backported to Win 7 Platform Update 550 | swap_chain_desc.Flags = dxgi_13 ? DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT : 0; 551 | swap_chain_desc.SampleDesc.Count = 1; 552 | 553 | run_as_dpi_aware([&] () { 554 | // When setting size for the buffers, the values that DXGI puts into the desc (that can later be retrieved by GetDesc1) 555 | // have been divided by the current scaling factor. By making this call dpi aware, no division will be performed. 556 | // The same goes for IDXGISwapChain::ResizeBuffers(), however that function is currently only called from the message handler. 557 | ThrowIfFailed(dxgi.factory->CreateSwapChainForHwnd(device, dxgi.h_wnd, &swap_chain_desc, nullptr, nullptr, &dxgi.swap_chain)); 558 | }); 559 | ThrowIfFailed(dxgi.factory->MakeWindowAssociation(dxgi.h_wnd, DXGI_MWA_NO_ALT_ENTER)); 560 | 561 | ComPtr swap_chain2; 562 | if (dxgi.swap_chain->QueryInterface(__uuidof(IDXGISwapChain2), &swap_chain2) == S_OK) { 563 | ThrowIfFailed(swap_chain2->SetMaximumFrameLatency(1)); 564 | dxgi.waitable_object = swap_chain2->GetFrameLatencyWaitableObject(); 565 | WaitForSingleObject(dxgi.waitable_object, INFINITE); 566 | } else { 567 | ComPtr device1; 568 | ThrowIfFailed(device->QueryInterface(IID_PPV_ARGS(&device1))); 569 | ThrowIfFailed(device1->SetMaximumFrameLatency(1)); 570 | } 571 | 572 | ThrowIfFailed(dxgi.swap_chain->GetDesc1(&swap_chain_desc)); 573 | dxgi.current_width = swap_chain_desc.Width; 574 | dxgi.current_height = swap_chain_desc.Height; 575 | 576 | return dxgi.swap_chain; 577 | } 578 | 579 | HWND gfx_dxgi_get_h_wnd(void) { 580 | return dxgi.h_wnd; 581 | } 582 | 583 | void ThrowIfFailed(HRESULT res) { 584 | if (FAILED(res)) { 585 | fprintf(stderr, "Error: 0x%08X\n", res); 586 | throw res; 587 | } 588 | } 589 | 590 | void ThrowIfFailed(HRESULT res, HWND h_wnd, const char *message) { 591 | if (FAILED(res)) { 592 | char full_message[256]; 593 | sprintf(full_message, "%s\n\nHRESULT: 0x%08X", message, res); 594 | dxgi.showing_error = true; 595 | MessageBox(h_wnd, full_message, "Error", MB_OK | MB_ICONERROR); 596 | throw res; 597 | } 598 | } 599 | 600 | struct GfxWindowManagerAPI gfx_dxgi_api = { 601 | gfx_dxgi_init, 602 | gfx_dxgi_set_keyboard_callbacks, 603 | gfx_dxgi_set_fullscreen_changed_callback, 604 | gfx_dxgi_set_fullscreen, 605 | gfx_dxgi_main_loop, 606 | gfx_dxgi_get_dimensions, 607 | gfx_dxgi_handle_events, 608 | gfx_dxgi_start_frame, 609 | gfx_dxgi_swap_buffers_begin, 610 | gfx_dxgi_swap_buffers_end, 611 | gfx_dxgi_get_time, 612 | }; 613 | 614 | #endif 615 | -------------------------------------------------------------------------------- /gfx_dxgi.h: -------------------------------------------------------------------------------- 1 | #ifndef GFX_DXGI_H 2 | #define GFX_DXGI_H 3 | 4 | #include "gfx_rendering_api.h" 5 | 6 | #ifdef DECLARE_GFX_DXGI_FUNCTIONS 7 | void gfx_dxgi_create_factory_and_device(bool debug, int d3d_version, bool (*create_device_fn)(IDXGIAdapter1 *adapter, bool test_only)); 8 | Microsoft::WRL::ComPtr gfx_dxgi_create_swap_chain(IUnknown *device); 9 | HWND gfx_dxgi_get_h_wnd(void); 10 | void ThrowIfFailed(HRESULT res); 11 | void ThrowIfFailed(HRESULT res, HWND h_wnd, const char *message); 12 | #endif 13 | 14 | extern struct GfxWindowManagerAPI gfx_dxgi_api; 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /gfx_glx.c: -------------------------------------------------------------------------------- 1 | #ifdef __linux__ 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "gfx_window_manager_api.h" 16 | #include "gfx_screen_config.h" 17 | 18 | #define GFX_API_NAME "GLX - OpenGL" 19 | 20 | #ifdef VERSION_EU 21 | #define FRAME_INTERVAL_US_NUMERATOR 40000 22 | #define FRAME_INTERVAL_US_DENOMINATOR 1 23 | #else 24 | #define FRAME_INTERVAL_US_NUMERATOR 100000 25 | #define FRAME_INTERVAL_US_DENOMINATOR 3 26 | #endif 27 | 28 | const struct { 29 | const char *name; 30 | int scancode; 31 | } keymap_name_to_scancode[] = { 32 | {"ESC", 0x01}, 33 | {"AE01", 0x02 }, 34 | {"AE02", 0x03 }, 35 | {"AE03", 0x04 }, 36 | {"AE04", 0x05 }, 37 | {"AE05", 0x06 }, 38 | {"AE06", 0x07 }, 39 | {"AE07", 0x08 }, 40 | {"AE08", 0x09 }, 41 | {"AE09", 0x0a }, 42 | {"AE10", 0x0b }, 43 | {"AE11", 0x0c }, 44 | {"AE12", 0x0d }, 45 | {"BKSP", 0x0e }, 46 | {"TAB", 0x0f }, 47 | {"AD01", 0x10 }, 48 | {"AD02", 0x11 }, 49 | {"AD03", 0x12 }, 50 | {"AD04", 0x13 }, 51 | {"AD05", 0x14 }, 52 | {"AD06", 0x15 }, 53 | {"AD07", 0x16 }, 54 | {"AD08", 0x17 }, 55 | {"AD09", 0x18 }, 56 | {"AD10", 0x19 }, 57 | {"AD11", 0x1a }, 58 | {"AD12", 0x1b }, 59 | {"RTRN", 0x1c }, 60 | {"LCTL", 0x1d }, 61 | {"AC01", 0x1e }, 62 | {"AC02", 0x1f }, 63 | {"AC03", 0x20 }, 64 | {"AC04", 0x21 }, 65 | {"AC05", 0x22 }, 66 | {"AC06", 0x23 }, 67 | {"AC07", 0x24 }, 68 | {"AC08", 0x25 }, 69 | {"AC09", 0x26 }, 70 | {"AC10", 0x27 }, 71 | {"AC11", 0x28 }, 72 | {"TLDE", 0x29 }, 73 | {"LFSH", 0x2a }, 74 | {"BKSL", 0x2b }, 75 | {"AB01", 0x2c }, 76 | {"AB02", 0x2d }, 77 | {"AB03", 0x2e }, 78 | {"AB04", 0x2f }, 79 | {"AB05", 0x30 }, 80 | {"AB06", 0x31 }, 81 | {"AB07", 0x32 }, 82 | {"AB08", 0x33 }, 83 | {"AB09", 0x34 }, 84 | {"AB10", 0x35 }, 85 | {"RTSH", 0x36 }, 86 | {"KPMU", 0x37 }, 87 | {"LALT", 0x38 }, 88 | {"SPCE", 0x39 }, 89 | {"CAPS", 0x3a }, 90 | {"FK01", 0x3b }, 91 | {"FK02", 0x3c }, 92 | {"FK03", 0x3d }, 93 | {"FK04", 0x3e }, 94 | {"FK05", 0x3f }, 95 | {"FK06", 0x40 }, 96 | {"FK07", 0x41 }, 97 | {"FK08", 0x42 }, 98 | {"FK09", 0x43 }, 99 | {"FK10", 0x44 }, 100 | {"NMLK", 0x45 }, 101 | {"SCLK", 0x46 }, 102 | {"KP7", 0x47 }, 103 | {"KP8", 0x48 }, 104 | {"KP9", 0x49 }, 105 | {"KPSU", 0x4a }, 106 | {"KP4", 0x4b }, 107 | {"KP5", 0x4c }, 108 | {"KP6", 0x4d }, 109 | {"KPAD", 0x4e }, 110 | {"KP1", 0x4f }, 111 | {"KP2", 0x50 }, 112 | {"KP3", 0x51 }, 113 | {"KP0", 0x52 }, 114 | {"KPDL", 0x53 }, 115 | {"LVL3", 0x54 }, // correct? 116 | {"", 0x55 }, // not mapped? 117 | {"LSGT", 0x56 }, 118 | {"FK11", 0x57 }, 119 | {"FK12", 0x58 }, 120 | {"AB11", 0x59 }, 121 | {"KATA", 0 }, 122 | {"HIRA", 0 }, 123 | {"HENK", 0 }, 124 | {"HKTG", 0 }, 125 | {"MUHE", 0 }, 126 | {"JPCM", 0 }, 127 | {"KPEN", 0x11c }, 128 | {"RCTL", 0x11d }, 129 | {"KPDV", 0x135 }, 130 | {"PRSC", 0x54 }, // ? 131 | {"RALT", 0x138 }, 132 | {"LNFD", 0 }, 133 | {"HOME", 0x147 }, 134 | {"UP", 0x148 }, 135 | {"PGUP", 0x149 }, 136 | {"LEFT", 0x14b }, 137 | {"RGHT", 0x14d }, 138 | {"END", 0x14f }, 139 | {"DOWN", 0x150 }, 140 | {"PGDN", 0x151 }, 141 | {"INS", 0x152 }, 142 | {"DELE", 0x153 }, 143 | {"PAUS", 0x21d }, 144 | {"LWIN", 0x15b }, 145 | {"RWIN", 0x15c }, 146 | {"COMP", 0x15d }, 147 | }; 148 | 149 | static struct { 150 | Display *dpy; 151 | Window root; 152 | Window win; 153 | 154 | Atom atom_wm_state; 155 | Atom atom_wm_state_fullscreen; 156 | Atom atom_wm_delete_window; 157 | 158 | bool is_fullscreen; 159 | void (*on_fullscreen_changed)(bool is_now_fullscreen); 160 | 161 | int keymap[256]; 162 | bool (*on_key_down)(int scancode); 163 | bool (*on_key_up)(int scancode); 164 | void (*on_all_keys_up)(void); 165 | 166 | PFNGLXGETSYNCVALUESOMLPROC glXGetSyncValuesOML; 167 | PFNGLXSWAPBUFFERSMSCOMLPROC glXSwapBuffersMscOML; 168 | PFNGLXWAITFORSBCOMLPROC glXWaitForSbcOML; 169 | 170 | PFNGLXSWAPINTERVALEXTPROC glXSwapIntervalEXT; 171 | PFNGLXSWAPINTERVALSGIPROC glXSwapIntervalSGI; 172 | 173 | PFNGLXGETVIDEOSYNCSGIPROC glXGetVideoSyncSGI; 174 | PFNGLXWAITVIDEOSYNCSGIPROC glXWaitVideoSyncSGI; 175 | 176 | bool has_oml_sync_control; 177 | uint64_t ust0; 178 | int64_t last_msc; 179 | uint64_t wanted_ust; // multiplied by FRAME_INTERVAL_US_DENOMINATOR 180 | uint64_t vsync_interval; 181 | uint64_t last_ust; 182 | int64_t target_msc; 183 | bool dropped_frame; 184 | 185 | bool has_sgi_video_sync; 186 | uint64_t last_sync_counter; 187 | int64_t this_msc; 188 | int64_t this_ust; 189 | } glx; 190 | 191 | static int64_t get_time(void) { 192 | struct timespec ts; 193 | clock_gettime(CLOCK_MONOTONIC, &ts); 194 | return (int64_t)ts.tv_sec * 1000000 + ts.tv_nsec / 1000; 195 | } 196 | 197 | static int64_t adjust_sync_counter(uint32_t counter) { 198 | uint32_t hi = glx.last_sync_counter >> 32; 199 | uint32_t lo = (uint32_t)glx.last_sync_counter; 200 | if (lo >= 0x80000000U && counter < 0x80000000U) { 201 | // Wrapped 202 | ++hi; 203 | } 204 | glx.last_sync_counter = ((uint64_t)hi << 32) | counter; 205 | return glx.last_sync_counter; 206 | } 207 | 208 | static int64_t glXWaitVideoSyncSGI_wrapper(void) { 209 | unsigned int counter = 0; 210 | glx.glXWaitVideoSyncSGI(1, 0, &counter); 211 | return adjust_sync_counter(counter); 212 | } 213 | 214 | static int64_t glXGetVideoSyncSGI_wrapper(void) { 215 | unsigned int counter = 0; 216 | glx.glXGetVideoSyncSGI(&counter); 217 | return adjust_sync_counter(counter); 218 | } 219 | 220 | static void init_keymap(void) { 221 | XkbDescPtr desc = XkbGetMap(glx.dpy, 0, XkbUseCoreKbd); 222 | XkbGetNames(glx.dpy, XkbKeyNamesMask, desc); 223 | 224 | for (int i = desc->min_key_code; i <= desc->max_key_code && i < 256; i++) { 225 | char name[XkbKeyNameLength + 1]; 226 | memcpy(name, desc->names->keys[i].name, XkbKeyNameLength); 227 | name[XkbKeyNameLength] = '\0'; 228 | for (size_t j = 0; j < sizeof(keymap_name_to_scancode) / sizeof(keymap_name_to_scancode[0]); j++) { 229 | if (strcmp(keymap_name_to_scancode[j].name, name) == 0) { 230 | glx.keymap[i] = keymap_name_to_scancode[j].scancode; 231 | break; 232 | } 233 | } 234 | } 235 | 236 | XkbFreeNames(desc, XkbKeyNamesMask, True); 237 | XkbFreeKeyboard(desc, 0, True); 238 | } 239 | 240 | static void gfx_glx_hide_mouse(bool hide) { 241 | // Removes distracting mouse cursor during fullscreen play 242 | if (hide) { 243 | Cursor hideCursor; 244 | Pixmap bitmapNoData; 245 | XColor black; 246 | static char noData[] = { 0,0,0,0,0,0,0,0 }; 247 | black.red = black.green = black.blue = 0; 248 | 249 | bitmapNoData = XCreateBitmapFromData(glx.dpy, glx.win, noData, 8, 8); 250 | hideCursor = XCreatePixmapCursor(glx.dpy, bitmapNoData, bitmapNoData, 251 | &black, &black, 0, 0); 252 | XDefineCursor(glx.dpy, glx.win, hideCursor); 253 | XSync(glx.dpy, False); 254 | XFreeCursor(glx.dpy, hideCursor); 255 | XFreePixmap(glx.dpy, bitmapNoData); 256 | } else { 257 | XUndefineCursor(glx.dpy, glx.win); 258 | XSync(glx.dpy, False); 259 | } 260 | 261 | } 262 | 263 | static void gfx_glx_set_fullscreen_state(bool on, bool call_callback) { 264 | if (glx.is_fullscreen == on) { 265 | return; 266 | } 267 | glx.is_fullscreen = on; 268 | 269 | XEvent xev; 270 | xev.xany.type = ClientMessage; 271 | xev.xclient.message_type = glx.atom_wm_state; 272 | xev.xclient.format = 32; 273 | xev.xclient.window = glx.win; 274 | xev.xclient.data.l[0] = on; 275 | xev.xclient.data.l[1] = glx.atom_wm_state_fullscreen; 276 | xev.xclient.data.l[2] = 0; 277 | xev.xclient.data.l[3] = 0; 278 | XSendEvent(glx.dpy, glx.root, 0, SubstructureNotifyMask | SubstructureRedirectMask, &xev); 279 | gfx_glx_hide_mouse(on); 280 | 281 | if (glx.on_fullscreen_changed != NULL && call_callback) { 282 | glx.on_fullscreen_changed(on); 283 | } 284 | } 285 | 286 | static bool gfx_glx_check_extension(const char *extensions, const char *extension) { 287 | size_t len = strlen(extension); 288 | const char *pos = extensions; 289 | while ((pos = strstr(pos, extension)) != NULL) { 290 | if ((pos[len] == ' ' || pos[len] == '\0') && (pos == extensions || pos[-1] == ' ')) { 291 | return true; 292 | } 293 | if (pos[len] == '\0') { 294 | break; 295 | } 296 | pos += len + 1; 297 | } 298 | return false; 299 | } 300 | 301 | static void gfx_glx_init(const char *game_name, bool start_in_fullscreen) { 302 | // On NVIDIA proprietary driver, make the driver queue up to two frames on glXSwapBuffers, 303 | // which means that glXSwapBuffers should be non-blocking, 304 | // if we are sure to wait at least one vsync interval between calls. 305 | setenv("__GL_MaxFramesAllowed", "2", true); 306 | 307 | glx.dpy = XOpenDisplay(NULL); 308 | if (glx.dpy == NULL) { 309 | fprintf(stderr, "Cannot connect to X server\n"); 310 | exit(1); 311 | } 312 | int screen = DefaultScreen(glx.dpy); 313 | glx.root = RootWindow(glx.dpy, screen); 314 | 315 | GLint att[] = { GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None }; 316 | XVisualInfo *vi = glXChooseVisual(glx.dpy, 0, att); 317 | if (vi == NULL) { 318 | fprintf(stderr, "No appropriate GLX visual found\n"); 319 | exit(1); 320 | } 321 | Colormap cmap = XCreateColormap(glx.dpy, glx.root, vi->visual, AllocNone); 322 | XSetWindowAttributes swa; 323 | swa.colormap = cmap; 324 | swa.event_mask = ExposureMask | KeyPressMask | KeyReleaseMask | FocusChangeMask; 325 | glx.win = XCreateWindow(glx.dpy, glx.root, 0, 0, DESIRED_SCREEN_WIDTH, DESIRED_SCREEN_HEIGHT, 0, vi->depth, InputOutput, vi->visual, CWColormap | CWEventMask, &swa); 326 | 327 | glx.atom_wm_state = XInternAtom(glx.dpy, "_NET_WM_STATE", False); 328 | glx.atom_wm_state_fullscreen = XInternAtom(glx.dpy, "_NET_WM_STATE_FULLSCREEN", False); 329 | glx.atom_wm_delete_window = XInternAtom(glx.dpy, "WM_DELETE_WINDOW", False); 330 | XSetWMProtocols(glx.dpy, glx.win, &glx.atom_wm_delete_window, 1); 331 | XMapWindow(glx.dpy, glx.win); 332 | 333 | if (start_in_fullscreen) { 334 | gfx_glx_set_fullscreen_state(true, false); 335 | } 336 | 337 | char title[512]; 338 | int len = sprintf(title, "%s (%s)", game_name, GFX_API_NAME); 339 | 340 | XStoreName(glx.dpy, glx.win, title); 341 | GLXContext glc = glXCreateContext(glx.dpy, vi, NULL, GL_TRUE); 342 | glXMakeCurrent(glx.dpy, glx.win, glc); 343 | 344 | init_keymap(); 345 | 346 | const char *extensions = glXQueryExtensionsString(glx.dpy, screen); 347 | 348 | if (gfx_glx_check_extension(extensions, "GLX_OML_sync_control")) { 349 | glx.glXGetSyncValuesOML = (PFNGLXGETSYNCVALUESOMLPROC)glXGetProcAddressARB((const GLubyte *)"glXGetSyncValuesOML"); 350 | glx.glXSwapBuffersMscOML = (PFNGLXSWAPBUFFERSMSCOMLPROC)glXGetProcAddressARB((const GLubyte *)"glXSwapBuffersMscOML"); 351 | glx.glXWaitForSbcOML = (PFNGLXWAITFORSBCOMLPROC)glXGetProcAddressARB((const GLubyte *)"glXWaitForSbcOML"); 352 | } 353 | if (gfx_glx_check_extension(extensions, "GLX_EXT_swap_control")) { 354 | glx.glXSwapIntervalEXT = (PFNGLXSWAPINTERVALEXTPROC)glXGetProcAddressARB((const GLubyte *)"glXSwapIntervalEXT"); 355 | } 356 | if (gfx_glx_check_extension(extensions, "GLX_SGI_swap_control")) { 357 | glx.glXSwapIntervalSGI = (PFNGLXSWAPINTERVALSGIPROC)glXGetProcAddressARB((const GLubyte *)"glXSwapIntervalSGI"); 358 | } 359 | if (gfx_glx_check_extension(extensions, "GLX_SGI_video_sync")) { 360 | glx.glXGetVideoSyncSGI = (PFNGLXGETVIDEOSYNCSGIPROC)glXGetProcAddressARB((const GLubyte *)"glXGetVideoSyncSGI"); 361 | glx.glXWaitVideoSyncSGI = (PFNGLXWAITVIDEOSYNCSGIPROC)glXGetProcAddressARB((const GLubyte *)"glXWaitVideoSyncSGI"); 362 | } 363 | 364 | int64_t ust, msc, sbc; 365 | if (glx.glXGetSyncValuesOML != NULL && glx.glXGetSyncValuesOML(glx.dpy, glx.win, &ust, &msc, &sbc)) { 366 | glx.has_oml_sync_control = true; 367 | glx.ust0 = (uint64_t)ust; 368 | } else { 369 | glx.ust0 = get_time(); 370 | if (glx.glXSwapIntervalEXT != NULL) { 371 | glx.glXSwapIntervalEXT(glx.dpy, glx.win, 1); 372 | } else if (glx.glXSwapIntervalSGI != NULL) { 373 | glx.glXSwapIntervalSGI(1); 374 | } 375 | if (glx.glXGetVideoSyncSGI != NULL) { 376 | // Try if it really works 377 | unsigned int count; 378 | if (glx.glXGetVideoSyncSGI(&count) == 0) { 379 | glx.last_sync_counter = count; 380 | glx.has_sgi_video_sync = true; 381 | } 382 | } 383 | } 384 | glx.vsync_interval = 16666; 385 | } 386 | 387 | static void gfx_glx_set_fullscreen_changed_callback(void (*on_fullscreen_changed)(bool is_now_fullscreen)) { 388 | glx.on_fullscreen_changed = on_fullscreen_changed; 389 | } 390 | 391 | static void gfx_glx_set_fullscreen(bool enable) { 392 | gfx_glx_set_fullscreen_state(enable, true); 393 | } 394 | 395 | static void gfx_glx_set_keyboard_callbacks(bool (*on_key_down)(int scancode), bool (*on_key_up)(int scancode), void (*on_all_keys_up)(void)) { 396 | glx.on_key_down = on_key_down; 397 | glx.on_key_up = on_key_up; 398 | glx.on_all_keys_up = on_all_keys_up; 399 | } 400 | 401 | static void gfx_glx_main_loop(void (*run_one_game_iter)(void)) { 402 | while (1) { 403 | run_one_game_iter(); 404 | } 405 | } 406 | 407 | static void gfx_glx_get_dimensions(uint32_t *width, uint32_t *height) { 408 | XWindowAttributes attributes; 409 | XGetWindowAttributes(glx.dpy, glx.win, &attributes); 410 | *width = attributes.width; 411 | *height = attributes.height; 412 | } 413 | 414 | static void gfx_glx_handle_events(void) { 415 | while (XPending(glx.dpy)) { 416 | XEvent xev; 417 | XNextEvent(glx.dpy, &xev); 418 | if (xev.type == FocusOut) { 419 | if (glx.on_all_keys_up != NULL) { 420 | glx.on_all_keys_up(); 421 | } 422 | } 423 | if (xev.type == KeyPress || xev.type == KeyRelease) { 424 | if (xev.xkey.keycode < 256) { 425 | int scancode = glx.keymap[xev.xkey.keycode]; 426 | if (scancode != 0) { 427 | if (xev.type == KeyPress) { 428 | if (scancode == 0x44) { // F10 429 | gfx_glx_set_fullscreen_state(!glx.is_fullscreen, true); 430 | } 431 | if (glx.on_key_down != NULL) { 432 | glx.on_key_down(scancode); 433 | } 434 | } else { 435 | if (glx.on_key_up != NULL) { 436 | glx.on_key_up(scancode); 437 | } 438 | } 439 | } 440 | } 441 | } 442 | if (xev.type == ClientMessage && xev.xclient.data.l[0] == glx.atom_wm_delete_window) { 443 | exit(0); 444 | } 445 | } 446 | } 447 | 448 | static bool gfx_glx_start_frame(void) { 449 | return true; 450 | } 451 | 452 | static void gfx_glx_swap_buffers_begin(void) { 453 | glx.wanted_ust += FRAME_INTERVAL_US_NUMERATOR; // advance 1/30 seconds on JP/US or 1/25 seconds on EU 454 | 455 | if (!glx.has_oml_sync_control && !glx.has_sgi_video_sync) { 456 | glFlush(); 457 | 458 | uint64_t target = glx.wanted_ust / FRAME_INTERVAL_US_DENOMINATOR; 459 | uint64_t now; 460 | while (target > (now = (uint64_t)get_time() - glx.ust0)) { 461 | struct timespec ts = {(target - now) / 1000000, ((target - now) % 1000000) * 1000}; 462 | if (nanosleep(&ts, NULL) == 0) { 463 | break; 464 | } 465 | } 466 | 467 | if (target + 2 * FRAME_INTERVAL_US_NUMERATOR / FRAME_INTERVAL_US_DENOMINATOR < now) { 468 | if (target + 32 * FRAME_INTERVAL_US_NUMERATOR / FRAME_INTERVAL_US_DENOMINATOR >= now) { 469 | printf("Dropping frame\n"); 470 | glx.dropped_frame = true; 471 | return; 472 | } else { 473 | // Reset timer since we are way out of sync 474 | glx.wanted_ust = now * FRAME_INTERVAL_US_DENOMINATOR; 475 | } 476 | } 477 | glXSwapBuffers(glx.dpy, glx.win); 478 | glx.dropped_frame = false; 479 | 480 | return; 481 | } 482 | 483 | double vsyncs_to_wait = (int64_t)(glx.wanted_ust / FRAME_INTERVAL_US_DENOMINATOR - glx.last_ust) / (double)glx.vsync_interval; 484 | if (vsyncs_to_wait <= 0) { 485 | printf("Dropping frame\n"); 486 | // Drop frame 487 | glx.dropped_frame = true; 488 | return; 489 | } 490 | if (floor(vsyncs_to_wait) != vsyncs_to_wait) { 491 | uint64_t left_ust = glx.last_ust + floor(vsyncs_to_wait) * glx.vsync_interval; 492 | uint64_t right_ust = glx.last_ust + ceil(vsyncs_to_wait) * glx.vsync_interval; 493 | uint64_t adjusted_wanted_ust = glx.wanted_ust / FRAME_INTERVAL_US_DENOMINATOR + (glx.last_ust + FRAME_INTERVAL_US_NUMERATOR / FRAME_INTERVAL_US_DENOMINATOR > glx.wanted_ust / FRAME_INTERVAL_US_DENOMINATOR ? 2000 : -2000); 494 | int64_t diff_left = adjusted_wanted_ust - left_ust; 495 | int64_t diff_right = right_ust - adjusted_wanted_ust; 496 | if (diff_left < 0) { 497 | diff_left = -diff_left; 498 | } 499 | if (diff_right < 0) { 500 | diff_right = -diff_right; 501 | } 502 | if (diff_left < diff_right) { 503 | vsyncs_to_wait = floor(vsyncs_to_wait); 504 | } else { 505 | vsyncs_to_wait = ceil(vsyncs_to_wait); 506 | } 507 | if (vsyncs_to_wait <= -4) { 508 | printf("vsyncs_to_wait became -4 or less so dropping frame\n"); 509 | glx.dropped_frame = true; 510 | return; 511 | } else if (vsyncs_to_wait < 1) { 512 | vsyncs_to_wait = 1; 513 | } 514 | } 515 | glx.dropped_frame = false; 516 | //printf("Vsyncs to wait: %d, diff: %d\n", (int)vsyncs_to_wait, (int)(glx.last_ust + (int64_t)vsyncs_to_wait * glx.vsync_interval - glx.wanted_ust / 3)); 517 | if (vsyncs_to_wait > 30) { 518 | // Unreasonable, so change to 2 519 | vsyncs_to_wait = 2; 520 | } 521 | glx.target_msc = glx.last_msc + vsyncs_to_wait; 522 | 523 | if (glx.has_oml_sync_control) { 524 | glx.glXSwapBuffersMscOML(glx.dpy, glx.win, glx.target_msc, 0, 0); 525 | } else if (glx.has_sgi_video_sync) { 526 | glFlush(); // Try to submit pending work. Don't use glFinish since that busy loops on NVIDIA proprietary driver. 527 | 528 | //uint64_t counter0; 529 | uint64_t counter1, counter2; 530 | 531 | //uint64_t before_wait = get_time(); 532 | 533 | counter1 = glXGetVideoSyncSGI_wrapper(); 534 | //counter0 = counter1; 535 | //int waits = 0; 536 | while (counter1 < (uint64_t)glx.target_msc - 1) { 537 | counter1 = glXWaitVideoSyncSGI_wrapper(); 538 | //++waits; 539 | } 540 | 541 | //uint64_t before = get_time(); 542 | glXSwapBuffers(glx.dpy, glx.win); 543 | 544 | 545 | counter2 = glXGetVideoSyncSGI_wrapper(); 546 | while (counter2 < (uint64_t)glx.target_msc) { 547 | counter2 = glXWaitVideoSyncSGI_wrapper(); 548 | } 549 | uint64_t after = get_time(); 550 | 551 | //printf("%.3f %.3f %.3f\t%.3f\t%u %d %.2f %u %d\n", before_wait * 0.000060, before * 0.000060, after * 0.000060, (after - before) * 0.000060, counter0, counter2 - counter0, vsyncs_to_wait, (unsigned int)glx.target_msc, waits); 552 | glx.this_msc = counter2; 553 | glx.this_ust = after; 554 | } 555 | } 556 | 557 | static void gfx_glx_swap_buffers_end(void) { 558 | if (glx.dropped_frame || (!glx.has_oml_sync_control && !glx.has_sgi_video_sync)) { 559 | return; 560 | } 561 | 562 | int64_t ust, msc, sbc; 563 | if (glx.has_oml_sync_control) { 564 | if (!glx.glXWaitForSbcOML(glx.dpy, glx.win, 0, &ust, &msc, &sbc)) { 565 | // X connection broke or something? 566 | glx.last_ust += (glx.target_msc - glx.last_msc) * glx.vsync_interval; 567 | glx.last_msc = glx.target_msc; 568 | return; 569 | } 570 | } else { 571 | ust = glx.this_ust; 572 | msc = glx.this_msc; 573 | } 574 | uint64_t this_ust = ust - glx.ust0; 575 | uint64_t vsyncs_passed = msc - glx.last_msc; 576 | bool bad_vsync_interval = false; 577 | if (glx.last_ust != 0 && vsyncs_passed != 0) { 578 | uint64_t new_vsync_interval = (this_ust - glx.last_ust) / vsyncs_passed; 579 | if (new_vsync_interval <= 500000) { 580 | // Should be less than 0.5 seconds to be trusted 581 | glx.vsync_interval = new_vsync_interval; 582 | } else { 583 | bad_vsync_interval = true; 584 | } 585 | //printf("glx.vsync_interval: %d\n", (int)glx.vsync_interval); 586 | } 587 | glx.last_ust = this_ust; 588 | glx.last_msc = msc; 589 | if (msc != glx.target_msc) { 590 | printf("Frame too late by %d vsyncs\n", (int)(msc - glx.target_msc)); 591 | } 592 | if (msc - glx.target_msc >= 8 || bad_vsync_interval) { 593 | // Frame arrived way too late, so reset timer from here 594 | printf("Reseting timer\n"); 595 | glx.wanted_ust = this_ust * FRAME_INTERVAL_US_DENOMINATOR; 596 | } 597 | } 598 | 599 | static double gfx_glx_get_time(void) { 600 | return 0.0; 601 | } 602 | 603 | struct GfxWindowManagerAPI gfx_glx = { 604 | gfx_glx_init, 605 | gfx_glx_set_keyboard_callbacks, 606 | gfx_glx_set_fullscreen_changed_callback, 607 | gfx_glx_set_fullscreen, 608 | gfx_glx_main_loop, 609 | gfx_glx_get_dimensions, 610 | gfx_glx_handle_events, 611 | gfx_glx_start_frame, 612 | gfx_glx_swap_buffers_begin, 613 | gfx_glx_swap_buffers_end, 614 | gfx_glx_get_time 615 | }; 616 | 617 | #endif 618 | -------------------------------------------------------------------------------- /gfx_glx.h: -------------------------------------------------------------------------------- 1 | #ifndef GFX_GLX_H 2 | #define GFX_GLX_H 3 | 4 | #include "gfx_window_manager_api.h" 5 | 6 | struct GfxWindowManagerAPI gfx_glx; 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /gfx_opengl.c: -------------------------------------------------------------------------------- 1 | #ifdef ENABLE_OPENGL 2 | 3 | #include 4 | #include 5 | 6 | #ifndef _LANGUAGE_C 7 | #define _LANGUAGE_C 8 | #endif 9 | #include 10 | 11 | #ifdef __MINGW32__ 12 | #define FOR_WINDOWS 1 13 | #else 14 | #define FOR_WINDOWS 0 15 | #endif 16 | 17 | #if FOR_WINDOWS 18 | #include 19 | #include "SDL.h" 20 | #define GL_GLEXT_PROTOTYPES 1 21 | #include "SDL_opengl.h" 22 | #else 23 | #include 24 | #define GL_GLEXT_PROTOTYPES 1 25 | #include 26 | #endif 27 | 28 | #include "gfx_cc.h" 29 | #include "gfx_rendering_api.h" 30 | 31 | struct ShaderProgram { 32 | uint32_t shader_id; 33 | GLuint opengl_program_id; 34 | uint8_t num_inputs; 35 | bool used_textures[2]; 36 | uint8_t num_floats; 37 | GLint attrib_locations[7]; 38 | uint8_t attrib_sizes[7]; 39 | uint8_t num_attribs; 40 | bool used_noise; 41 | GLint frame_count_location; 42 | GLint window_height_location; 43 | }; 44 | 45 | static struct ShaderProgram shader_program_pool[64]; 46 | static uint8_t shader_program_pool_size; 47 | static GLuint opengl_vbo; 48 | 49 | static uint32_t frame_count; 50 | static uint32_t current_height; 51 | 52 | static bool gfx_opengl_z_is_from_0_to_1(void) { 53 | return false; 54 | } 55 | 56 | static void gfx_opengl_vertex_array_set_attribs(struct ShaderProgram *prg) { 57 | size_t num_floats = prg->num_floats; 58 | size_t pos = 0; 59 | 60 | for (int i = 0; i < prg->num_attribs; i++) { 61 | glEnableVertexAttribArray(prg->attrib_locations[i]); 62 | glVertexAttribPointer(prg->attrib_locations[i], prg->attrib_sizes[i], GL_FLOAT, GL_FALSE, num_floats * sizeof(float), (void *) (pos * sizeof(float))); 63 | pos += prg->attrib_sizes[i]; 64 | } 65 | } 66 | 67 | static void gfx_opengl_set_uniforms(struct ShaderProgram *prg) { 68 | if (prg->used_noise) { 69 | glUniform1i(prg->frame_count_location, frame_count); 70 | glUniform1i(prg->window_height_location, current_height); 71 | } 72 | } 73 | 74 | static void gfx_opengl_unload_shader(struct ShaderProgram *old_prg) { 75 | if (old_prg != NULL) { 76 | for (int i = 0; i < old_prg->num_attribs; i++) { 77 | glDisableVertexAttribArray(old_prg->attrib_locations[i]); 78 | } 79 | } 80 | } 81 | 82 | static void gfx_opengl_load_shader(struct ShaderProgram *new_prg) { 83 | glUseProgram(new_prg->opengl_program_id); 84 | gfx_opengl_vertex_array_set_attribs(new_prg); 85 | gfx_opengl_set_uniforms(new_prg); 86 | } 87 | 88 | static void append_str(char *buf, size_t *len, const char *str) { 89 | while (*str != '\0') buf[(*len)++] = *str++; 90 | } 91 | 92 | static void append_line(char *buf, size_t *len, const char *str) { 93 | while (*str != '\0') buf[(*len)++] = *str++; 94 | buf[(*len)++] = '\n'; 95 | } 96 | 97 | static const char *shader_item_to_str(uint32_t item, bool with_alpha, bool only_alpha, bool inputs_have_alpha, bool hint_single_element) { 98 | if (!only_alpha) { 99 | switch (item) { 100 | case SHADER_0: 101 | return with_alpha ? "vec4(0.0, 0.0, 0.0, 0.0)" : "vec3(0.0, 0.0, 0.0)"; 102 | case SHADER_INPUT_1: 103 | return with_alpha || !inputs_have_alpha ? "vInput1" : "vInput1.rgb"; 104 | case SHADER_INPUT_2: 105 | return with_alpha || !inputs_have_alpha ? "vInput2" : "vInput2.rgb"; 106 | case SHADER_INPUT_3: 107 | return with_alpha || !inputs_have_alpha ? "vInput3" : "vInput3.rgb"; 108 | case SHADER_INPUT_4: 109 | return with_alpha || !inputs_have_alpha ? "vInput4" : "vInput4.rgb"; 110 | case SHADER_TEXEL0: 111 | return with_alpha ? "texVal0" : "texVal0.rgb"; 112 | case SHADER_TEXEL0A: 113 | return hint_single_element ? "texVal0.a" : 114 | (with_alpha ? "vec4(texVal0.a, texVal0.a, texVal0.a, texVal0.a)" : "vec3(texVal0.a, texVal0.a, texVal0.a)"); 115 | case SHADER_TEXEL1: 116 | return with_alpha ? "texVal1" : "texVal1.rgb"; 117 | } 118 | } else { 119 | switch (item) { 120 | case SHADER_0: 121 | return "0.0"; 122 | case SHADER_INPUT_1: 123 | return "vInput1.a"; 124 | case SHADER_INPUT_2: 125 | return "vInput2.a"; 126 | case SHADER_INPUT_3: 127 | return "vInput3.a"; 128 | case SHADER_INPUT_4: 129 | return "vInput4.a"; 130 | case SHADER_TEXEL0: 131 | return "texVal0.a"; 132 | case SHADER_TEXEL0A: 133 | return "texVal0.a"; 134 | case SHADER_TEXEL1: 135 | return "texVal1.a"; 136 | } 137 | } 138 | } 139 | 140 | static void append_formula(char *buf, size_t *len, uint8_t c[2][4], bool do_single, bool do_multiply, bool do_mix, bool with_alpha, bool only_alpha, bool opt_alpha) { 141 | if (do_single) { 142 | append_str(buf, len, shader_item_to_str(c[only_alpha][3], with_alpha, only_alpha, opt_alpha, false)); 143 | } else if (do_multiply) { 144 | append_str(buf, len, shader_item_to_str(c[only_alpha][0], with_alpha, only_alpha, opt_alpha, false)); 145 | append_str(buf, len, " * "); 146 | append_str(buf, len, shader_item_to_str(c[only_alpha][2], with_alpha, only_alpha, opt_alpha, true)); 147 | } else if (do_mix) { 148 | append_str(buf, len, "mix("); 149 | append_str(buf, len, shader_item_to_str(c[only_alpha][1], with_alpha, only_alpha, opt_alpha, false)); 150 | append_str(buf, len, ", "); 151 | append_str(buf, len, shader_item_to_str(c[only_alpha][0], with_alpha, only_alpha, opt_alpha, false)); 152 | append_str(buf, len, ", "); 153 | append_str(buf, len, shader_item_to_str(c[only_alpha][2], with_alpha, only_alpha, opt_alpha, true)); 154 | append_str(buf, len, ")"); 155 | } else { 156 | append_str(buf, len, "("); 157 | append_str(buf, len, shader_item_to_str(c[only_alpha][0], with_alpha, only_alpha, opt_alpha, false)); 158 | append_str(buf, len, " - "); 159 | append_str(buf, len, shader_item_to_str(c[only_alpha][1], with_alpha, only_alpha, opt_alpha, false)); 160 | append_str(buf, len, ") * "); 161 | append_str(buf, len, shader_item_to_str(c[only_alpha][2], with_alpha, only_alpha, opt_alpha, true)); 162 | append_str(buf, len, " + "); 163 | append_str(buf, len, shader_item_to_str(c[only_alpha][3], with_alpha, only_alpha, opt_alpha, false)); 164 | } 165 | } 166 | 167 | static struct ShaderProgram *gfx_opengl_create_and_load_new_shader(uint32_t shader_id) { 168 | struct CCFeatures cc_features; 169 | gfx_cc_get_features(shader_id, &cc_features); 170 | 171 | char vs_buf[1024]; 172 | char fs_buf[1024]; 173 | size_t vs_len = 0; 174 | size_t fs_len = 0; 175 | size_t num_floats = 4; 176 | 177 | // Vertex shader 178 | append_line(vs_buf, &vs_len, "#version 110"); 179 | append_line(vs_buf, &vs_len, "attribute vec4 aVtxPos;"); 180 | if (cc_features.used_textures[0] || cc_features.used_textures[1]) { 181 | append_line(vs_buf, &vs_len, "attribute vec2 aTexCoord;"); 182 | append_line(vs_buf, &vs_len, "varying vec2 vTexCoord;"); 183 | num_floats += 2; 184 | } 185 | if (cc_features.opt_fog) { 186 | append_line(vs_buf, &vs_len, "attribute vec4 aFog;"); 187 | append_line(vs_buf, &vs_len, "varying vec4 vFog;"); 188 | num_floats += 4; 189 | } 190 | for (int i = 0; i < cc_features.num_inputs; i++) { 191 | vs_len += sprintf(vs_buf + vs_len, "attribute vec%d aInput%d;\n", cc_features.opt_alpha ? 4 : 3, i + 1); 192 | vs_len += sprintf(vs_buf + vs_len, "varying vec%d vInput%d;\n", cc_features.opt_alpha ? 4 : 3, i + 1); 193 | num_floats += cc_features.opt_alpha ? 4 : 3; 194 | } 195 | append_line(vs_buf, &vs_len, "void main() {"); 196 | if (cc_features.used_textures[0] || cc_features.used_textures[1]) { 197 | append_line(vs_buf, &vs_len, "vTexCoord = aTexCoord;"); 198 | } 199 | if (cc_features.opt_fog) { 200 | append_line(vs_buf, &vs_len, "vFog = aFog;"); 201 | } 202 | for (int i = 0; i < cc_features.num_inputs; i++) { 203 | vs_len += sprintf(vs_buf + vs_len, "vInput%d = aInput%d;\n", i + 1, i + 1); 204 | } 205 | append_line(vs_buf, &vs_len, "gl_Position = aVtxPos;"); 206 | append_line(vs_buf, &vs_len, "}"); 207 | 208 | // Fragment shader 209 | append_line(fs_buf, &fs_len, "#version 110"); 210 | //append_line(fs_buf, &fs_len, "precision mediump float;"); 211 | if (cc_features.used_textures[0] || cc_features.used_textures[1]) { 212 | append_line(fs_buf, &fs_len, "varying vec2 vTexCoord;"); 213 | } 214 | if (cc_features.opt_fog) { 215 | append_line(fs_buf, &fs_len, "varying vec4 vFog;"); 216 | } 217 | for (int i = 0; i < cc_features.num_inputs; i++) { 218 | fs_len += sprintf(fs_buf + fs_len, "varying vec%d vInput%d;\n", cc_features.opt_alpha ? 4 : 3, i + 1); 219 | } 220 | if (cc_features.used_textures[0]) { 221 | append_line(fs_buf, &fs_len, "uniform sampler2D uTex0;"); 222 | } 223 | if (cc_features.used_textures[1]) { 224 | append_line(fs_buf, &fs_len, "uniform sampler2D uTex1;"); 225 | } 226 | 227 | if (cc_features.opt_alpha && cc_features.opt_noise) { 228 | append_line(fs_buf, &fs_len, "uniform int frame_count;"); 229 | append_line(fs_buf, &fs_len, "uniform int window_height;"); 230 | 231 | append_line(fs_buf, &fs_len, "float random(in vec3 value) {"); 232 | append_line(fs_buf, &fs_len, " float random = dot(sin(value), vec3(12.9898, 78.233, 37.719));"); 233 | append_line(fs_buf, &fs_len, " return fract(sin(random) * 143758.5453);"); 234 | append_line(fs_buf, &fs_len, "}"); 235 | } 236 | 237 | append_line(fs_buf, &fs_len, "void main() {"); 238 | 239 | if (cc_features.used_textures[0]) { 240 | append_line(fs_buf, &fs_len, "vec4 texVal0 = texture2D(uTex0, vTexCoord);"); 241 | } 242 | if (cc_features.used_textures[1]) { 243 | append_line(fs_buf, &fs_len, "vec4 texVal1 = texture2D(uTex1, vTexCoord);"); 244 | } 245 | 246 | append_str(fs_buf, &fs_len, cc_features.opt_alpha ? "vec4 texel = " : "vec3 texel = "); 247 | if (!cc_features.color_alpha_same && cc_features.opt_alpha) { 248 | append_str(fs_buf, &fs_len, "vec4("); 249 | append_formula(fs_buf, &fs_len, cc_features.c, cc_features.do_single[0], cc_features.do_multiply[0], cc_features.do_mix[0], false, false, true); 250 | append_str(fs_buf, &fs_len, ", "); 251 | append_formula(fs_buf, &fs_len, cc_features.c, cc_features.do_single[1], cc_features.do_multiply[1], cc_features.do_mix[1], true, true, true); 252 | append_str(fs_buf, &fs_len, ")"); 253 | } else { 254 | append_formula(fs_buf, &fs_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); 255 | } 256 | append_line(fs_buf, &fs_len, ";"); 257 | 258 | if (cc_features.opt_texture_edge && cc_features.opt_alpha) { 259 | append_line(fs_buf, &fs_len, "if (texel.a > 0.3) texel.a = 1.0; else discard;"); 260 | } 261 | // TODO discard if alpha is 0? 262 | if (cc_features.opt_fog) { 263 | if (cc_features.opt_alpha) { 264 | append_line(fs_buf, &fs_len, "texel = vec4(mix(texel.rgb, vFog.rgb, vFog.a), texel.a);"); 265 | } else { 266 | append_line(fs_buf, &fs_len, "texel = mix(texel, vFog.rgb, vFog.a);"); 267 | } 268 | } 269 | 270 | if (cc_features.opt_alpha && cc_features.opt_noise) { 271 | append_line(fs_buf, &fs_len, "texel.a *= floor(clamp(random(vec3(floor(gl_FragCoord.xy * (240.0 / float(window_height))), float(frame_count))) + texel.a, 0.0, 1.0));"); 272 | } 273 | 274 | if (cc_features.opt_alpha) { 275 | append_line(fs_buf, &fs_len, "gl_FragColor = texel;"); 276 | } else { 277 | append_line(fs_buf, &fs_len, "gl_FragColor = vec4(texel, 1.0);"); 278 | } 279 | append_line(fs_buf, &fs_len, "}"); 280 | 281 | vs_buf[vs_len] = '\0'; 282 | fs_buf[fs_len] = '\0'; 283 | 284 | /*puts("Vertex shader:"); 285 | puts(vs_buf); 286 | puts("Fragment shader:"); 287 | puts(fs_buf); 288 | puts("End");*/ 289 | 290 | const GLchar *sources[2] = { vs_buf, fs_buf }; 291 | const GLint lengths[2] = { vs_len, fs_len }; 292 | GLint success; 293 | 294 | GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER); 295 | glShaderSource(vertex_shader, 1, &sources[0], &lengths[0]); 296 | glCompileShader(vertex_shader); 297 | glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &success); 298 | if (!success) { 299 | GLint max_length = 0; 300 | glGetShaderiv(vertex_shader, GL_INFO_LOG_LENGTH, &max_length); 301 | char error_log[1024]; 302 | fprintf(stderr, "Vertex shader compilation failed\n"); 303 | glGetShaderInfoLog(vertex_shader, max_length, &max_length, &error_log[0]); 304 | fprintf(stderr, "%s\n", &error_log[0]); 305 | abort(); 306 | } 307 | 308 | GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER); 309 | glShaderSource(fragment_shader, 1, &sources[1], &lengths[1]); 310 | glCompileShader(fragment_shader); 311 | glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &success); 312 | if (!success) { 313 | GLint max_length = 0; 314 | glGetShaderiv(fragment_shader, GL_INFO_LOG_LENGTH, &max_length); 315 | char error_log[1024]; 316 | fprintf(stderr, "Fragment shader compilation failed\n"); 317 | glGetShaderInfoLog(fragment_shader, max_length, &max_length, &error_log[0]); 318 | fprintf(stderr, "%s\n", &error_log[0]); 319 | abort(); 320 | } 321 | 322 | GLuint shader_program = glCreateProgram(); 323 | glAttachShader(shader_program, vertex_shader); 324 | glAttachShader(shader_program, fragment_shader); 325 | glLinkProgram(shader_program); 326 | 327 | size_t cnt = 0; 328 | 329 | struct ShaderProgram *prg = &shader_program_pool[shader_program_pool_size++]; 330 | prg->attrib_locations[cnt] = glGetAttribLocation(shader_program, "aVtxPos"); 331 | prg->attrib_sizes[cnt] = 4; 332 | ++cnt; 333 | 334 | if (cc_features.used_textures[0] || cc_features.used_textures[1]) { 335 | prg->attrib_locations[cnt] = glGetAttribLocation(shader_program, "aTexCoord"); 336 | prg->attrib_sizes[cnt] = 2; 337 | ++cnt; 338 | } 339 | 340 | if (cc_features.opt_fog) { 341 | prg->attrib_locations[cnt] = glGetAttribLocation(shader_program, "aFog"); 342 | prg->attrib_sizes[cnt] = 4; 343 | ++cnt; 344 | } 345 | 346 | for (int i = 0; i < cc_features.num_inputs; i++) { 347 | char name[16]; 348 | sprintf(name, "aInput%d", i + 1); 349 | prg->attrib_locations[cnt] = glGetAttribLocation(shader_program, name); 350 | prg->attrib_sizes[cnt] = cc_features.opt_alpha ? 4 : 3; 351 | ++cnt; 352 | } 353 | 354 | prg->shader_id = shader_id; 355 | prg->opengl_program_id = shader_program; 356 | prg->num_inputs = cc_features.num_inputs; 357 | prg->used_textures[0] = cc_features.used_textures[0]; 358 | prg->used_textures[1] = cc_features.used_textures[1]; 359 | prg->num_floats = num_floats; 360 | prg->num_attribs = cnt; 361 | 362 | gfx_opengl_load_shader(prg); 363 | 364 | if (cc_features.used_textures[0]) { 365 | GLint sampler_location = glGetUniformLocation(shader_program, "uTex0"); 366 | glUniform1i(sampler_location, 0); 367 | } 368 | if (cc_features.used_textures[1]) { 369 | GLint sampler_location = glGetUniformLocation(shader_program, "uTex1"); 370 | glUniform1i(sampler_location, 1); 371 | } 372 | 373 | if (cc_features.opt_alpha && cc_features.opt_noise) { 374 | prg->frame_count_location = glGetUniformLocation(shader_program, "frame_count"); 375 | prg->window_height_location = glGetUniformLocation(shader_program, "window_height"); 376 | prg->used_noise = true; 377 | } else { 378 | prg->used_noise = false; 379 | } 380 | 381 | return prg; 382 | } 383 | 384 | static struct ShaderProgram *gfx_opengl_lookup_shader(uint32_t shader_id) { 385 | for (size_t i = 0; i < shader_program_pool_size; i++) { 386 | if (shader_program_pool[i].shader_id == shader_id) { 387 | return &shader_program_pool[i]; 388 | } 389 | } 390 | return NULL; 391 | } 392 | 393 | static void gfx_opengl_shader_get_info(struct ShaderProgram *prg, uint8_t *num_inputs, bool used_textures[2]) { 394 | *num_inputs = prg->num_inputs; 395 | used_textures[0] = prg->used_textures[0]; 396 | used_textures[1] = prg->used_textures[1]; 397 | } 398 | 399 | static GLuint gfx_opengl_new_texture(void) { 400 | GLuint ret; 401 | glGenTextures(1, &ret); 402 | return ret; 403 | } 404 | 405 | static void gfx_opengl_select_texture(int tile, GLuint texture_id) { 406 | glActiveTexture(GL_TEXTURE0 + tile); 407 | glBindTexture(GL_TEXTURE_2D, texture_id); 408 | } 409 | 410 | static void gfx_opengl_upload_texture(const uint8_t *rgba32_buf, int width, int height) { 411 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, rgba32_buf); 412 | } 413 | 414 | static uint32_t gfx_cm_to_opengl(uint32_t val) { 415 | if (val & G_TX_CLAMP) { 416 | return GL_CLAMP_TO_EDGE; 417 | } 418 | return (val & G_TX_MIRROR) ? GL_MIRRORED_REPEAT : GL_REPEAT; 419 | } 420 | 421 | static void gfx_opengl_set_sampler_parameters(int tile, bool linear_filter, uint32_t cms, uint32_t cmt) { 422 | glActiveTexture(GL_TEXTURE0 + tile); 423 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, linear_filter ? GL_LINEAR : GL_NEAREST); 424 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, linear_filter ? GL_LINEAR : GL_NEAREST); 425 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, gfx_cm_to_opengl(cms)); 426 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, gfx_cm_to_opengl(cmt)); 427 | } 428 | 429 | static void gfx_opengl_set_depth_test(bool depth_test) { 430 | if (depth_test) { 431 | glEnable(GL_DEPTH_TEST); 432 | } else { 433 | glDisable(GL_DEPTH_TEST); 434 | } 435 | } 436 | 437 | static void gfx_opengl_set_depth_mask(bool z_upd) { 438 | glDepthMask(z_upd ? GL_TRUE : GL_FALSE); 439 | } 440 | 441 | static void gfx_opengl_set_zmode_decal(bool zmode_decal) { 442 | if (zmode_decal) { 443 | glPolygonOffset(-2, -2); 444 | glEnable(GL_POLYGON_OFFSET_FILL); 445 | } else { 446 | glPolygonOffset(0, 0); 447 | glDisable(GL_POLYGON_OFFSET_FILL); 448 | } 449 | } 450 | 451 | static void gfx_opengl_set_viewport(int x, int y, int width, int height) { 452 | glViewport(x, y, width, height); 453 | current_height = height; 454 | } 455 | 456 | static void gfx_opengl_set_scissor(int x, int y, int width, int height) { 457 | glScissor(x, y, width, height); 458 | } 459 | 460 | static void gfx_opengl_set_use_alpha(bool use_alpha) { 461 | if (use_alpha) { 462 | glEnable(GL_BLEND); 463 | } else { 464 | glDisable(GL_BLEND); 465 | } 466 | } 467 | 468 | static void gfx_opengl_draw_triangles(float buf_vbo[], size_t buf_vbo_len, size_t buf_vbo_num_tris) { 469 | //printf("flushing %d tris\n", buf_vbo_num_tris); 470 | glBufferData(GL_ARRAY_BUFFER, sizeof(float) * buf_vbo_len, buf_vbo, GL_STREAM_DRAW); 471 | glDrawArrays(GL_TRIANGLES, 0, 3 * buf_vbo_num_tris); 472 | } 473 | 474 | static void gfx_opengl_init(void) { 475 | #if FOR_WINDOWS 476 | glewInit(); 477 | #endif 478 | 479 | glGenBuffers(1, &opengl_vbo); 480 | 481 | glBindBuffer(GL_ARRAY_BUFFER, opengl_vbo); 482 | 483 | glDepthFunc(GL_LEQUAL); 484 | glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 485 | } 486 | 487 | static void gfx_opengl_on_resize(void) { 488 | } 489 | 490 | static void gfx_opengl_start_frame(void) { 491 | frame_count++; 492 | 493 | glDisable(GL_SCISSOR_TEST); 494 | glDepthMask(GL_TRUE); // Must be set to clear Z-buffer 495 | glClearColor(0.0f, 0.0f, 0.0f, 1.0f); 496 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 497 | glEnable(GL_SCISSOR_TEST); 498 | } 499 | 500 | static void gfx_opengl_end_frame(void) { 501 | } 502 | 503 | static void gfx_opengl_finish_render(void) { 504 | } 505 | 506 | struct GfxRenderingAPI gfx_opengl_api = { 507 | gfx_opengl_z_is_from_0_to_1, 508 | gfx_opengl_unload_shader, 509 | gfx_opengl_load_shader, 510 | gfx_opengl_create_and_load_new_shader, 511 | gfx_opengl_lookup_shader, 512 | gfx_opengl_shader_get_info, 513 | gfx_opengl_new_texture, 514 | gfx_opengl_select_texture, 515 | gfx_opengl_upload_texture, 516 | gfx_opengl_set_sampler_parameters, 517 | gfx_opengl_set_depth_test, 518 | gfx_opengl_set_depth_mask, 519 | gfx_opengl_set_zmode_decal, 520 | gfx_opengl_set_viewport, 521 | gfx_opengl_set_scissor, 522 | gfx_opengl_set_use_alpha, 523 | gfx_opengl_draw_triangles, 524 | gfx_opengl_init, 525 | gfx_opengl_on_resize, 526 | gfx_opengl_start_frame, 527 | gfx_opengl_end_frame, 528 | gfx_opengl_finish_render 529 | }; 530 | 531 | #endif 532 | -------------------------------------------------------------------------------- /gfx_opengl.h: -------------------------------------------------------------------------------- 1 | #ifndef GFX_OPENGL_H 2 | #define GFX_OPENGL_H 3 | 4 | #include "gfx_rendering_api.h" 5 | 6 | extern struct GfxRenderingAPI gfx_opengl_api; 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /gfx_pc.h: -------------------------------------------------------------------------------- 1 | #ifndef GFX_PC_H 2 | #define GFX_PC_H 3 | 4 | #include 5 | 6 | struct GfxRenderingAPI; 7 | struct GfxWindowManagerAPI; 8 | 9 | struct GfxDimensions { 10 | uint32_t width, height; 11 | float aspect_ratio; 12 | }; 13 | 14 | extern struct GfxDimensions gfx_current_dimensions; 15 | 16 | #ifdef __cplusplus 17 | extern "C" { 18 | #endif 19 | 20 | void gfx_init(struct GfxWindowManagerAPI *wapi, struct GfxRenderingAPI *rapi, const char *game_name, bool start_in_fullscreen); 21 | struct GfxRenderingAPI *gfx_get_current_rendering_api(void); 22 | void gfx_start_frame(void); 23 | void gfx_run(Gfx *commands); 24 | void gfx_end_frame(void); 25 | 26 | #ifdef __cplusplus 27 | } 28 | #endif 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /gfx_rendering_api.h: -------------------------------------------------------------------------------- 1 | #ifndef GFX_RENDERING_API_H 2 | #define GFX_RENDERING_API_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | struct ShaderProgram; 9 | 10 | struct GfxRenderingAPI { 11 | bool (*z_is_from_0_to_1)(void); 12 | void (*unload_shader)(struct ShaderProgram *old_prg); 13 | void (*load_shader)(struct ShaderProgram *new_prg); 14 | struct ShaderProgram *(*create_and_load_new_shader)(uint32_t shader_id); 15 | struct ShaderProgram *(*lookup_shader)(uint32_t shader_id); 16 | void (*shader_get_info)(struct ShaderProgram *prg, uint8_t *num_inputs, bool used_textures[2]); 17 | uint32_t (*new_texture)(void); 18 | void (*select_texture)(int tile, uint32_t texture_id); 19 | void (*upload_texture)(const uint8_t *rgba32_buf, int width, int height); 20 | void (*set_sampler_parameters)(int sampler, bool linear_filter, uint32_t cms, uint32_t cmt); 21 | void (*set_depth_test)(bool depth_test); 22 | void (*set_depth_mask)(bool z_upd); 23 | void (*set_zmode_decal)(bool zmode_decal); 24 | void (*set_viewport)(int x, int y, int width, int height); 25 | void (*set_scissor)(int x, int y, int width, int height); 26 | void (*set_use_alpha)(bool use_alpha); 27 | void (*draw_triangles)(float buf_vbo[], size_t buf_vbo_len, size_t buf_vbo_num_tris); 28 | void (*init)(void); 29 | void (*on_resize)(void); 30 | void (*start_frame)(void); 31 | void (*end_frame)(void); 32 | void (*finish_render)(void); 33 | }; 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /gfx_screen_config.h: -------------------------------------------------------------------------------- 1 | #ifndef GFX_SCREEN_CONFIG_H 2 | #define GFX_SCREEN_CONFIG_H 3 | 4 | #define DESIRED_SCREEN_WIDTH 640 5 | #define DESIRED_SCREEN_HEIGHT 480 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /gfx_sdl.h: -------------------------------------------------------------------------------- 1 | #ifndef GFX_SDL_H 2 | #define GFX_SDL_H 3 | 4 | #include "gfx_window_manager_api.h" 5 | 6 | extern struct GfxWindowManagerAPI gfx_sdl; 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /gfx_sdl2.c: -------------------------------------------------------------------------------- 1 | #if !defined(__linux__) && defined(ENABLE_OPENGL) 2 | 3 | #ifdef __MINGW32__ 4 | #define FOR_WINDOWS 1 5 | #else 6 | #define FOR_WINDOWS 0 7 | #endif 8 | 9 | #if FOR_WINDOWS 10 | #include 11 | #include "SDL.h" 12 | #define GL_GLEXT_PROTOTYPES 1 13 | #include "SDL_opengl.h" 14 | #else 15 | #include 16 | #define GL_GLEXT_PROTOTYPES 1 17 | #include 18 | #endif 19 | 20 | #include "gfx_window_manager_api.h" 21 | #include "gfx_screen_config.h" 22 | 23 | #define GFX_API_NAME "SDL2 - OpenGL" 24 | 25 | static SDL_Window *wnd; 26 | static int inverted_scancode_table[512]; 27 | static int vsync_enabled = 0; 28 | static unsigned int window_width = DESIRED_SCREEN_WIDTH; 29 | static unsigned int window_height = DESIRED_SCREEN_HEIGHT; 30 | static bool fullscreen_state; 31 | static void (*on_fullscreen_changed_callback)(bool is_now_fullscreen); 32 | static bool (*on_key_down_callback)(int scancode); 33 | static bool (*on_key_up_callback)(int scancode); 34 | static void (*on_all_keys_up_callback)(void); 35 | 36 | const SDL_Scancode windows_scancode_table[] = 37 | { 38 | /* 0 1 2 3 4 5 6 7 */ 39 | /* 8 9 A B C D E F */ 40 | SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_ESCAPE, SDL_SCANCODE_1, SDL_SCANCODE_2, SDL_SCANCODE_3, SDL_SCANCODE_4, SDL_SCANCODE_5, SDL_SCANCODE_6, /* 0 */ 41 | SDL_SCANCODE_7, SDL_SCANCODE_8, SDL_SCANCODE_9, SDL_SCANCODE_0, SDL_SCANCODE_MINUS, SDL_SCANCODE_EQUALS, SDL_SCANCODE_BACKSPACE, SDL_SCANCODE_TAB, /* 0 */ 42 | 43 | SDL_SCANCODE_Q, SDL_SCANCODE_W, SDL_SCANCODE_E, SDL_SCANCODE_R, SDL_SCANCODE_T, SDL_SCANCODE_Y, SDL_SCANCODE_U, SDL_SCANCODE_I, /* 1 */ 44 | SDL_SCANCODE_O, SDL_SCANCODE_P, SDL_SCANCODE_LEFTBRACKET, SDL_SCANCODE_RIGHTBRACKET, SDL_SCANCODE_RETURN, SDL_SCANCODE_LCTRL, SDL_SCANCODE_A, SDL_SCANCODE_S, /* 1 */ 45 | 46 | SDL_SCANCODE_D, SDL_SCANCODE_F, SDL_SCANCODE_G, SDL_SCANCODE_H, SDL_SCANCODE_J, SDL_SCANCODE_K, SDL_SCANCODE_L, SDL_SCANCODE_SEMICOLON, /* 2 */ 47 | SDL_SCANCODE_APOSTROPHE, SDL_SCANCODE_GRAVE, SDL_SCANCODE_LSHIFT, SDL_SCANCODE_BACKSLASH, SDL_SCANCODE_Z, SDL_SCANCODE_X, SDL_SCANCODE_C, SDL_SCANCODE_V, /* 2 */ 48 | 49 | SDL_SCANCODE_B, SDL_SCANCODE_N, SDL_SCANCODE_M, SDL_SCANCODE_COMMA, SDL_SCANCODE_PERIOD, SDL_SCANCODE_SLASH, SDL_SCANCODE_RSHIFT, SDL_SCANCODE_PRINTSCREEN,/* 3 */ 50 | SDL_SCANCODE_LALT, SDL_SCANCODE_SPACE, SDL_SCANCODE_CAPSLOCK, SDL_SCANCODE_F1, SDL_SCANCODE_F2, SDL_SCANCODE_F3, SDL_SCANCODE_F4, SDL_SCANCODE_F5, /* 3 */ 51 | 52 | SDL_SCANCODE_F6, SDL_SCANCODE_F7, SDL_SCANCODE_F8, SDL_SCANCODE_F9, SDL_SCANCODE_F10, SDL_SCANCODE_NUMLOCKCLEAR, SDL_SCANCODE_SCROLLLOCK, SDL_SCANCODE_HOME, /* 4 */ 53 | SDL_SCANCODE_UP, SDL_SCANCODE_PAGEUP, SDL_SCANCODE_KP_MINUS, SDL_SCANCODE_LEFT, SDL_SCANCODE_KP_5, SDL_SCANCODE_RIGHT, SDL_SCANCODE_KP_PLUS, SDL_SCANCODE_END, /* 4 */ 54 | 55 | SDL_SCANCODE_DOWN, SDL_SCANCODE_PAGEDOWN, SDL_SCANCODE_INSERT, SDL_SCANCODE_DELETE, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_NONUSBACKSLASH,SDL_SCANCODE_F11, /* 5 */ 56 | SDL_SCANCODE_F12, SDL_SCANCODE_PAUSE, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_LGUI, SDL_SCANCODE_RGUI, SDL_SCANCODE_APPLICATION, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, /* 5 */ 57 | 58 | SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_F13, SDL_SCANCODE_F14, SDL_SCANCODE_F15, SDL_SCANCODE_F16, /* 6 */ 59 | SDL_SCANCODE_F17, SDL_SCANCODE_F18, SDL_SCANCODE_F19, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, /* 6 */ 60 | 61 | SDL_SCANCODE_INTERNATIONAL2, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_INTERNATIONAL1, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, /* 7 */ 62 | SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_INTERNATIONAL4, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_INTERNATIONAL5, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_INTERNATIONAL3, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN /* 7 */ 63 | }; 64 | 65 | const SDL_Scancode scancode_rmapping_extended[][2] = { 66 | {SDL_SCANCODE_KP_ENTER, SDL_SCANCODE_RETURN}, 67 | {SDL_SCANCODE_RALT, SDL_SCANCODE_LALT}, 68 | {SDL_SCANCODE_RCTRL, SDL_SCANCODE_LCTRL}, 69 | {SDL_SCANCODE_KP_DIVIDE, SDL_SCANCODE_SLASH}, 70 | //{SDL_SCANCODE_KP_PLUS, SDL_SCANCODE_CAPSLOCK} 71 | }; 72 | 73 | const SDL_Scancode scancode_rmapping_nonextended[][2] = { 74 | {SDL_SCANCODE_KP_7, SDL_SCANCODE_HOME}, 75 | {SDL_SCANCODE_KP_8, SDL_SCANCODE_UP}, 76 | {SDL_SCANCODE_KP_9, SDL_SCANCODE_PAGEUP}, 77 | {SDL_SCANCODE_KP_4, SDL_SCANCODE_LEFT}, 78 | {SDL_SCANCODE_KP_6, SDL_SCANCODE_RIGHT}, 79 | {SDL_SCANCODE_KP_1, SDL_SCANCODE_END}, 80 | {SDL_SCANCODE_KP_2, SDL_SCANCODE_DOWN}, 81 | {SDL_SCANCODE_KP_3, SDL_SCANCODE_PAGEDOWN}, 82 | {SDL_SCANCODE_KP_0, SDL_SCANCODE_INSERT}, 83 | {SDL_SCANCODE_KP_PERIOD, SDL_SCANCODE_DELETE}, 84 | {SDL_SCANCODE_KP_MULTIPLY, SDL_SCANCODE_PRINTSCREEN} 85 | }; 86 | 87 | static void set_fullscreen(bool on, bool call_callback) { 88 | if (fullscreen_state == on) { 89 | return; 90 | } 91 | fullscreen_state = on; 92 | 93 | if (on) { 94 | SDL_DisplayMode mode; 95 | SDL_GetDesktopDisplayMode(0, &mode); 96 | window_width = mode.w; 97 | window_height = mode.h; 98 | } else { 99 | window_width = DESIRED_SCREEN_WIDTH; 100 | window_height = DESIRED_SCREEN_HEIGHT; 101 | } 102 | SDL_SetWindowSize(wnd, window_width, window_height); 103 | SDL_SetWindowFullscreen(wnd, on ? SDL_WINDOW_FULLSCREEN : 0); 104 | 105 | if (on_fullscreen_changed_callback != NULL && call_callback) { 106 | on_fullscreen_changed_callback(on); 107 | } 108 | } 109 | 110 | int test_vsync(void) { 111 | // Even if SDL_GL_SetSwapInterval succeeds, it doesn't mean that VSync actually works. 112 | // A 60 Hz monitor should have a swap interval of 16.67 milliseconds. 113 | // Try to detect the length of a vsync by swapping buffers some times. 114 | // Since the graphics card may enqueue a fixed number of frames, 115 | // first send in four dummy frames to hopefully fill the queue. 116 | // This method will fail if the refresh rate is changed, which, in 117 | // combination with that we can't control the queue size (i.e. lag) 118 | // is a reason this generic SDL2 backend should only be used as last resort. 119 | Uint32 start; 120 | Uint32 end; 121 | 122 | SDL_GL_SwapWindow(wnd); 123 | SDL_GL_SwapWindow(wnd); 124 | SDL_GL_SwapWindow(wnd); 125 | SDL_GL_SwapWindow(wnd); 126 | SDL_GL_SwapWindow(wnd); 127 | SDL_GL_SwapWindow(wnd); 128 | SDL_GL_SwapWindow(wnd); 129 | SDL_GL_SwapWindow(wnd); 130 | start = SDL_GetTicks(); 131 | SDL_GL_SwapWindow(wnd); 132 | SDL_GL_SwapWindow(wnd); 133 | SDL_GL_SwapWindow(wnd); 134 | SDL_GL_SwapWindow(wnd); 135 | end = SDL_GetTicks(); 136 | 137 | float average = 4.0 * 1000.0 / (end - start); 138 | 139 | vsync_enabled = 1; 140 | if (average > 27 && average < 33) { 141 | SDL_GL_SetSwapInterval(1); 142 | } else if (average > 57 && average < 63) { 143 | SDL_GL_SetSwapInterval(2); 144 | } else if (average > 86 && average < 94) { 145 | SDL_GL_SetSwapInterval(3); 146 | } else if (average > 115 && average < 125) { 147 | SDL_GL_SetSwapInterval(4); 148 | } else { 149 | vsync_enabled = 0; 150 | } 151 | } 152 | 153 | static void gfx_sdl_init(const char *game_name, bool start_in_fullscreen) { 154 | SDL_Init(SDL_INIT_VIDEO); 155 | 156 | SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); 157 | SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); 158 | 159 | //SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); 160 | //SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4); 161 | 162 | char title[512]; 163 | int len = sprintf(title, "%s (%s)", game_name, GFX_API_NAME); 164 | 165 | wnd = SDL_CreateWindow(title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 166 | window_width, window_height, SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE); 167 | 168 | if (start_in_fullscreen) { 169 | set_fullscreen(true, false); 170 | } 171 | 172 | SDL_GL_CreateContext(wnd); 173 | 174 | SDL_GL_SetSwapInterval(1); 175 | test_vsync(); 176 | if (!vsync_enabled) 177 | puts("Warning: VSync is not enabled or not working. Falling back to timer for synchronization"); 178 | 179 | for (size_t i = 0; i < sizeof(windows_scancode_table) / sizeof(SDL_Scancode); i++) { 180 | inverted_scancode_table[windows_scancode_table[i]] = i; 181 | } 182 | 183 | for (size_t i = 0; i < sizeof(scancode_rmapping_extended) / sizeof(scancode_rmapping_extended[0]); i++) { 184 | inverted_scancode_table[scancode_rmapping_extended[i][0]] = inverted_scancode_table[scancode_rmapping_extended[i][1]] + 0x100; 185 | } 186 | 187 | for (size_t i = 0; i < sizeof(scancode_rmapping_nonextended) / sizeof(scancode_rmapping_nonextended[0]); i++) { 188 | inverted_scancode_table[scancode_rmapping_extended[i][0]] = inverted_scancode_table[scancode_rmapping_extended[i][1]]; 189 | inverted_scancode_table[scancode_rmapping_extended[i][1]] += 0x100; 190 | } 191 | } 192 | 193 | static void gfx_sdl_set_fullscreen_changed_callback(void (*on_fullscreen_changed)(bool is_now_fullscreen)) { 194 | on_fullscreen_changed_callback = on_fullscreen_changed; 195 | } 196 | 197 | static void gfx_sdl_set_fullscreen(bool enable) { 198 | set_fullscreen(enable, true); 199 | } 200 | 201 | static void gfx_sdl_set_keyboard_callbacks(bool (*on_key_down)(int scancode), bool (*on_key_up)(int scancode), void (*on_all_keys_up)(void)) { 202 | on_key_down_callback = on_key_down; 203 | on_key_up_callback = on_key_up; 204 | on_all_keys_up_callback = on_all_keys_up; 205 | } 206 | 207 | static void gfx_sdl_main_loop(void (*run_one_game_iter)(void)) { 208 | while (1) { 209 | run_one_game_iter(); 210 | } 211 | } 212 | 213 | static void gfx_sdl_get_dimensions(uint32_t *width, uint32_t *height) { 214 | *width = window_width; 215 | *height = window_height; 216 | } 217 | 218 | static int translate_scancode(int scancode) { 219 | if (scancode < 512) { 220 | return inverted_scancode_table[scancode]; 221 | } else { 222 | return 0; 223 | } 224 | } 225 | 226 | static void gfx_sdl_onkeydown(int scancode) { 227 | int key = translate_scancode(scancode); 228 | if (on_key_down_callback != NULL) { 229 | on_key_down_callback(key); 230 | } 231 | } 232 | 233 | static void gfx_sdl_onkeyup(int scancode) { 234 | int key = translate_scancode(scancode); 235 | if (on_key_up_callback != NULL) { 236 | on_key_up_callback(key); 237 | } 238 | } 239 | 240 | static void gfx_sdl_handle_events(void) { 241 | SDL_Event event; 242 | while (SDL_PollEvent(&event)) { 243 | switch (event.type) { 244 | #ifndef TARGET_WEB 245 | // Scancodes are broken in Emscripten SDL2: https://bugzilla.libsdl.org/show_bug.cgi?id=3259 246 | case SDL_KEYDOWN: 247 | if (event.key.keysym.sym == SDLK_F10) { 248 | set_fullscreen(!fullscreen_state, true); 249 | break; 250 | } 251 | gfx_sdl_onkeydown(event.key.keysym.scancode); 252 | break; 253 | case SDL_KEYUP: 254 | gfx_sdl_onkeyup(event.key.keysym.scancode); 255 | break; 256 | #endif 257 | case SDL_WINDOWEVENT: 258 | if (event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) { 259 | window_width = event.window.data1; 260 | window_height = event.window.data2; 261 | } 262 | break; 263 | case SDL_QUIT: 264 | exit(0); 265 | } 266 | } 267 | } 268 | 269 | static bool gfx_sdl_start_frame(void) { 270 | return true; 271 | } 272 | 273 | static void sync_framerate_with_timer(void) { 274 | // Number of milliseconds a frame should take (30 fps) 275 | const Uint32 FRAME_TIME = 1000 / 30; 276 | static Uint32 last_time; 277 | Uint32 elapsed = SDL_GetTicks() - last_time; 278 | 279 | if (elapsed < FRAME_TIME) 280 | SDL_Delay(FRAME_TIME - elapsed); 281 | last_time += FRAME_TIME; 282 | } 283 | 284 | static void gfx_sdl_swap_buffers_begin(void) { 285 | if (!vsync_enabled) { 286 | sync_framerate_with_timer(); 287 | } 288 | 289 | SDL_GL_SwapWindow(wnd); 290 | } 291 | 292 | static void gfx_sdl_swap_buffers_end(void) { 293 | } 294 | 295 | static double gfx_sdl_get_time(void) { 296 | return 0.0; 297 | } 298 | 299 | struct GfxWindowManagerAPI gfx_sdl = { 300 | gfx_sdl_init, 301 | gfx_sdl_set_keyboard_callbacks, 302 | gfx_sdl_set_fullscreen_changed_callback, 303 | gfx_sdl_set_fullscreen, 304 | gfx_sdl_main_loop, 305 | gfx_sdl_get_dimensions, 306 | gfx_sdl_handle_events, 307 | gfx_sdl_start_frame, 308 | gfx_sdl_swap_buffers_begin, 309 | gfx_sdl_swap_buffers_end, 310 | gfx_sdl_get_time 311 | }; 312 | 313 | #endif 314 | -------------------------------------------------------------------------------- /gfx_window_manager_api.h: -------------------------------------------------------------------------------- 1 | #ifndef GFX_WINDOW_MANAGER_API_H 2 | #define GFX_WINDOW_MANAGER_API_H 3 | 4 | #include 5 | #include 6 | 7 | struct GfxWindowManagerAPI { 8 | void (*init)(const char *game_name, bool start_in_fullscreen); 9 | void (*set_keyboard_callbacks)(bool (*on_key_down)(int scancode), bool (*on_key_up)(int scancode), void (*on_all_keys_up)(void)); 10 | void (*set_fullscreen_changed_callback)(void (*on_fullscreen_changed)(bool is_now_fullscreen)); 11 | void (*set_fullscreen)(bool enable); 12 | void (*main_loop)(void (*run_one_game_iter)(void)); 13 | void (*get_dimensions)(uint32_t *width, uint32_t *height); 14 | void (*handle_events)(void); 15 | bool (*start_frame)(void); 16 | void (*swap_buffers_begin)(void); 17 | void (*swap_buffers_end)(void); 18 | double (*get_time)(void); // For debug 19 | }; 20 | 21 | #endif 22 | --------------------------------------------------------------------------------