// Copyright (C) 2003 Dolphin Project. // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 2.0. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 2.0 for more details. // A copy of the GPL 2.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official SVN repository and contact information can be found at // http://code.google.com/p/dolphin-emu/ #include "LineGeometryShader.h" #include #include "D3DBase.h" #include "D3DShader.h" #include "VertexShaderGen.h" namespace DX11 { union LineGSParams { struct { FLOAT LineWidth; // In units of 1/6 of an EFB pixel FLOAT TexOffset; FLOAT VpWidth; // Width and height of the viewport in EFB pixels FLOAT VpHeight; }; // Constant buffers must be a multiple of 16 bytes in size. u8 pad[16]; // Pad to the next multiple of 16 bytes }; static const char LINE_GS_COMMON[] = // The struct VS_OUTPUT used by the vertex shader goes here. "// dolphin-emu line geometry shader common part\n" "cbuffer cbParams : register(b0)\n" "{\n" "struct\n" // Should match LineGSParams above "{\n" "float LineWidth;\n" "float TexOffset;\n" "float VpWidth;\n" "float VpHeight;\n" "} Params;\n" "}\n" "[maxvertexcount(4)]\n" "void main(line VS_OUTPUT input[2], inout TriangleStream outStream)\n" "{\n" // Pretend input[0] is on the bottom and input[1] is on top. // We generate vertices to the left and right. "VS_OUTPUT l0 = input[0];\n" "VS_OUTPUT r0 = l0;\n" "VS_OUTPUT l1 = input[1];\n" "VS_OUTPUT r1 = l1;\n" // GameCube/Wii's line drawing algorithm is a little quirky. It does not // use the correct line caps. Instead, the line caps are vertical or // horizontal depending the slope of the line. "float2 offset;\n" "float2 to = abs(input[1].pos.xy - input[0].pos.xy);\n" // FIXME: What does real hardware do when line is at a 45-degree angle? // FIXME: Lines aren't drawn at the correct width. See Twilight Princess map. "if (Params.VpHeight*to.y > Params.VpWidth*to.x) {\n" // Line is more tall. Extend geometry left and right. // Lerp Params.LineWidth/2 from [0..VpWidth] to [-1..1] "offset = float2(Params.LineWidth/Params.VpWidth, 0);\n" "} else {\n" // Line is more wide. Extend geometry up and down. // Lerp Params.LineWidth/2 from [0..VpHeight] to [1..-1] "offset = float2(0, -Params.LineWidth/Params.VpHeight);\n" "}\n" "l0.pos.xy -= offset * input[0].pos.w;\n" "r0.pos.xy += offset * input[0].pos.w;\n" "l1.pos.xy -= offset * input[1].pos.w;\n" "r1.pos.xy += offset * input[1].pos.w;\n" "#ifndef NUM_TEXCOORDS\n" "#error NUM_TEXCOORDS not defined\n" "#endif\n" // Apply TexOffset to all tex coordinates in the vertex // FIXME: The game may be able to enable TexOffset for some coords and // disable for others, but where is that information stored? "#if NUM_TEXCOORDS >= 1\n" "r0.tex0.x += Params.TexOffset;\n" "r1.tex0.x += Params.TexOffset;\n" "#endif\n" "#if NUM_TEXCOORDS >= 2\n" "r0.tex1.x += Params.TexOffset;\n" "r1.tex1.x += Params.TexOffset;\n" "#endif\n" "#if NUM_TEXCOORDS >= 3\n" "r0.tex2.x += Params.TexOffset;\n" "r1.tex2.x += Params.TexOffset;\n" "#endif\n" "#if NUM_TEXCOORDS >= 4\n" "r0.tex3.x += Params.TexOffset;\n" "r1.tex3.x += Params.TexOffset;\n" "#endif\n" "#if NUM_TEXCOORDS >= 5\n" "r0.tex4.x += Params.TexOffset;\n" "r1.tex4.x += Params.TexOffset;\n" "#endif\n" "#if NUM_TEXCOORDS >= 6\n" "r0.tex5.x += Params.TexOffset;\n" "r1.tex5.x += Params.TexOffset;\n" "#endif\n" "#if NUM_TEXCOORDS >= 7\n" "r0.tex6.x += Params.TexOffset;\n" "r1.tex6.x += Params.TexOffset;\n" "#endif\n" "#if NUM_TEXCOORDS >= 8\n" "r0.tex7.x += Params.TexOffset;\n" "r1.tex7.x += Params.TexOffset;\n" "#endif\n" "outStream.Append(l0);\n" "outStream.Append(r0);\n" "outStream.Append(l1);\n" "outStream.Append(r1);\n" "}\n" ; LineGeometryShader::LineGeometryShader() : m_ready(false) { // Create constant buffer for uploading data to geometry shader D3D11_BUFFER_DESC bd = CD3D11_BUFFER_DESC(sizeof(LineGSParams), D3D11_BIND_CONSTANT_BUFFER); m_paramsBuffer = CreateBufferShared(&bd, NULL); CHECK(m_paramsBuffer, "create line geometry shader params buffer"); D3D::SetDebugObjectName(m_paramsBuffer, "line geometry shader params buffer"); m_ready = true; } bool LineGeometryShader::SetShader(u32 components, float lineWidth, float texOffset, float vpWidth, float vpHeight) { if (!m_ready) return false; // Make sure geometry shader for "components" is available ComboMap::iterator shaderIt = m_shaders.find(components); if (shaderIt == m_shaders.end()) { // Generate new shader. Warning: not thread-safe. static char code[16384]; char* p = code; p = GenerateVSOutputStruct(p, components, API_D3D11); p += sprintf(p, "\n%s", LINE_GS_COMMON); std::stringstream numTexCoordsStr; numTexCoordsStr << xfregs.numTexGen.numTexGens; INFO_LOG(VIDEO, "Compiling line geometry shader for components 0x%.08X (num texcoords %d)", components, xfregs.numTexGen.numTexGens); D3D_SHADER_MACRO macros[] = { { "NUM_TEXCOORDS", numTexCoordsStr.str().c_str() }, { NULL, NULL } }; auto const newShader = D3D::CompileAndCreateGeometryShader(code, unsigned int(strlen(code)), macros); if (!newShader) { WARN_LOG(VIDEO, "Line geometry shader for components 0x%.08X failed to compile", components); // Add dummy shader to prevent trying to compile again m_shaders[components].reset(); return false; } shaderIt = m_shaders.insert(std::make_pair(components, newShader)).first; } if (shaderIt != m_shaders.end()) { if (shaderIt->second) { LineGSParams params = {}; params.LineWidth = lineWidth; params.TexOffset = texOffset; params.VpWidth = vpWidth; params.VpHeight = vpHeight; D3D::g_context->UpdateSubresource(m_paramsBuffer, 0, NULL, ¶ms, 0, 0); DEBUG_LOG(VIDEO, "Line params: width %f, texOffset %f, vpWidth %f, vpHeight %f", lineWidth, texOffset, vpWidth, vpHeight); D3D::g_context->GSSetShader(shaderIt->second, NULL, 0); D3D::g_context->GSSetConstantBuffers(0, 1, &m_paramsBuffer); return true; } else return false; } else return false; } }