/* * System Interface Library for games * Copyright (c) 2007-2020 Andrew Church * Released under the GNU GPL version 3 or later; NO WARRANTY is provided. * See the file COPYING.txt for details. * * src/sysdep/windows/d3d-shader.c: Direct3D shader management functionality. */ #define IN_SYSDEP #include "src/base.h" #include "src/graphics.h" #include "src/math.h" #include "src/memory.h" #include "src/sysdep.h" #include "src/sysdep/windows/d3d.h" #include "src/sysdep/windows/d3d-internal.h" /*************************************************************************/ /**************** Shared data (private to Direct3D code) *****************/ /*************************************************************************/ uint8_t d3d_shader_objects_enabled; /*************************************************************************/ /****************************** Local data *******************************/ /*************************************************************************/ /* Current attribute bindings (strings are owned by us). */ static char *attribute_bindings[D3D11_VS_INPUT_REGISTER_COUNT]; /* Number of bound attributes (highest bound index + 1). */ static int num_attribute_bindings; /* Current standard attribute bindings + 1 (so we don't need an * initialization routine to set them all to -1). */ static int standard_attribute_bindings[SHADER_ATTRIBUTE__NUM]; /* Semantic names for standard attributes. */ static const char * const standard_attribute_names[SHADER_ATTRIBUTE__NUM] = { [SHADER_ATTRIBUTE_POSITION] = "POSITION", [SHADER_ATTRIBUTE_TEXCOORD] = "TEXCOORD", [SHADER_ATTRIBUTE_COLOR] = "COLOR", }; /* Currently active shader pipeline, or NULL if none. */ static D3DSysShaderPipeline *current_pipeline; /* Flag indicating whether to save compiled shader data. The data is * always available at shader creation time, but we can't fetch it back * from Direct3D after the fact, so we have to save our own copy; to avoid * wasting time/memory on unneeded copies, we only make that copy if the * caller explicitly requests it. */ static uint8_t save_shader_binaries; /* Uniform buffers for default shaders. */ static ID3D11Buffer *default_vs_uniforms; static ID3D11Buffer *default_ps_uniforms; /* Flag: Are the current shaders autogenerated ones? (Used to avoid * needlessly setting uniform buffers.) */ static uint8_t gen_shaders_installed; /* Compiler flags for debugging and optimization. */ static UINT debug_flag; static UINT opt_flag; /* Caches for default vertex and pixel shaders. */ static ID3D11VertexShader *default_vs_cache[3][5][3][2]; #define DEFAULT_VS_LOOKUP(array, position_count, has_texcoord, has_color, tex_is_fb, tex_offset, color_uniform, fog) \ (array)[(position_count)-2] \ [(has_texcoord) ? 1+2*(tex_is_fb)+(tex_offset) : 0] \ [(has_color) ? 1+(color_uniform) : 0] \ [(fog)] static ID3D11PixelShader *default_ps_cache[5][3][2][5]; #define DEFAULT_PS_LOOKUP(array, has_texcoord, has_color, texcolor_type, color_uniform, fog, alpha_test, alpha_comparison) \ (array)[(has_texcoord) ? (texcolor_type) : 0] \ [(has_color) ? 2 : (color_uniform)] \ [(fog)] \ [(alpha_test) ? 1+((alpha_comparison)-GRAPHICS_COMPARISON_LESS) : 0] /* Precompiled bytecode for default shaders (if enabled). */ #ifdef SIL_PLATFORM_WINDOWS_PRECOMPILED_D3D_SHADERS # include "d3d-defshaders.i" STATIC_ASSERT(lenof(default_vs_bytecode) == lenof(default_vs_cache), "Wrong dimensions on default_vs_bytecode"); STATIC_ASSERT(lenof(default_vs_bytecode[0]) == lenof(default_vs_cache[0]), "Wrong dimensions on default_vs_bytecode"); STATIC_ASSERT(lenof(default_vs_bytecode[0][0]) == lenof(default_vs_cache[0][0]), "Wrong dimensions on default_vs_bytecode"); STATIC_ASSERT(lenof(default_vs_bytecode[0][0][0]) == lenof(default_vs_cache[0][0][0]), "Wrong dimensions on default_vs_bytecode"); STATIC_ASSERT(lenof(default_ps_bytecode) == lenof(default_ps_cache), "Wrong dimensions on default_ps_bytecode"); STATIC_ASSERT(lenof(default_ps_bytecode[0]) == lenof(default_ps_cache[0]), "Wrong dimensions on default_ps_bytecode"); STATIC_ASSERT(lenof(default_ps_bytecode[0][0]) == lenof(default_ps_cache[0][0]), "Wrong dimensions on default_ps_bytecode"); STATIC_ASSERT(lenof(default_ps_bytecode[0][0][0]) == lenof(default_ps_cache[0][0][0]), "Wrong dimensions on default_ps_bytecode"); #endif /*-----------------------------------------------------------------------*/ /* Local routine declarations. */ /** * compile_shader: Compile the given shader and return the compiled data. * * The format of the returned data depends on data_size_ret. If * data_size_ret is NULL, the return value is the ID3DBlob produced by * the compiler. Otherwise, the return value is a buffer allocated with * mem_alloc(), and the buffer size is written to *data_size_ret. * * [Parameters] * source: Shader source code. * source_size: Size of shader source code, in bytes. * target: Target string (e.g. "vs_4_0"). * data_size_ret: Pointer to variable to receive the size of the * compiled shader code, in bytes. * [Return value] * Compiled shader code, or NULL on error. */ static void *compile_shader(const void *source, int source_size, const char *target, int *data_size_ret); /** * set_uniform_common: Set the value of the uniform at the given offset * in the shader's uniform buffer. * * [Parameters] * shader: Shader to operate on. * offset: Byte offset in uniform buffer of uniform to update. * data: Pointer to data to store. * num_floats: Number of "float"-type units in the data, or 0 if * storing an "int"-type value. */ static void set_uniform_common( D3DSysShader *shader, int offset, const void *data, int num_floats); /** * get_default_vs: Generate and return the default vertex shader for the * given rendering parameters. If a shader has already been generated for * the same parameters, it is returned from cache instead of being * generated again. * * [Parameters] * position_count: Number of elements in the position vector (2-4). * has_texcoord: True if the vertex data contains texture coordinates. * has_color: True if the vertex data contains per-vertex colors. * tex_is_fb: True if the texture is a framebuffer texture. * tex_offset: True if a texture coordinate offset is applied. * color_uniform: True if a fixed color multiplier is applied. * fog: True if fog is applied. * [Return value] * Shader object, or NULL on error. */ static ID3D11VertexShader *get_default_vs( int position_count, int has_texcoord, int has_color, int tex_is_fb, int tex_offset, int color_uniform, int fog); /** * get_default_ps: Generate and return the default pixel shader for the * given rendering parameters. If a shader has already been generated for * the same parameters, it is returned from cache instead of being * generated again. * * [Parameters] * has_texcoord: True if the vertex data contains texture coordinates. * has_color: True if the vertex data contains per-vertex colors. * texcolor_type: Color type (TEXCOLOR_*) of the currently applied * texture, or 0 if no texture is applied. * color_uniform: True if a fixed color multiplier is applied. * fog: True if fog is applied. * alpha_test: True if alpha testing is enabled. * alpha_comparison: Comparison function for alpha testing. * [Return value] * Shader object, or NULL on error. */ static ID3D11PixelShader *get_default_ps( int has_texcoord, int has_color, int texcolor_type, int color_uniform, int fog, int alpha_test, GraphicsComparisonType alpha_comparison); /** * compile_default_vs: Compile the default vertex shader for the given * rendering parameters and return the compiled code as an ID3DBlob. * * [Parameters] * position_count: Number of elements in the position vector (2-4). * has_texcoord: True if the vertex data contains texture coordinates. * has_color: True if the vertex data contains per-vertex colors. * tex_is_fb: True if the texture is a framebuffer texture. * tex_offset: True if a texture coordinate offset is applied. * color_uniform: True if a fixed color multiplier is applied. * fog: True if fog is applied. * [Return value] * Compiled code, or NULL on error. */ static ID3DBlob *compile_default_vs( int position_count, int has_texcoord, int has_color, int tex_is_fb, int tex_offset, int color_uniform, int fog); /** * compile_default_ps: Compile the default pixel shader for the given * rendering parameters, and return the compiled code as an ID3DBlob. * * [Parameters] * has_texcoord: True if the vertex data contains texture coordinates. * has_color: True if the vertex data contains per-vertex colors. * texcolor_type: Color type (TEXCOLOR_*) of the currently applied * texture, or 0 if no texture is applied. * color_uniform: True if a fixed color multiplier is applied. * fog: True if fog is applied. * alpha_test: True if alpha testing is enabled. * alpha_comparison: Comparison function for alpha testing. * [Return value] * Shader object, or NULL on error. */ static ID3DBlob *compile_default_ps( int has_texcoord, int has_color, int texcolor_type, int color_uniform, int fog, int alpha_test, GraphicsComparisonType alpha_comparison); /*************************************************************************/ /***************** Interface routines: Basic operations ******************/ /*************************************************************************/ int d3d_sys_graphics_enable_shader_objects(void) { d3d_shader_objects_enabled = 1; return 1; } /*-----------------------------------------------------------------------*/ int d3d_sys_graphics_disable_shader_objects(void) { d3d_shader_objects_enabled = 0; return 1; } /*-----------------------------------------------------------------------*/ int d3d_sys_shader_background_compilation_supported(void) { return 1; } /*-----------------------------------------------------------------------*/ void d3d_sys_shader_enable_get_binary(int enable) { save_shader_binaries = (enable != 0); } /*-----------------------------------------------------------------------*/ int d3d_sys_shader_max_attributes(void) { return D3D11_VS_INPUT_REGISTER_COUNT; } /*************************************************************************/ /****************** Interface: Shader object management ******************/ /*************************************************************************/ int d3d_sys_shader_set_attribute(int index, const char *name) { int retval = 1; mem_free(attribute_bindings[index]); attribute_bindings[index] = NULL; if (name) { if (strncmp(name, "SV_", 3) == 0) { DLOG("Attempt to use invalid attribute name %s", name); retval = 0; } if (retval) { for (int i = 0; i < lenof(standard_attribute_names); i++) { ASSERT(standard_attribute_names[i]); if (strcmp(name, standard_attribute_names[i]) == 0) { DLOG("Attempt to use reserved attribute name %s", name); retval = 0; break; } } } if (retval) { for (int i = 0; i < num_attribute_bindings; i++) { if (attribute_bindings[i] && strcmp(attribute_bindings[i], name) == 0) { DLOG("Attempt to rebind name %s (attribute %d) to" " attribute %d", name, i, index); retval = 0; break; } } } if (retval) { attribute_bindings[index] = mem_strdup(name, 0); if (UNLIKELY(!attribute_bindings[index])) { DLOG("Failed to copy name for attribute %d: %s", index, name); retval = 0; } } } if (attribute_bindings[index]) { if (index >= num_attribute_bindings) { num_attribute_bindings = index + 1; } } else { if (index == num_attribute_bindings - 1) { while (--num_attribute_bindings > 0) { if (attribute_bindings[num_attribute_bindings - 1]) { break; } } } } return retval; } /*-----------------------------------------------------------------------*/ void d3d_sys_shader_bind_standard_attribute(ShaderAttribute attribute, int index) { standard_attribute_bindings[attribute] = 0; if (index >= 0 && index < lenof(attribute_bindings)) { mem_free(attribute_bindings[index]); attribute_bindings[index] = NULL; if (index == num_attribute_bindings - 1) { while (--num_attribute_bindings > 0) { if (attribute_bindings[num_attribute_bindings - 1]) { break; } } } ASSERT(standard_attribute_names[attribute]); attribute_bindings[index] = mem_strdup(standard_attribute_names[attribute], 0); if (UNLIKELY(!attribute_bindings[index])) { DLOG("Failed to copy name for standard attribute %s", standard_attribute_names[attribute]); return; } if (index >= num_attribute_bindings) { num_attribute_bindings = index + 1; } standard_attribute_bindings[attribute] = index + 1; } } /*-----------------------------------------------------------------------*/ void d3d_sys_shader_clear_attributes(void) { for (int i = 0; i < num_attribute_bindings; i++) { mem_free(attribute_bindings[i]); attribute_bindings[i] = NULL; } num_attribute_bindings = 0; for (int i = 0; i < lenof(standard_attribute_bindings); i++) { standard_attribute_bindings[i] = 0; } } /*-----------------------------------------------------------------------*/ D3DSysShader *d3d_sys_shader_create(ShaderType type, const void *data, int size, int is_binary) { void *compiled_data = NULL; int compiled_size; if (!is_binary) { const char *target; if (type == SHADER_TYPE_VERTEX) { target = "vs_4_0_level_9_1"; } else { ASSERT(type == SHADER_TYPE_FRAGMENT); target = "ps_4_0_level_9_1"; } compiled_data = compile_shader(data, size, target, &compiled_size); if (!compiled_data) { DLOG("Failed to compile shader"); goto error_return; } data = compiled_data; size = compiled_size; } else if (save_shader_binaries) { compiled_data = mem_alloc(size, 0, 0); if (!compiled_data) { DLOG("Failed to make copy of shader data"); goto error_return; } memcpy(compiled_data, data, size); compiled_size = size; } D3DSysShader *shader = mem_alloc(sizeof(*shader), 0, 0); if (UNLIKELY(!shader)) { DLOG("No memory for shader object"); goto error_free_compiled_data; } shader->generation = d3d_device_generation; shader->type = type; shader->pipelines = NULL; if (save_shader_binaries) { shader->binary_data = compiled_data; shader->binary_size = compiled_size; } else { shader->binary_data = NULL; shader->binary_size = 0; } HRESULT result; if (type == SHADER_TYPE_VERTEX) { result = ID3D11Device_CreateVertexShader( d3d_device, data, size, NULL, &shader->vs); } else { ASSERT(type == SHADER_TYPE_FRAGMENT); result = ID3D11Device_CreatePixelShader( d3d_device, data, size, NULL, &shader->ps); } if (result != S_OK) { DLOG("Failed to create D3D shader object: %s", d3d_strerror(result)); goto error_free_shader; } /* We explicitly declare the IIDs here for two reasons: (1) MinGW * headers only include the d3dcompiler_43.dll IID, which doesn't * work in newer versions of the library, and (2) if we are using * d3dcompiler_43.dll (such as on Windows 7), we have to use the old * IID instead of the newer one. This hardcoding of IIDs and use of * old IIDs with newer headers is a Bad Idea in principle, but there * doesn't seem to be any material difference in the specific * interfaces we use. */ static const GUID IID_ID3D11ShaderReflection_47 = { 0x8D536CA1, 0x0CCA, 0x4956, {0xA8,0x37,0x78,0x69,0x63,0x75,0x55,0x84}}; static const GUID IID_ID3D11ShaderReflection_43 = { 0x0A233719, 0x3960, 0x4578, {0x9D,0x7C,0x20,0x3B,0x8B,0x1D,0x9C,0xC1}}; const GUID *iid; if (d3dcompiler_name && strcmp(d3dcompiler_name, "d3dcompiler_43.dll") == 0) { iid = &IID_ID3D11ShaderReflection_43; } else { /* Interestingly, this also works fine in the d3dcompiler_46.dll * shipped with the Windows 8.0 SDK, even though the d3d11shader.h * in that SDK defines a different IID. */ iid = &IID_ID3D11ShaderReflection_47; } result = (*p_D3DReflect)(data, size, iid, (void **)&shader->reflect); if (result != S_OK) { DLOG("Failed to create ID3D11ShaderReflection object: %s", d3d_strerror(result)); goto error_release_d3d_shader; } shader->uniforms_reflect = NULL; shader->uniforms_index = -1; D3D11_SHADER_BUFFER_DESC uniforms_desc; for (int i = 0; i < D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT; i++) { ID3D11ShaderReflectionConstantBuffer *info = ID3D11ShaderReflection_GetConstantBufferByIndex(shader->reflect, i); if (info) { result = ID3D11ShaderReflectionConstantBuffer_GetDesc( info, &uniforms_desc); if (result != S_OK) { if (result != E_FAIL) { // Just means there's no such buffer. DLOG("Failed to get info for constant buffer %d: %s", i, d3d_strerror(result)); } } else if (strcmp(uniforms_desc.Name, "uniforms") == 0) { // FIXME: can we use $Global? - https://msdn.microsoft.com/en-us/library/windows/desktop/bb509581(v=vs.85).aspx shader->uniforms_reflect = info; shader->uniforms_index = i; break; } } } if (shader->uniforms_reflect) { D3D11_BUFFER_DESC buffer_desc = { .ByteWidth = uniforms_desc.Size, .Usage = D3D11_USAGE_DYNAMIC, .BindFlags = D3D11_BIND_CONSTANT_BUFFER, .CPUAccessFlags = D3D11_CPU_ACCESS_WRITE, .MiscFlags = 0, .StructureByteStride = 0, }; result = ID3D11Device_CreateBuffer( d3d_device, &buffer_desc, NULL, &shader->uniforms); if (result != S_OK) { DLOG("Failed to create uniform buffer: %s", d3d_strerror(result)); goto error_release_reflect; } } else { shader->uniforms = NULL; } if (!save_shader_binaries) { mem_free(compiled_data); } return shader; error_release_reflect: ID3D11ShaderReflection_Release(shader->reflect); error_release_d3d_shader: if (shader->type == SHADER_TYPE_VERTEX) { ID3D11VertexShader_Release(shader->vs); } else { ASSERT(shader->type == SHADER_TYPE_FRAGMENT); ID3D11PixelShader_Release(shader->ps); } error_free_shader: mem_free(shader); error_free_compiled_data: mem_free(compiled_data); error_return: return NULL; } /*-----------------------------------------------------------------------*/ void d3d_sys_shader_destroy(D3DSysShader *shader) { for (D3DSysShaderPipeline *pipeline = shader->pipelines, *next; pipeline; pipeline = next) { if (shader->type == SHADER_TYPE_VERTEX) { next = pipeline->vertex_next; pipeline->vertex_next = NULL; pipeline->vertex_prev_ptr = NULL; pipeline->vertex_shader = NULL; } else { ASSERT(shader->type == SHADER_TYPE_FRAGMENT, return); next = pipeline->pixel_next; pipeline->pixel_next = NULL; pipeline->pixel_prev_ptr = NULL; pipeline->pixel_shader = NULL; } } if (shader->generation == d3d_device_generation) { ID3D11ShaderReflection_Release(shader->reflect); if (shader->uniforms) { ID3D11Buffer_Release(shader->uniforms); } if (shader->type == SHADER_TYPE_VERTEX) { ID3D11VertexShader_Release(shader->vs); } else { ASSERT(shader->type == SHADER_TYPE_FRAGMENT); ID3D11PixelShader_Release(shader->ps); } } mem_free(shader->binary_data); mem_free(shader); } /*-----------------------------------------------------------------------*/ void *d3d_sys_shader_get_binary(D3DSysShader *shader, int *size_ret) { if (UNLIKELY(shader->generation != d3d_device_generation)) { DLOG("Attempt to use invalidated shader %p", shader); return NULL; } void *buffer = mem_alloc(shader->binary_size, 0, 0); if (UNLIKELY(!buffer)) { DLOG("Failed to allocate buffer for shader data (%d bytes)", shader->binary_size); return NULL; } *size_ret = shader->binary_size; return shader->binary_data; } /*-----------------------------------------------------------------------*/ void *d3d_sys_shader_compile(ShaderType type, const char *source, int length, int *size_ret) { const char *target; if (type == SHADER_TYPE_VERTEX) { target = "vs_4_0_level_9_1"; } else { ASSERT(type == SHADER_TYPE_FRAGMENT); target = "ps_4_0_level_9_1"; } return compile_shader(source, length, target, size_ret); } /*-----------------------------------------------------------------------*/ int d3d_sys_shader_get_uniform_id(D3DSysShader *shader, const char *name) { if (UNLIKELY(shader->generation != d3d_device_generation)) { DLOG("Attempt to use invalidated shader %p", shader); return 0; } if (!shader->uniforms_reflect) { return 0; } ID3D11ShaderReflectionVariable *var_info = ID3D11ShaderReflectionConstantBuffer_GetVariableByName( shader->uniforms_reflect, name); /* GetVariableByName() is documented to return a sentinel object if * the variable is not found. */ ASSERT(var_info, return 0); D3D11_SHADER_VARIABLE_DESC desc; HRESULT result = ID3D11ShaderReflectionVariable_GetDesc(var_info, &desc); if (result != S_OK) { if (result == E_FAIL) { DLOG("Uniform \"%s\" not found in shader %p", name, shader); } else { DLOG("ID3D11ShaderReflectionVariable::GetDesc() failed: %s", d3d_strerror(result)); } return 0; } /* Use the buffer offset as the uniform ID, but add 1 since 0 is a * failure return from this function. */ return 1 + desc.StartOffset; } /*-----------------------------------------------------------------------*/ void d3d_sys_shader_set_uniform_int(D3DSysShader *shader, int uniform, int value) { if (UNLIKELY(shader->generation != d3d_device_generation)) { DLOG("Attempt to use invalidated shader %p", shader); return; } set_uniform_common(shader, uniform-1, &value, 0); } /*-----------------------------------------------------------------------*/ void d3d_sys_shader_set_uniform_float( D3DSysShader *shader, int uniform, float value) { if (UNLIKELY(shader->generation != d3d_device_generation)) { DLOG("Attempt to use invalidated shader %p", shader); return; } set_uniform_common(shader, uniform-1, &value, 1); } /*-----------------------------------------------------------------------*/ void d3d_sys_shader_set_uniform_vec2( D3DSysShader *shader, int uniform, const Vector2f *value) { if (UNLIKELY(shader->generation != d3d_device_generation)) { DLOG("Attempt to use invalidated shader %p", shader); return; } set_uniform_common(shader, uniform-1, value, 2); } /*-----------------------------------------------------------------------*/ void d3d_sys_shader_set_uniform_vec3( D3DSysShader *shader, int uniform, const Vector3f *value) { if (UNLIKELY(shader->generation != d3d_device_generation)) { DLOG("Attempt to use invalidated shader %p", shader); return; } set_uniform_common(shader, uniform-1, value, 3); } /*-----------------------------------------------------------------------*/ void d3d_sys_shader_set_uniform_vec4( D3DSysShader *shader, int uniform, const Vector4f *value) { if (UNLIKELY(shader->generation != d3d_device_generation)) { DLOG("Attempt to use invalidated shader %p", shader); return; } set_uniform_common(shader, uniform-1, value, 4); } /*-----------------------------------------------------------------------*/ void d3d_sys_shader_set_uniform_mat4( D3DSysShader *shader, int uniform, const Matrix4f *value) { if (UNLIKELY(shader->generation != d3d_device_generation)) { DLOG("Attempt to use invalidated shader %p", shader); return; } set_uniform_common(shader, uniform-1, value, 16); } /*************************************************************************/ /***************** Interface: Shader pipeline management *****************/ /*************************************************************************/ D3DSysShaderPipeline *d3d_sys_shader_pipeline_create( D3DSysShader *vertex_shader, D3DSysShader *fragment_shader) { if (UNLIKELY(vertex_shader->type != SHADER_TYPE_VERTEX)) { DLOG("Invalid type %d for vertex shader", vertex_shader->type); goto error_return; } if (UNLIKELY(fragment_shader->type != SHADER_TYPE_FRAGMENT)) { DLOG("Invalid type %d for fragment shader", fragment_shader->type); goto error_return; } if (UNLIKELY(vertex_shader->generation != d3d_device_generation)) { DLOG("Attempt to use invalidated vertex shader %p", vertex_shader); goto error_return; } if (UNLIKELY(fragment_shader->generation != d3d_device_generation)) { DLOG("Attempt to use invalidated fragment shader %p", fragment_shader); goto error_return; } D3DSysShaderPipeline *pipeline = mem_alloc(sizeof(*pipeline), 0, MEM_ALLOC_CLEAR); if (UNLIKELY(!pipeline)) { DLOG("No memory for pipeline object"); goto error_return; } pipeline->generation = d3d_device_generation; pipeline->vertex_shader = vertex_shader; pipeline->pixel_shader = fragment_shader; pipeline->vertex_next = vertex_shader->pipelines; pipeline->vertex_prev_ptr = &vertex_shader->pipelines; if (vertex_shader->pipelines) { vertex_shader->pipelines->vertex_prev_ptr = &pipeline->vertex_next; } vertex_shader->pipelines = pipeline; pipeline->pixel_next = fragment_shader->pipelines; pipeline->pixel_prev_ptr = &fragment_shader->pipelines; if (fragment_shader->pipelines) { fragment_shader->pipelines->pixel_prev_ptr = &pipeline->pixel_next; } fragment_shader->pipelines = pipeline; return pipeline; error_return: return NULL; } /*-----------------------------------------------------------------------*/ void d3d_sys_shader_pipeline_destroy(D3DSysShaderPipeline *pipeline) { if (current_pipeline == pipeline) { d3d_sys_shader_pipeline_apply(NULL); } if (pipeline->vertex_next) { pipeline->vertex_next->vertex_prev_ptr = pipeline->vertex_prev_ptr; } if (pipeline->vertex_prev_ptr) { *pipeline->vertex_prev_ptr = pipeline->vertex_next; } if (pipeline->pixel_next) { pipeline->pixel_next->pixel_prev_ptr = pipeline->pixel_prev_ptr; } if (pipeline->pixel_prev_ptr) { *pipeline->pixel_prev_ptr = pipeline->pixel_next; } mem_free(pipeline); } /*-----------------------------------------------------------------------*/ void d3d_sys_shader_pipeline_apply(D3DSysShaderPipeline *pipeline) { if (pipeline && UNLIKELY(pipeline->generation != d3d_device_generation)) { DLOG("Attempt to use invalidated shader pipeline %p", pipeline); return; } if (!pipeline) { ID3D11DeviceContext_VSSetShader(d3d_context, NULL, NULL, 0); ID3D11DeviceContext_PSSetShader(d3d_context, NULL, NULL, 0); } else { ID3D11DeviceContext_VSSetShader( d3d_context, pipeline->vertex_shader->vs, NULL, 0); if (pipeline->vertex_shader->uniforms_index >= 0) { ID3D11DeviceContext_VSSetConstantBuffers( d3d_context, pipeline->vertex_shader->uniforms_index, 1, &pipeline->vertex_shader->uniforms); } ID3D11DeviceContext_PSSetShader( d3d_context, pipeline->pixel_shader->ps, NULL, 0); if (pipeline->pixel_shader->uniforms_index >= 0) { ID3D11DeviceContext_PSSetConstantBuffers( d3d_context, pipeline->pixel_shader->uniforms_index, 1, &pipeline->pixel_shader->uniforms); } gen_shaders_installed = 0; } current_pipeline = pipeline; } /*************************************************************************/ /****** Internal interface routines (exposed to other Windows code) ******/ /*************************************************************************/ void d3d_shader_set_debug_info(int enable) { debug_flag = enable ? D3DCOMPILE_DEBUG : 0; } /*-----------------------------------------------------------------------*/ void d3d_shader_set_opt_level(int level) { switch (level) { case 0: /* Unclear whether these have different effects, but it can't hurt * to set them both... */ opt_flag = D3DCOMPILE_OPTIMIZATION_LEVEL0 | D3DCOMPILE_SKIP_OPTIMIZATION; break; case 1: opt_flag = D3DCOMPILE_OPTIMIZATION_LEVEL1; break; case 2: opt_flag = D3DCOMPILE_OPTIMIZATION_LEVEL2; break; case 3: opt_flag = D3DCOMPILE_OPTIMIZATION_LEVEL3; break; default: ASSERT(!"Invalid optimization level"); } } /*-----------------------------------------------------------------------*/ char *d3d_compile_default_shaders(void) { PRECOND(p_D3DCompile, return NULL); const UINT saved_debug_flag = debug_flag; const UINT saved_opt_flag = opt_flag; debug_flag = 0; opt_flag = D3DCOMPILE_OPTIMIZATION_LEVEL3; ID3DBlob *default_vs[3][5][3][2]; ID3DBlob *default_ps[5][3][2][5]; memset(default_vs, 0, sizeof(default_vs)); memset(default_ps, 0, sizeof(default_ps)); STATIC_ASSERT(lenof(default_vs) == lenof(default_vs_cache), "Wrong dimensions on default_vs"); STATIC_ASSERT(lenof(default_vs[0]) == lenof(default_vs_cache[0]), "Wrong dimensions on default_vs"); STATIC_ASSERT(lenof(default_vs[0][0]) == lenof(default_vs_cache[0][0]), "Wrong dimensions on default_vs"); STATIC_ASSERT(lenof(default_vs[0][0][0]) == lenof(default_vs_cache[0][0][0]), "Wrong dimensions on default_vs"); STATIC_ASSERT(lenof(default_ps) == lenof(default_ps_cache), "Wrong dimensions on default_ps"); STATIC_ASSERT(lenof(default_ps[0]) == lenof(default_ps_cache[0]), "Wrong dimensions on default_ps"); STATIC_ASSERT(lenof(default_ps[0][0]) == lenof(default_ps_cache[0][0]), "Wrong dimensions on default_ps"); STATIC_ASSERT(lenof(default_ps[0][0][0]) == lenof(default_ps_cache[0][0][0]), "Wrong dimensions on default_ps"); for (int position_count = 2; position_count <= 4; position_count++) { for (int has_texcoord = 0; has_texcoord <= 1; has_texcoord++) { for (int tex_is_fb = 0; tex_is_fb <= 1; tex_is_fb++) { for (int tex_offset = 0; tex_offset <= 1; tex_offset++) { for (int has_color = 0; has_color <= 1; has_color++) { for (int color_uniform = 0; color_uniform <= 1; color_uniform++){ for (int fog = 0; fog <= 1; fog++) { ID3DBlob **blob_ptr = &DEFAULT_VS_LOOKUP( default_vs, position_count, has_texcoord, has_color, tex_is_fb, tex_offset, color_uniform, fog); if (!*blob_ptr) { ID3DBlob *shader = compile_default_vs( position_count, has_texcoord, has_color, tex_is_fb, tex_offset, color_uniform, fog); if (UNLIKELY(!shader)) { DLOG("Failed to compile vertex shader for:" " position_count=%d has_texcoord=%d" " tex_is_fb=%d tex_offset=%d has_color=%d" " color_uniform=%d fog=%d", position_count, has_texcoord, tex_is_fb, tex_offset, has_color, color_uniform, fog); goto error_free_blobs; } *blob_ptr = shader; } } } } } } } } for (int has_texcoord = 0; has_texcoord <= 1; has_texcoord++) { for (int has_color = 0; has_color <= 1; has_color++) { for (int texcolor_type = TEXCOLOR_RGBA; texcolor_type <= TEXCOLOR_L; texcolor_type++) { for (int color_uniform = 0; color_uniform <= 1; color_uniform++) { for (int fog = 0; fog <= 1; fog++) { for (int alpha_test = 0; alpha_test <= 1; alpha_test++) { for (int alpha_comparison = GRAPHICS_COMPARISON_LESS; alpha_comparison <= GRAPHICS_COMPARISON_GREATER; alpha_comparison++) { ID3DBlob **blob_ptr = &DEFAULT_PS_LOOKUP( default_ps, has_texcoord, has_color, texcolor_type, color_uniform, fog, alpha_test, alpha_comparison); if (!*blob_ptr) { ID3DBlob *shader = compile_default_ps( has_texcoord, has_color, texcolor_type, color_uniform, fog, alpha_test, alpha_comparison); if (UNLIKELY(!shader)) { DLOG("Failed to compile pixel shader for:" " has_texcoord=%d has_color=%d" " texcolor_type=%d color_uniform=%d fog=%d" " alpha_test=%d alpha_comparison=%d", has_texcoord, has_color, texcolor_type, color_uniform, fog, alpha_test, alpha_comparison); goto error_free_blobs; } *blob_ptr = shader; } } } } } } } } char *outbuf = NULL; int outsize = 0; #define APPEND(...) do { \ if (UNLIKELY(!strformat_append(&outbuf, &outsize, 0, __VA_ARGS__))) { \ goto error_free_outbuf; \ } \ } while (0) APPEND("typedef struct D3DPrecompiledShader {\n" " int size;\n" " const char *data;\n" "} D3DPrecompiledShader;\n"); APPEND("\nstatic const D3DPrecompiledShader default_vs_bytecode[%d][%d][%d][%d] = {\n", lenof(default_vs), lenof(default_vs[0]), lenof(default_vs[0][0]), lenof(default_vs[0][0][0])); for (int i = 0; i < lenof(default_vs); i++) { APPEND(" {\n"); for (int j = 0; j < lenof(default_vs[0]); j++) { APPEND(" {\n"); for (int k = 0; k < lenof(default_vs[0][0]); k++) { APPEND(" {\n"); for (int l = 0; l < lenof(default_vs[0][0][0]); l++) { ID3DBlob *blob = default_vs[i][j][k][l]; ASSERT(blob, return NULL); const uint8_t *data = ID3D10Blob_GetBufferPointer(blob); const int len = ID3D10Blob_GetBufferSize(blob); APPEND("\n // vs[%d][%d][%d][%d]\n {%d,\n \"", i, j, k, l, len); for (int pos = 0; pos < len; pos++) { APPEND("\\x%02X", data[pos]); if (pos < len-1 && pos%16 == 15) { APPEND("\"\n \""); } } APPEND("\"},\n"); ID3D10Blob_Release(blob); } APPEND(" },\n"); } APPEND(" },\n"); } APPEND(" },\n"); } APPEND("};\n"); APPEND("\nstatic const D3DPrecompiledShader default_ps_bytecode[%d][%d][%d][%d] = {\n", lenof(default_ps), lenof(default_ps[0]), lenof(default_ps[0][0]), lenof(default_ps[0][0][0])); for (int i = 0; i < lenof(default_ps); i++) { APPEND(" {\n"); for (int j = 0; j < lenof(default_ps[0]); j++) { APPEND(" {\n"); for (int k = 0; k < lenof(default_ps[0][0]); k++) { APPEND(" {\n"); for (int l = 0; l < lenof(default_ps[0][0][0]); l++) { ID3DBlob *blob = default_ps[i][j][k][l]; ASSERT(blob, return NULL); const uint8_t *data = ID3D10Blob_GetBufferPointer(blob); const int len = ID3D10Blob_GetBufferSize(blob); APPEND("\n // ps[%d][%d][%d][%d]\n {%d,\n \"", i, j, k, l, len); for (int pos = 0; pos < len; pos++) { APPEND("\\x%02X", data[pos]); if (pos < len-1 && pos%16 == 15) { APPEND("\"\n \""); } } APPEND("\"},\n"); ID3D10Blob_Release(blob); } APPEND(" },\n"); } APPEND(" },\n"); } APPEND(" },\n"); } APPEND("};\n"); #undef APPEND debug_flag = saved_debug_flag; opt_flag = saved_opt_flag; return outbuf; error_free_outbuf: mem_free(outbuf); error_free_blobs: for (int i = 0; i < lenof(default_vs); i++) { for (int j = 0; j < lenof(default_vs[0]); j++) { for (int k = 0; k < lenof(default_vs[0][0]); k++) { for (int l = 0; l < lenof(default_vs[0][0][0]); l++) { ID3DBlob *blob = default_vs[i][j][k][l]; if (blob) { ID3D10Blob_Release(blob); } } } } } for (int i = 0; i < lenof(default_ps); i++) { for (int j = 0; j < lenof(default_ps[0]); j++) { for (int k = 0; k < lenof(default_ps[0][0]); k++) { for (int l = 0; l < lenof(default_ps[0][0][0]); l++) { ID3DBlob *blob = default_ps[i][j][k][l]; if (blob) { ID3D10Blob_Release(blob); } } } } } debug_flag = saved_debug_flag; opt_flag = saved_opt_flag; return NULL; } /*************************************************************************/ /******** Internal interface routines (private to Direct3D code) *********/ /*************************************************************************/ void d3d_shader_init(void) { d3d_shader_objects_enabled = 0; current_pipeline = NULL; save_shader_binaries = 0; gen_shaders_installed = 0; debug_flag = 0; opt_flag = D3DCOMPILE_OPTIMIZATION_LEVEL1; } /*-----------------------------------------------------------------------*/ void d3d_shader_cleanup(void) { for (int i = 0; i < num_attribute_bindings; i++) { mem_free(attribute_bindings[i]); attribute_bindings[i] = NULL; } num_attribute_bindings = 0; if (default_vs_uniforms) { ID3D11Buffer_Release(default_vs_uniforms); default_vs_uniforms = NULL; } if (default_ps_uniforms) { ID3D11Buffer_Release(default_ps_uniforms); default_ps_uniforms = NULL; } for (int i = 0; i < lenof(default_vs_cache); i++) { for (int j = 0; j < lenof(default_vs_cache[0]); j++) { for (int k = 0; k < lenof(default_vs_cache[0][0]); k++) { for (int l = 0; l < lenof(default_vs_cache[0][0][0]); l++) { ID3D11VertexShader **ptr = &default_vs_cache[i][j][k][l]; if (*ptr) { ID3D11VertexShader_Release(*ptr); *ptr = NULL; } } } } } for (int i = 0; i < lenof(default_ps_cache); i++) { for (int j = 0; j < lenof(default_ps_cache[0]); j++) { for (int k = 0; k < lenof(default_ps_cache[0][0]); k++) { for (int l = 0; l < lenof(default_ps_cache[0][0][0]); l++) { ID3D11PixelShader **ptr = &default_ps_cache[i][j][k][l]; if (*ptr) { ID3D11PixelShader_Release(*ptr); *ptr = NULL; } } } } } } /*-----------------------------------------------------------------------*/ int d3d_apply_default_shader( const D3DSysPrimitive *primitive, int tex_offset, int color_uniform, int fog, int alpha_test, GraphicsComparisonType alpha_comparison) { /* Craete uniform buffers if they don't yet exist. */ if (!default_vs_uniforms) { const D3D11_BUFFER_DESC desc = { .ByteWidth = align_up(sizeof(D3DVertexUniformBlock), 16), .Usage = D3D11_USAGE_DYNAMIC, .BindFlags = D3D11_BIND_CONSTANT_BUFFER, .CPUAccessFlags = D3D11_CPU_ACCESS_WRITE, .MiscFlags = 0, .StructureByteStride = 0, }; HRESULT result = ID3D11Device_CreateBuffer( d3d_device, &desc, NULL, &default_vs_uniforms); if (UNLIKELY(result != S_OK)) { DLOG("Failed to create VS uniform buffer (%d bytes): %s", (int)desc.ByteWidth, d3d_strerror(result)); return 0; } } if (!default_ps_uniforms) { const D3D11_BUFFER_DESC desc = { .ByteWidth = align_up(sizeof(D3DPixelUniformBlock), 16), .Usage = D3D11_USAGE_DYNAMIC, .BindFlags = D3D11_BIND_CONSTANT_BUFFER, .CPUAccessFlags = D3D11_CPU_ACCESS_WRITE, .MiscFlags = 0, .StructureByteStride = 0, }; HRESULT result = ID3D11Device_CreateBuffer( d3d_device, &desc, NULL, &default_ps_uniforms); if (UNLIKELY(result != S_OK)) { DLOG("Failed to create PS uniform buffer (%d bytes): %s", (int)desc.ByteWidth, d3d_strerror(result)); return 0; } } /* Generate shaders (or fetch from cache). */ const D3DSysTexture *texture = d3d_get_current_texture(); ID3D11VertexShader *vs = get_default_vs( primitive->position_count, primitive->has_texcoord, primitive->has_color, texture ? texture->is_framebuffer : 0, tex_offset, color_uniform, fog); if (UNLIKELY(!vs)) { return 0; } if (alpha_test) { if (UNLIKELY(alpha_comparison != GRAPHICS_COMPARISON_LESS && alpha_comparison != GRAPHICS_COMPARISON_LESS_EQUAL && alpha_comparison != GRAPHICS_COMPARISON_GREATER_EQUAL && alpha_comparison != GRAPHICS_COMPARISON_GREATER)) { DLOG("Ignoring invalid alpha comparison %d", alpha_comparison); alpha_test = 0; } } ID3D11PixelShader *ps = get_default_ps( primitive->has_texcoord, primitive->has_color, texture ? texture->color_type : 0, color_uniform, fog, alpha_test, alpha_comparison); if (UNLIKELY(!ps)) { return 0; } /* Install generated shaders. */ ID3D11DeviceContext_VSSetShader(d3d_context, vs, NULL, 0); ID3D11DeviceContext_PSSetShader(d3d_context, ps, NULL, 0); current_pipeline = NULL; if (!gen_shaders_installed) { ID3D11DeviceContext_VSSetConstantBuffers( d3d_context, 0, 1, &default_vs_uniforms); ID3D11DeviceContext_PSSetConstantBuffers( d3d_context, 0, 1, &default_ps_uniforms); gen_shaders_installed = 1; } return 1; } /*-----------------------------------------------------------------------*/ void d3d_set_default_vs_uniforms(const D3DVertexUniformBlock *vs_data) { ASSERT(default_vs_uniforms != NULL, return); D3D11_MAPPED_SUBRESOURCE map; HRESULT result = ID3D11DeviceContext_Map( d3d_context, (ID3D11Resource *)default_vs_uniforms, 0, D3D11_MAP_WRITE_DISCARD, 0, &map); if (UNLIKELY(result != S_OK)) { DLOG("Failed to map VS uniform buffer: %s", d3d_strerror(result)); return; } memcpy(map.pData, vs_data, sizeof(*vs_data)); ID3D11DeviceContext_Unmap( d3d_context, (ID3D11Resource *)default_vs_uniforms, 0); } /*-----------------------------------------------------------------------*/ void d3d_set_default_ps_uniforms(const D3DPixelUniformBlock *ps_data) { ASSERT(default_ps_uniforms != NULL, return); D3D11_MAPPED_SUBRESOURCE map; HRESULT result = ID3D11DeviceContext_Map( d3d_context, (ID3D11Resource *)default_ps_uniforms, 0, D3D11_MAP_WRITE_DISCARD, 0, &map); if (UNLIKELY(result != S_OK)) { DLOG("Failed to map PS uniform buffer: %s", d3d_strerror(result)); return; } memcpy(map.pData, ps_data, sizeof(*ps_data)); ID3D11DeviceContext_Unmap( d3d_context, (ID3D11Resource *)default_ps_uniforms, 0); } /*************************************************************************/ /**************************** Local routines *****************************/ /*************************************************************************/ static void *compile_shader(const void *source, int source_size, const char *target, int *data_size_ret) { UINT flags = debug_flag | opt_flag | D3DCOMPILE_PACK_MATRIX_ROW_MAJOR | D3DCOMPILE_ENABLE_STRICTNESS | D3DCOMPILE_IEEE_STRICTNESS | D3DCOMPILE_WARNINGS_ARE_ERRORS; ID3DBlob *output_blob = NULL, *errors_blob = NULL; HRESULT result = (*p_D3DCompile)( source, source_size, NULL, NULL, NULL, "main", target, flags, 0, &output_blob, &errors_blob); char *errors = errors_blob ? ID3D10Blob_GetBufferPointer(errors_blob) : NULL; if (errors) { int errors_size = ID3D10Blob_GetBufferSize(errors_blob); char *s; for (s = errors; s < errors + errors_size; s++) { if (!*s) { break; } else if (*s == '\r') { memmove(s, s+1, errors_size - ((s+1)-errors)); errors_size--; s--; } } if (s > errors && s[-1] == '\n') { s--; } ASSERT(s < errors + errors_size); *s = '\0'; } void *retbuf = NULL; if (result != S_OK) { DLOG("Shader compile failed (%s). Error log follows:\n%s", d3d_strerror(result), errors ? errors : "(no errors reported)"); } else if (!output_blob) { DLOG("Shader compile succeeded but no data returned! Error log" " follows:\n%s", errors ? errors : "(no errors reported)"); } else if (!data_size_ret) { retbuf = output_blob; } else { const void *output = ID3D10Blob_GetBufferPointer(output_blob); const int size = ID3D10Blob_GetBufferSize(output_blob); retbuf = mem_alloc(size, 0, 0); if (UNLIKELY(!retbuf)) { DLOG("Failed to allocate memory for compiled shader data (%d" " bytes)", size); } else { ASSERT(output != NULL, return NULL); memcpy(retbuf, output, size); *data_size_ret = size; } ID3D10Blob_Release(output_blob); } if (errors_blob) { ID3D10Blob_Release(errors_blob); } return retbuf; } /*-----------------------------------------------------------------------*/ static void set_uniform_common( D3DSysShader *shader, int offset, const void *data, int num_floats) { PRECOND(shader != NULL); PRECOND(shader->uniforms != NULL); PRECOND(offset >= 0); PRECOND(data != NULL); D3D11_MAPPED_SUBRESOURCE map; HRESULT result = ID3D11DeviceContext_Map( d3d_context, (ID3D11Resource *)shader->uniforms, 0, // FIXME: WRITE_DISCARD works for safe_clear shader, won't work for general shaders but we can't map DYNAMIC buffers read+write -- need local mirror buffer? D3D11_MAP_WRITE_DISCARD, 0, &map); if (UNLIKELY(result != S_OK)) { DLOG("Failed to map uniforms buffer: %s", d3d_strerror(errno)); return; } int size; uint32_t int_buffer; if (num_floats > 0) { size = 4 * num_floats; } else { int_buffer = (uint32_t) *((int *)data); data = &int_buffer; size = 4; } memcpy((char *)map.pData + offset, data, size); ID3D11DeviceContext_Unmap( d3d_context, (ID3D11Resource *)shader->uniforms, 0); } /*-----------------------------------------------------------------------*/ static ID3D11VertexShader *get_default_vs( int position_count, int has_texcoord, int has_color, int tex_is_fb, int tex_offset, int color_uniform, int fog) { PRECOND(position_count >= 2 && position_count <= 4); PRECOND(has_texcoord == 0 || has_texcoord == 1); PRECOND(has_color == 0 || has_color == 1); PRECOND(tex_is_fb == 0 || tex_is_fb == 1); PRECOND(tex_offset == 0 || tex_offset == 1); PRECOND(color_uniform == 0 || color_uniform == 1); PRECOND(fog == 0 || fog == 1); ID3D11VertexShader **cache_ptr = &DEFAULT_VS_LOOKUP( default_vs_cache, position_count, has_texcoord, has_color, tex_is_fb, tex_offset, color_uniform, fog); if (*cache_ptr) { return *cache_ptr; } ID3D11VertexShader *vs; HRESULT result; #ifdef SIL_PLATFORM_WINDOWS_PRECOMPILED_D3D_SHADERS const D3DPrecompiledShader *data = &DEFAULT_VS_LOOKUP( default_vs_bytecode, position_count, has_texcoord, has_color, tex_is_fb, tex_offset, color_uniform, fog); result = ID3D11Device_CreateVertexShader( d3d_device, data->data, data->size, NULL, &vs); #else ID3DBlob *blob = compile_default_vs( position_count, has_texcoord, has_color, tex_is_fb, tex_offset, color_uniform, fog); if (!blob) { return NULL; } result = ID3D11Device_CreateVertexShader( d3d_device, ID3D10Blob_GetBufferPointer(blob), ID3D10Blob_GetBufferSize(blob), NULL, &vs); ID3D10Blob_Release(blob); #endif if (result != S_OK) { DLOG("Failed to create vertex shader: %s", d3d_strerror(result)); return NULL; } *cache_ptr = vs; return vs; } /*-----------------------------------------------------------------------*/ static ID3D11PixelShader *get_default_ps( int has_texcoord, int has_color, int texcolor_type, int color_uniform, int fog, int alpha_test, GraphicsComparisonType alpha_comparison) { PRECOND(has_texcoord == 0 || has_texcoord == 1); PRECOND(has_color == 0 || has_color == 1); PRECOND(texcolor_type >= 0 && texcolor_type <= 4); PRECOND(color_uniform == 0 || color_uniform == 1); PRECOND(fog == 0 || fog == 1); PRECOND(!alpha_test || alpha_comparison == GRAPHICS_COMPARISON_LESS || alpha_comparison == GRAPHICS_COMPARISON_LESS_EQUAL || alpha_comparison == GRAPHICS_COMPARISON_GREATER_EQUAL || alpha_comparison == GRAPHICS_COMPARISON_GREATER); ID3D11PixelShader **cache_ptr = &DEFAULT_PS_LOOKUP( default_ps_cache, has_texcoord, has_color, texcolor_type, color_uniform, fog, alpha_test, alpha_comparison); if (*cache_ptr) { return *cache_ptr; } ID3D11PixelShader *ps; HRESULT result; #ifdef SIL_PLATFORM_WINDOWS_PRECOMPILED_D3D_SHADERS const D3DPrecompiledShader *data = &DEFAULT_PS_LOOKUP( default_ps_bytecode, has_texcoord, has_color, texcolor_type, color_uniform, fog, alpha_test, alpha_comparison); result = ID3D11Device_CreatePixelShader( d3d_device, data->data, data->size, NULL, &ps); #else ID3DBlob *blob = compile_default_ps( has_texcoord, has_color, texcolor_type, color_uniform, fog, alpha_test, alpha_comparison); if (!blob) { return NULL; } result = ID3D11Device_CreatePixelShader( d3d_device, ID3D10Blob_GetBufferPointer(blob), ID3D10Blob_GetBufferSize(blob), NULL, &ps); ID3D10Blob_Release(blob); #endif if (result != S_OK) { DLOG("Failed to create pixel shader: %s", d3d_strerror(result)); return NULL; } *cache_ptr = ps; return ps; } /*-----------------------------------------------------------------------*/ #define ADD_LINE(s) \ (source_len += strformat(source+source_len, \ sizeof(source)-source_len, "%s\n", (s))) static ID3DBlob *compile_default_vs( int position_count, int has_texcoord, int has_color, int tex_is_fb, int tex_offset, int color_uniform, int fog) { PRECOND(position_count >= 2 && position_count <= 4); PRECOND(has_texcoord == 0 || has_texcoord == 1); PRECOND(has_color == 0 || has_color == 1); PRECOND(tex_is_fb == 0 || tex_is_fb == 1); PRECOND(tex_offset == 0 || tex_offset == 1); PRECOND(color_uniform == 0 || color_uniform == 1); PRECOND(fog == 0 || fog == 1); if (!p_D3DCompile) { DLOG("d3dcompiler_*.dll not loaded"); return NULL; } char source[10000]; int source_len = 0; ADD_LINE("cbuffer uniforms: register(b0) {"); ADD_LINE(" float4x4 transform;"); ADD_LINE(" float4 fixed_color;"); ADD_LINE(" float4 fog_transform;"); ADD_LINE(" float2 tex_offset;"); ADD_LINE("};"); ADD_LINE("struct VARYING {"); ADD_LINE(" float4 position: SV_Position;"); if (has_texcoord) { ADD_LINE(" float2 texcoord: V_TEXCOORD;"); } if (has_color) { ADD_LINE(" float4 color: V_COLOR;"); } if (fog) { ADD_LINE(" float fog: V_FOG;"); } ADD_LINE("};"); ADD_LINE("VARYING main("); switch (position_count) { case 2: ADD_LINE(" float2 position: POSITION"); break; case 3: ADD_LINE(" float3 position: POSITION"); break; case 4: ADD_LINE(" float4 position: POSITION"); break; } if (has_texcoord) { ADD_LINE(" , float2 texcoord: TEXCOORD"); } if (has_color) { ADD_LINE(" , float4 color: COLOR"); } ADD_LINE(") {"); ADD_LINE(" VARYING output;"); switch (position_count) { case 2: ADD_LINE(" output.position = mul(float4(position, 0.0f, 1.0f), transform);"); break; case 3: ADD_LINE(" output.position = mul(float4(position, 1.0f), transform);"); break; case 4: ADD_LINE(" output.position = mul(position, transform);"); break; } if (has_texcoord) { if (tex_offset) { ADD_LINE(" output.texcoord = texcoord + tex_offset;"); } else { ADD_LINE(" output.texcoord = texcoord;"); } if (tex_is_fb) { ADD_LINE(" output.texcoord.y = 1.0f - output.texcoord.y;"); } } if (has_color) { if (color_uniform) { ADD_LINE(" output.color = color * fixed_color;"); } else { ADD_LINE(" output.color = color;"); } } if (fog) { switch (position_count) { case 2: ADD_LINE(" output.fog = dot(fog_transform, float4(position, 0.0f, 1.0f));"); break; case 3: ADD_LINE(" output.fog = dot(fog_transform, float4(position, 1.0f));"); break; case 4: ADD_LINE(" output.fog = dot(fog_transform, position);"); break; } } /* Direct3D's device space Z range is (0,+1] rather than the [-1,+1] * expected by SIL, so adjust the Z output to fit within that range. * The final addition is to force a Z value of exactly zero (which * would have originally been -1) to a nonzero value. The HLSL * documentation isn't clear on exactly where "precise" applies, so * we split the calculation up among several steps to ensure that the * final addition doesn't get optimized out. */ ADD_LINE(" float adjusted_z = (output.position.z * 0.5f) + 0.5f;"); ADD_LINE(" precise float nonzero_z = adjusted_z;"); ADD_LINE(" nonzero_z += 1.1754944e-38f;"); ADD_LINE(" output.position.z = nonzero_z;"); ADD_LINE(" return output;"); ADD_LINE("}"); ID3DBlob *blob = compile_shader(source, source_len, "vs_4_0_level_9_1", NULL); if (!blob) { DLOG("Failed to generate vertex shader. Source follows:\n%s", source); return NULL; } return blob; } /*-----------------------------------------------------------------------*/ static ID3DBlob *compile_default_ps( int has_texcoord, int has_color, int texcolor_type, int color_uniform, int fog, int alpha_test, GraphicsComparisonType alpha_comparison) { PRECOND(has_texcoord == 0 || has_texcoord == 1); PRECOND(has_color == 0 || has_color == 1); PRECOND(texcolor_type >= 0 && texcolor_type <= 4); PRECOND(color_uniform == 0 || color_uniform == 1); PRECOND(fog == 0 || fog == 1); PRECOND(!alpha_test || alpha_comparison == GRAPHICS_COMPARISON_LESS || alpha_comparison == GRAPHICS_COMPARISON_LESS_EQUAL || alpha_comparison == GRAPHICS_COMPARISON_GREATER_EQUAL || alpha_comparison == GRAPHICS_COMPARISON_GREATER); if (!p_D3DCompile) { DLOG("d3dcompiler_*.dll not loaded"); return NULL; } char source[10000]; int source_len = 0; ADD_LINE("cbuffer uniforms: register(b0) {"); ADD_LINE(" float4 fixed_color;"); ADD_LINE(" float4 fog_color;"); ADD_LINE(" float2 fog_params;"); ADD_LINE(" float alpha_ref;"); ADD_LINE("};"); if (has_texcoord) { ADD_LINE("Texture2D tex: register(t0);"); ADD_LINE("SamplerState texSampler: register(s0);"); } ADD_LINE("struct VARYING {"); ADD_LINE(" float4 position: SV_Position;"); if (has_texcoord) { ADD_LINE(" float2 texcoord: V_TEXCOORD;"); } if (has_color) { ADD_LINE(" float4 color: V_COLOR;"); } if (fog) { ADD_LINE(" float fog: V_FOG;"); } ADD_LINE("};"); ADD_LINE("float4 main(VARYING input): SV_Target"); ADD_LINE("{"); if (has_texcoord && texcolor_type) { switch (texcolor_type) { case TEXCOLOR_RGBA: ADD_LINE(" float4 sampleval = tex.Sample(texSampler, input.texcoord);"); break; case TEXCOLOR_RGB: ADD_LINE(" float4 sampleval = float4(tex.Sample(texSampler, input.texcoord).rgb, 1.0f);"); break; case TEXCOLOR_A: case TEXCOLOR_L: ADD_LINE(" float sampleval = tex.Sample(texSampler, input.texcoord).r;"); break; default: ASSERT(!"Invalid color_type"); break; } } ADD_LINE(" float4 color_out ="); if (has_color) { if (!has_texcoord || !texcolor_type) { ADD_LINE(" input.color;"); } else switch (texcolor_type) { case TEXCOLOR_RGBA: case TEXCOLOR_RGB: ADD_LINE(" sampleval * input.color;"); break; case TEXCOLOR_A: ADD_LINE(" float4(input.color.rgb, input.color.a * sampleval);"); break; case TEXCOLOR_L: ADD_LINE(" float4(input.color.rgb * sampleval, input.color.a);"); break; } } else if (color_uniform) { if (!has_texcoord || !texcolor_type) { ADD_LINE(" fixed_color;"); } else switch (texcolor_type) { case TEXCOLOR_RGBA: case TEXCOLOR_RGB: ADD_LINE(" sampleval * fixed_color;"); break; case TEXCOLOR_A: ADD_LINE(" float4(fixed_color.rgb, fixed_color.a * sampleval);"); break; case TEXCOLOR_L: ADD_LINE(" float4(fixed_color.rgb * sampleval, fixed_color.a);"); break; } } else { if (!has_texcoord || !texcolor_type) { ADD_LINE(" float4(1.0f, 1.0f, 1.0f, 1.0f);"); } else switch (texcolor_type) { case TEXCOLOR_RGBA: case TEXCOLOR_RGB: ADD_LINE(" sampleval;"); break; case TEXCOLOR_A: ADD_LINE(" float4(1.0f, 1.0f, 1.0f, sampleval);"); break; case TEXCOLOR_L: ADD_LINE(" float4(sampleval, sampleval, sampleval, 1.0f);"); break; } } if (alpha_test) { switch (alpha_comparison) { case GRAPHICS_COMPARISON_LESS: ADD_LINE(" if (color_out.a >= alpha_ref) discard;"); break; case GRAPHICS_COMPARISON_LESS_EQUAL: ADD_LINE(" if (color_out.a > alpha_ref) discard;"); break; case GRAPHICS_COMPARISON_GREATER_EQUAL: ADD_LINE(" if (color_out.a < alpha_ref) discard;"); break; case GRAPHICS_COMPARISON_GREATER: ADD_LINE(" if (color_out.a <= alpha_ref) discard;"); break; default: DLOG("WARNING: Ignoring invalid alpha comparison type %d", alpha_comparison); break; } } if (fog) { ADD_LINE(" float fog_factor = abs(input.fog) * fog_params.x - fog_params.y;"); ADD_LINE(" return lerp(color_out, float4(fog_color.rgb, color_out.a), clamp(fog_factor, 0.0, 1.0));"); } else { ADD_LINE(" return color_out;"); } ADD_LINE("}"); ID3DBlob *blob = compile_shader(source, source_len, "ps_4_0_level_9_1", NULL); if (!blob) { DLOG("Failed to generate pixel shader. Source follows:\n%s", source); return NULL; } return blob; } /*************************************************************************/ /*************************************************************************/