summaryrefslogtreecommitdiff
path: root/Source/Core/VideoBackends/D3D/LineGeometryShader.cpp
blob: 7f1040a716e1e2b7a3a4c429c540230b5e3886d6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
// Copyright 2013 Dolphin Emulator Project
// Licensed under GPLv2
// Refer to the license.txt file included.

#include <sstream>

#include "VideoBackends/D3D/D3DBase.h"
#include "VideoBackends/D3D/D3DShader.h"
#include "VideoBackends/D3D/LineGeometryShader.h"
#include "VideoCommon/VertexShaderGen.h"

namespace DX11
{

struct LineGSParams
{
	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;
	FLOAT TexOffsetEnable[8]; // For each tex coordinate, whether to apply offset to it (1 on, 0 off)
};

union LineGSParams_Padded
{
	LineGSParams params;
	// Constant buffers must be a multiple of 16 bytes in size.
	u8 pad[(sizeof(LineGSParams) + 15) & ~15];
};

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"
		"float TexOffsetEnable[8];\n"
	"} Params;\n"
"}\n"

"[maxvertexcount(4)]\n"
"void main(line VS_OUTPUT input[2], inout TriangleStream<VS_OUTPUT> 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.
	// They can each be enabled seperately.
"#if NUM_TEXCOORDS >= 1\n"
	"r0.tex0.x += Params.TexOffset * Params.TexOffsetEnable[0];\n"
	"r1.tex0.x += Params.TexOffset * Params.TexOffsetEnable[0];\n"
"#endif\n"
"#if NUM_TEXCOORDS >= 2\n"
	"r0.tex1.x += Params.TexOffset * Params.TexOffsetEnable[1];\n"
	"r1.tex1.x += Params.TexOffset * Params.TexOffsetEnable[1];\n"
"#endif\n"
"#if NUM_TEXCOORDS >= 3\n"
	"r0.tex2.x += Params.TexOffset * Params.TexOffsetEnable[2];\n"
	"r1.tex2.x += Params.TexOffset * Params.TexOffsetEnable[2];\n"
"#endif\n"
"#if NUM_TEXCOORDS >= 4\n"
	"r0.tex3.x += Params.TexOffset * Params.TexOffsetEnable[3];\n"
	"r1.tex3.x += Params.TexOffset * Params.TexOffsetEnable[3];\n"
"#endif\n"
"#if NUM_TEXCOORDS >= 5\n"
	"r0.tex4.x += Params.TexOffset * Params.TexOffsetEnable[4];\n"
	"r1.tex4.x += Params.TexOffset * Params.TexOffsetEnable[4];\n"
"#endif\n"
"#if NUM_TEXCOORDS >= 6\n"
	"r0.tex5.x += Params.TexOffset * Params.TexOffsetEnable[5];\n"
	"r1.tex5.x += Params.TexOffset * Params.TexOffsetEnable[5];\n"
"#endif\n"
"#if NUM_TEXCOORDS >= 7\n"
	"r0.tex6.x += Params.TexOffset * Params.TexOffsetEnable[6];\n"
	"r1.tex6.x += Params.TexOffset * Params.TexOffsetEnable[6];\n"
"#endif\n"
"#if NUM_TEXCOORDS >= 8\n"
	"r0.tex7.x += Params.TexOffset * Params.TexOffsetEnable[7];\n"
	"r1.tex7.x += Params.TexOffset * Params.TexOffsetEnable[7];\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), m_paramsBuffer(nullptr)
{ }

void LineGeometryShader::Init()
{
	m_ready = false;

	HRESULT hr;

	// Create constant buffer for uploading data to geometry shader

	D3D11_BUFFER_DESC bd = CD3D11_BUFFER_DESC(sizeof(LineGSParams_Padded),
		D3D11_BIND_CONSTANT_BUFFER, D3D11_USAGE_DYNAMIC, D3D11_CPU_ACCESS_WRITE);
	hr = D3D::device->CreateBuffer(&bd, nullptr, &m_paramsBuffer);
	CHECK(SUCCEEDED(hr), "create line geometry shader params buffer");
	D3D::SetDebugObjectName(m_paramsBuffer, "line geometry shader params buffer");

	m_ready = true;
}

void LineGeometryShader::Shutdown()
{
	m_ready = false;

	for (auto& it : m_shaders)
	{
		SAFE_RELEASE(it.second);
	}
	m_shaders.clear();

	SAFE_RELEASE(m_paramsBuffer);
}

bool LineGeometryShader::SetShader(u32 components, float lineWidth,
	float texOffset, float vpWidth, float vpHeight, const bool* texOffsetEnable)
{
	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 buffer[16384];
		ShaderCode code;
		code.SetBuffer(buffer);
		GenerateVSOutputStructForGS(code, API_D3D);
		code.Write("\n%s", LINE_GS_COMMON);

		std::stringstream numTexCoordsStream;
		numTexCoordsStream << xfmem.numTexGen.numTexGens;

		INFO_LOG(VIDEO, "Compiling line geometry shader for components 0x%.08X (num texcoords %d)",
			components, xfmem.numTexGen.numTexGens);

		const std::string& numTexCoordsStr = numTexCoordsStream.str();
		D3D_SHADER_MACRO macros[] = {
			{ "NUM_TEXCOORDS", numTexCoordsStr.c_str() },
			{ nullptr, nullptr }
		};
		ID3D11GeometryShader* newShader = D3D::CompileAndCreateGeometryShader(code.GetBuffer(), 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] = nullptr;
			return false;
		}

		shaderIt = m_shaders.insert(std::make_pair(components, newShader)).first;
	}

	if (shaderIt != m_shaders.end())
	{
		if (shaderIt->second)
		{
			D3D11_MAPPED_SUBRESOURCE map;
			HRESULT hr = D3D::context->Map(m_paramsBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &map);
			if (SUCCEEDED(hr))
			{
				LineGSParams* params = (LineGSParams*)map.pData;
				params->LineWidth = lineWidth;

				params->TexOffset = texOffset;
				params->VpWidth = vpWidth;
				params->VpHeight = vpHeight;
				for (int i = 0; i < 8; ++i)
					params->TexOffsetEnable[i] = texOffsetEnable[i] ? 1.f : 0.f;

				D3D::context->Unmap(m_paramsBuffer, 0);
			}
			else
				ERROR_LOG(VIDEO, "Failed to map line gs params buffer");

			DEBUG_LOG(VIDEO, "Line params: width %f, texOffset %f, vpWidth %f, vpHeight %f",
				lineWidth, texOffset, vpWidth, vpHeight);

			D3D::context->GSSetShader(shaderIt->second, nullptr, 0);
			D3D::context->GSSetConstantBuffers(0, 1, &m_paramsBuffer);

			return true;
		}
		else
			return false;
	}
	else
		return false;
}

}