summaryrefslogtreecommitdiff
path: root/Source/Core/VideoBackends/OGL/TextureConverter.cpp
blob: b56b08ab4faa23b0e0b35da1d47736f123bfd75e (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
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
// Copyright 2008 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.

// Fast image conversion using OpenGL shaders.

#include <string>

#include "Common/Common.h"
#include "Common/FileUtil.h"
#include "Common/StringUtil.h"

#include "Core/HW/Memmap.h"

#include "VideoBackends/OGL/FramebufferManager.h"
#include "VideoBackends/OGL/ProgramShaderCache.h"
#include "VideoBackends/OGL/Render.h"
#include "VideoBackends/OGL/SamplerCache.h"
#include "VideoBackends/OGL/TextureCache.h"
#include "VideoBackends/OGL/TextureConverter.h"

#include "VideoCommon/DriverDetails.h"
#include "VideoCommon/ImageWrite.h"
#include "VideoCommon/TextureConversionShader.h"
#include "VideoCommon/VideoConfig.h"


namespace OGL
{

namespace TextureConverter
{

using OGL::TextureCache;

static GLuint s_texConvFrameBuffer[2] = {0,0};
static GLuint s_srcTexture = 0; // for decoding from RAM
static GLuint s_dstTexture = 0; // for encoding to RAM

const int renderBufferWidth = EFB_WIDTH * 4;
const int renderBufferHeight = 1024;

static SHADER s_rgbToYuyvProgram;
static int s_rgbToYuyvUniform_loc;

static SHADER s_yuyvToRgbProgram;

// Not all slots are taken - but who cares.
const u32 NUM_ENCODING_PROGRAMS = 64;
static SHADER s_encodingPrograms[NUM_ENCODING_PROGRAMS];
static int s_encodingUniforms[NUM_ENCODING_PROGRAMS];

static GLuint s_PBO = 0; // for readback with different strides

static void CreatePrograms()
{
	/* TODO: Accuracy Improvements
	 *
	 * This shader doesn't really match what the GameCube does internally in the
	 * copy pipeline.
	 *  1. It uses OpenGL's built in filtering when yscaling, someone could work
	 *     out how the copypipeline does it's filtering and implement it correctly
	 *     in this shader.
	 *  2. Deflickering isn't implemented, a futher filtering over 3 lines.
	 *     Isn't really needed on non-interlaced monitors (and would lower quality;
	 *     But hey, accuracy!)
	 *  3. Flipper's YUYV conversion implements a 3 pixel horizontal blur on the
	 *     UV channels, centering the U channel on the Left pixel and the V channel
	 *     on the Right pixel.
	 *     The current implementation Centers both UV channels at the same place
	 *     inbetween the two Pixels, and only blurs over these two pixels.
	 */
	// Output is BGRA because that is slightly faster than RGBA.
	const char *VProgramRgbToYuyv =
		"out vec2 uv0;\n"
		"uniform vec4 copy_position;\n" // left, top, right, bottom
		"SAMPLER_BINDING(9) uniform sampler2DArray samp9;\n"
		"void main()\n"
		"{\n"
		"	vec2 rawpos = vec2(gl_VertexID&1, gl_VertexID&2);\n"
		"	gl_Position = vec4(rawpos*2.0-1.0, 0.0, 1.0);\n"
		"	uv0 = mix(copy_position.xy, copy_position.zw, rawpos) / vec2(textureSize(samp9, 0).xy);\n"
		"}\n";
	const char *FProgramRgbToYuyv =
		"SAMPLER_BINDING(9) uniform sampler2DArray samp9;\n"
		"in vec2 uv0;\n"
		"out vec4 ocol0;\n"
		"void main()\n"
		"{\n"
		"	vec3 c0 = texture(samp9, vec3(uv0 - dFdx(uv0) * 0.25, 0.0)).rgb;\n"
		"	vec3 c1 = texture(samp9, vec3(uv0 + dFdx(uv0) * 0.25, 0.0)).rgb;\n"
		"	vec3 c01 = (c0 + c1) * 0.5;\n"
		"	vec3 y_const = vec3(0.257,0.504,0.098);\n"
		"	vec3 u_const = vec3(-0.148,-0.291,0.439);\n"
		"	vec3 v_const = vec3(0.439,-0.368,-0.071);\n"
		"	vec4 const3 = vec4(0.0625,0.5,0.0625,0.5);\n"
		"	ocol0 = vec4(dot(c1,y_const),dot(c01,u_const),dot(c0,y_const),dot(c01, v_const)) + const3;\n"
		"}\n";
	ProgramShaderCache::CompileShader(s_rgbToYuyvProgram, VProgramRgbToYuyv, FProgramRgbToYuyv);
	s_rgbToYuyvUniform_loc = glGetUniformLocation(s_rgbToYuyvProgram.glprogid, "copy_position");

	/* TODO: Accuracy Improvements
	 *
	 * The YVYU to RGB conversion here matches the RGB to YUYV done above, but
	 * if a game modifies or adds images to the XFB then it should be using the
	 * same algorithm as the flipper, and could result in slight color inaccuracies
	 * when run back through this shader.
	 */
	const char *VProgramYuyvToRgb =
		"void main()\n"
		"{\n"
		"	vec2 rawpos = vec2(gl_VertexID&1, gl_VertexID&2);\n"
		"	gl_Position = vec4(rawpos*2.0-1.0, 0.0, 1.0);\n"
		"}\n";
	const char *FProgramYuyvToRgb =
		"SAMPLER_BINDING(9) uniform sampler2D samp9;\n"
		"in vec2 uv0;\n"
		"out vec4 ocol0;\n"
		"void main()\n"
		"{\n"
		"	ivec2 uv = ivec2(gl_FragCoord.xy);\n"
			// We switch top/bottom here. TODO: move this to screen blit.
		"	ivec2 ts = textureSize(samp9, 0);\n"
		"	vec4 c0 = texelFetch(samp9, ivec2(uv.x>>1, ts.y-uv.y-1), 0);\n"
		"	float y = mix(c0.r, c0.b, (uv.x & 1) == 1);\n"
		"	float yComp = 1.164 * (y - 0.0625);\n"
		"	float uComp = c0.g - 0.5;\n"
		"	float vComp = c0.a - 0.5;\n"
		"	ocol0 = vec4(yComp + (1.596 * vComp),\n"
		"		yComp - (0.813 * vComp) - (0.391 * uComp),\n"
		"		yComp + (2.018 * uComp),\n"
		"		1.0);\n"
		"}\n";
	ProgramShaderCache::CompileShader(s_yuyvToRgbProgram, VProgramYuyvToRgb, FProgramYuyvToRgb);
}

static SHADER &GetOrCreateEncodingShader(u32 format)
{
	if (format >= NUM_ENCODING_PROGRAMS)
	{
		PanicAlert("Unknown texture copy format: 0x%x\n", format);
		return s_encodingPrograms[0];
	}

	if (s_encodingPrograms[format].glprogid == 0)
	{
		const char* shader = TextureConversionShader::GenerateEncodingShader(format, API_OPENGL);

#if defined(_DEBUG) || defined(DEBUGFAST)
		if (g_ActiveConfig.iLog & CONF_SAVESHADERS && shader)
		{
			static int counter = 0;
			std::string filename = StringFromFormat("%senc_%04i.txt", File::GetUserPath(D_DUMP_IDX).c_str(), counter++);

			SaveData(filename, shader);
		}
#endif

		const char *VProgram =
			"void main()\n"
			"{\n"
			"	vec2 rawpos = vec2(gl_VertexID&1, gl_VertexID&2);\n"
			"	gl_Position = vec4(rawpos*2.0-1.0, 0.0, 1.0);\n"
			"}\n";

		ProgramShaderCache::CompileShader(s_encodingPrograms[format], VProgram, shader);

		s_encodingUniforms[format] = glGetUniformLocation(s_encodingPrograms[format].glprogid, "position");
	}
	return s_encodingPrograms[format];
}

void Init()
{
	glGenFramebuffers(2, s_texConvFrameBuffer);

	glActiveTexture(GL_TEXTURE9);
	glGenTextures(1, &s_srcTexture);
	glBindTexture(GL_TEXTURE_2D, s_srcTexture);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);

	glGenTextures(1, &s_dstTexture);
	glBindTexture(GL_TEXTURE_2D, s_dstTexture);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, renderBufferWidth, renderBufferHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);


	FramebufferManager::SetFramebuffer(s_texConvFrameBuffer[0]);
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, s_dstTexture, 0);
	FramebufferManager::SetFramebuffer(0);

	glGenBuffers(1, &s_PBO);

	CreatePrograms();
}

void Shutdown()
{
	glDeleteTextures(1, &s_srcTexture);
	glDeleteTextures(1, &s_dstTexture);
	glDeleteBuffers(1, &s_PBO);
	glDeleteFramebuffers(2, s_texConvFrameBuffer);

	s_rgbToYuyvProgram.Destroy();
	s_yuyvToRgbProgram.Destroy();

	for (auto& program : s_encodingPrograms)
		program.Destroy();

	s_srcTexture = 0;
	s_dstTexture = 0;
	s_PBO = 0;
	s_texConvFrameBuffer[0] = 0;
	s_texConvFrameBuffer[1] = 0;
}

// dst_line_size, writeStride in bytes

static void EncodeToRamUsingShader(GLuint srcTexture,
						u8* destAddr, u32 dst_line_size, u32 dstHeight,
						u32 writeStride, bool linearFilter)
{
	// switch to texture converter frame buffer
	// attach render buffer as color destination
	FramebufferManager::SetFramebuffer(s_texConvFrameBuffer[0]);

	OpenGL_BindAttributelessVAO();

	// set source texture
	glActiveTexture(GL_TEXTURE9);
	glBindTexture(GL_TEXTURE_2D_ARRAY, srcTexture);

	if (linearFilter)
		g_sampler_cache->BindLinearSampler(9);
	else
		g_sampler_cache->BindNearestSampler(9);

	glViewport(0, 0, (GLsizei)(dst_line_size / 4), (GLsizei)dstHeight);

	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

	int dstSize = dst_line_size * dstHeight;

	if ((writeStride != dst_line_size) && (dstHeight > 1))
	{
		// writing to a texture of a different size
		// also copy more then one block line, so the different strides matters
		// copy into one pbo first, map this buffer, and then memcpy into GC memory
		// in this way, we only have one vram->ram transfer, but maybe a bigger
		// CPU overhead because of the pbo
		glBindBuffer(GL_PIXEL_PACK_BUFFER, s_PBO);
		glBufferData(GL_PIXEL_PACK_BUFFER, dstSize, nullptr, GL_STREAM_READ);
		glReadPixels(0, 0, (GLsizei)(dst_line_size / 4), (GLsizei)dstHeight, GL_BGRA, GL_UNSIGNED_BYTE, nullptr);
		u8* pbo = (u8*)glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, dstSize, GL_MAP_READ_BIT);

		for (size_t i = 0; i < dstHeight; ++i)
		{
			memcpy(destAddr, pbo, dst_line_size);
			pbo += dst_line_size;
			destAddr += writeStride;
		}

		glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
		glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
	}
	else
	{
		glReadPixels(0, 0, (GLsizei)(dst_line_size / 4), (GLsizei)dstHeight, GL_BGRA, GL_UNSIGNED_BYTE, destAddr);
	}
}

void EncodeToRamFromTexture(u8* dest_ptr, u32 format, u32 native_width, u32 bytes_per_row, u32 num_blocks_y, u32 memory_stride,
                            PEControl::PixelFormat srcFormat, bool bIsIntensityFmt, int bScaleByHalf, const EFBRectangle& source)
{
	g_renderer->ResetAPIState();

	SHADER& texconv_shader = GetOrCreateEncodingShader(format);

	texconv_shader.Bind();
	glUniform4i(s_encodingUniforms[format],
		source.left, source.top, native_width, bScaleByHalf ? 2 : 1);

	const GLuint read_texture = (srcFormat == PEControl::Z24) ?
		FramebufferManager::ResolveAndGetDepthTarget(source) :
		FramebufferManager::ResolveAndGetRenderTarget(source);

	EncodeToRamUsingShader(read_texture,
		dest_ptr, bytes_per_row, num_blocks_y,
		memory_stride, bScaleByHalf > 0 && srcFormat != PEControl::Z24);

	FramebufferManager::SetFramebuffer(0);
	g_renderer->RestoreAPIState();
}

void EncodeToRamYUYV(GLuint srcTexture, const TargetRectangle& sourceRc, u8* destAddr, u32 dstWidth, u32 dstStride, u32 dstHeight)
{
	g_renderer->ResetAPIState();

	s_rgbToYuyvProgram.Bind();

	glUniform4f(s_rgbToYuyvUniform_loc, static_cast<float>(sourceRc.left), static_cast<float>(sourceRc.top),
		static_cast<float>(sourceRc.right), static_cast<float>(sourceRc.bottom));

	// We enable linear filtering, because the GameCube does filtering in the vertical direction when
	// yscale is enabled.
	// Otherwise we get jaggies when a game uses yscaling (most PAL games)
	EncodeToRamUsingShader(srcTexture, destAddr, dstWidth * 2, dstHeight, dstStride, true);
	FramebufferManager::SetFramebuffer(0);
	TextureCache::DisableStage(0);
	g_renderer->RestoreAPIState();
}


// Should be scale free.
void DecodeToTexture(u32 xfbAddr, int srcWidth, int srcHeight, GLuint destTexture)
{
	u8* srcAddr = Memory::GetPointer(xfbAddr);
	if (!srcAddr)
	{
		WARN_LOG(VIDEO, "Tried to decode from invalid memory address");
		return;
	}

	g_renderer->ResetAPIState(); // reset any game specific settings

	OpenGL_BindAttributelessVAO();

	// switch to texture converter frame buffer
	// attach destTexture as color destination
	FramebufferManager::SetFramebuffer(s_texConvFrameBuffer[1]);
	FramebufferManager::FramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_ARRAY, destTexture, 0);

	// activate source texture
	// set srcAddr as data for source texture
	glActiveTexture(GL_TEXTURE9);
	glBindTexture(GL_TEXTURE_2D, s_srcTexture);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, srcWidth / 2, srcHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, srcAddr);
	g_sampler_cache->BindNearestSampler(9);

	glViewport(0, 0, srcWidth, srcHeight);
	s_yuyvToRgbProgram.Bind();

	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

	FramebufferManager::SetFramebuffer(0);

	g_renderer->RestoreAPIState();
}

}  // namespace

}  // namespace OGL