1 // Copyright (c) 2013- PPSSPP Project.
2 
3 // This program is free software: you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation, version 2.0 or later versions.
6 
7 // This program is distributed in the hope that it will be useful,
8 // but WITHOUT ANY WARRANTY; without even the implied warranty of
9 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10 // GNU General Public License 2.0 for more details.
11 
12 // A copy of the GPL 2.0 should have been included with the program.
13 // If not, see http://www.gnu.org/licenses/
14 
15 // Official git repository and contact information can be found at
16 // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
17 
18 #include "Common/Math/lin/matrix4x4.h"
19 #include "Common/GPU/OpenGL/GLSLProgram.h"
20 #include "Common/GPU/OpenGL/GLFeatures.h"
21 #include "Windows/GEDebugger/GEDebugger.h"
22 #include "Windows/GEDebugger/SimpleGLWindow.h"
23 #include "Core/System.h"
24 #include "Core/Config.h"
25 #include "GPU/GPUInterface.h"
26 #include "GPU/Common/GPUDebugInterface.h"
27 #include "GPU/Common/SplineCommon.h"
28 #include "GPU/GPUState.h"
29 #include "Common/Log.h"
30 #include "Common/MemoryUtil.h"
31 
32 static const char preview_fs[] =
33 	"#ifdef GL_ES\n"
34 	"precision mediump float;\n"
35 	"#endif\n"
36 	"void main() {\n"
37 	"	gl_FragColor = vec4(1.0, 0.0, 0.0, 0.6);\n"
38 	"}\n";
39 
40 static const char preview_vs[] =
41 	"#version 120\n"
42 	"attribute vec4 a_position;\n"
43 	"uniform mat4 u_viewproj;\n"
44 	"void main() {\n"
45 	"  gl_Position = u_viewproj * a_position;\n"
46 	"  gl_Position.z = 1.0f;\n"
47 	"}\n";
48 
49 static GLSLProgram *previewProgram = nullptr;
50 static GLSLProgram *texPreviewProgram = nullptr;
51 
52 static GLuint previewVao = 0;
53 static GLuint texPreviewVao = 0;
54 static GLuint vbuf = 0;
55 static GLuint ibuf = 0;
56 
57 static const GLuint glprim[8] = {
58 	GL_POINTS,
59 	GL_LINES,
60 	GL_LINE_STRIP,
61 	GL_TRIANGLES,
62 	GL_TRIANGLE_STRIP,
63 	GL_TRIANGLE_FAN,
64 	// This is for RECTANGLES (see ExpandRectangles().)
65 	GL_TRIANGLES,
66 };
67 
BindPreviewProgram(GLSLProgram * & prog)68 static void BindPreviewProgram(GLSLProgram *&prog) {
69 	if (prog == nullptr) {
70 		prog = glsl_create_source(preview_vs, preview_fs);
71 	}
72 
73 	glsl_bind(prog);
74 }
75 
SwapUVs(GPUDebugVertex & a,GPUDebugVertex & b)76 static void SwapUVs(GPUDebugVertex &a, GPUDebugVertex &b) {
77 	float tempu = a.u;
78 	float tempv = a.v;
79 	a.u = b.u;
80 	a.v = b.v;
81 	b.u = tempu;
82 	b.v = tempv;
83 }
84 
RotateUVThrough(GPUDebugVertex v[4])85 static void RotateUVThrough(GPUDebugVertex v[4]) {
86 	float x1 = v[2].x;
87 	float x2 = v[0].x;
88 	float y1 = v[2].y;
89 	float y2 = v[0].y;
90 
91 	if ((x1 < x2 && y1 > y2) || (x1 > x2 && y1 < y2))
92 		SwapUVs(v[1], v[3]);
93 }
94 
ExpandRectangles(std::vector<GPUDebugVertex> & vertices,std::vector<u16> & indices,int & count,bool throughMode)95 static void ExpandRectangles(std::vector<GPUDebugVertex> &vertices, std::vector<u16> &indices, int &count, bool throughMode) {
96 	static std::vector<GPUDebugVertex> newVerts;
97 	static std::vector<u16> newInds;
98 
99 	bool useInds = true;
100 	size_t numInds = indices.size();
101 	if (indices.empty()) {
102 		useInds = false;
103 		numInds = count;
104 	}
105 
106 	//rectangles always need 2 vertices, disregard the last one if there's an odd number
107 	numInds = numInds & ~1;
108 
109 	// Will need 4 coords and 6 points per rectangle (currently 2 each.)
110 	newVerts.resize(numInds * 2);
111 	newInds.resize(numInds * 3);
112 
113 	u16 v = 0;
114 	GPUDebugVertex *vert = &newVerts[0];
115 	u16 *ind = &newInds[0];
116 	for (size_t i = 0; i < numInds; i += 2) {
117 		const auto &orig_tl = useInds ? vertices[indices[i + 0]] : vertices[i + 0];
118 		const auto &orig_br = useInds ? vertices[indices[i + 1]] : vertices[i + 1];
119 
120 		vert[0] = orig_br;
121 
122 		// Top right.
123 		vert[1] = orig_br;
124 		vert[1].y = orig_tl.y;
125 		vert[1].v = orig_tl.v;
126 
127 		vert[2] = orig_tl;
128 
129 		// Bottom left.
130 		vert[3] = orig_br;
131 		vert[3].x = orig_tl.x;
132 		vert[3].u = orig_tl.u;
133 
134 		// That's the four corners. Now process UV rotation.
135 		// This is the same for through and non-through, since it's already transformed.
136 		RotateUVThrough(vert);
137 
138 		// Build the two 3 point triangles from our 4 coordinates.
139 		*ind++ = v + 0;
140 		*ind++ = v + 1;
141 		*ind++ = v + 2;
142 		*ind++ = v + 3;
143 		*ind++ = v + 0;
144 		*ind++ = v + 2;
145 
146 		vert += 4;
147 		v += 4;
148 	}
149 
150 	std::swap(vertices, newVerts);
151 	std::swap(indices, newInds);
152 	count *= 3;
153 }
154 
PrimPreviewOp()155 u32 CGEDebugger::PrimPreviewOp() {
156 	DisplayList list;
157 	if (gpuDebug != nullptr && gpuDebug->GetCurrentDisplayList(list) && !showClut_) {
158 		const u32 op = Memory::Read_U32(list.pc);
159 		const u32 cmd = op >> 24;
160 		if (cmd == GE_CMD_PRIM || cmd == GE_CMD_BEZIER || cmd == GE_CMD_SPLINE) {
161 			return op;
162 		}
163 	}
164 
165 	return 0;
166 }
167 
ExpandBezier(int & count,int op,const std::vector<SimpleVertex> & simpleVerts,const std::vector<u16> & indices,std::vector<SimpleVertex> & generatedVerts,std::vector<u16> & generatedInds)168 static void ExpandBezier(int &count, int op, const std::vector<SimpleVertex> &simpleVerts, const std::vector<u16> &indices, std::vector<SimpleVertex> &generatedVerts, std::vector<u16> &generatedInds) {
169 	using namespace Spline;
170 
171 	int count_u = (op >> 0) & 0xFF;
172 	int count_v = (op >> 8) & 0xFF;
173 	// Real hardware seems to draw nothing when given < 4 either U or V.
174 	if (count_u < 4 || count_v < 4)
175 		return;
176 
177 	BezierSurface surface;
178 	surface.num_points_u = count_u;
179 	surface.num_points_v = count_v;
180 	surface.tess_u = gstate.getPatchDivisionU();
181 	surface.tess_v = gstate.getPatchDivisionV();
182 	surface.num_patches_u = (count_u - 1) / 3;
183 	surface.num_patches_v = (count_v - 1) / 3;
184 	surface.primType = gstate.getPatchPrimitiveType();
185 	surface.patchFacing = false;
186 
187 	int num_points = count_u * count_v;
188 	// Make an array of pointers to the control points, to get rid of indices.
189 	std::vector<const SimpleVertex *> points(num_points);
190 	for (int idx = 0; idx < num_points; idx++)
191 		points[idx] = simpleVerts.data() + (!indices.empty() ? indices[idx] : idx);
192 
193 	int total_patches = surface.num_patches_u * surface.num_patches_v;
194 	generatedVerts.resize((surface.tess_u + 1) * (surface.tess_v + 1) * total_patches);
195 	generatedInds.resize(surface.tess_u * surface.tess_v * 6 * total_patches);
196 
197 	OutputBuffers output;
198 	output.vertices = generatedVerts.data();
199 	output.indices = generatedInds.data();
200 	output.count = 0;
201 
202 	ControlPoints cpoints;
203 	cpoints.pos = (Vec3f *)AllocateAlignedMemory(sizeof(Vec3f) * num_points, 16);
204 	cpoints.tex = (Vec2f *)AllocateAlignedMemory(sizeof(Vec2f) * num_points, 16);
205 	cpoints.col = (Vec4f *)AllocateAlignedMemory(sizeof(Vec4f) * num_points, 16);
206 	cpoints.Convert(points.data(), num_points);
207 
208 	surface.Init((int)generatedVerts.size());
209 	SoftwareTessellation(output, surface, gstate.vertType, cpoints);
210 	count = output.count;
211 
212 	FreeAlignedMemory(cpoints.pos);
213 	FreeAlignedMemory(cpoints.tex);
214 	FreeAlignedMemory(cpoints.col);
215 }
216 
ExpandSpline(int & count,int op,const std::vector<SimpleVertex> & simpleVerts,const std::vector<u16> & indices,std::vector<SimpleVertex> & generatedVerts,std::vector<u16> & generatedInds)217 static void ExpandSpline(int &count, int op, const std::vector<SimpleVertex> &simpleVerts, const std::vector<u16> &indices, std::vector<SimpleVertex> &generatedVerts, std::vector<u16> &generatedInds) {
218 	using namespace Spline;
219 
220 	int count_u = (op >> 0) & 0xFF;
221 	int count_v = (op >> 8) & 0xFF;
222 	// Real hardware seems to draw nothing when given < 4 either U or V.
223 	if (count_u < 4 || count_v < 4)
224 		return;
225 
226 	SplineSurface surface;
227 	surface.num_points_u = count_u;
228 	surface.num_points_v = count_v;
229 	surface.tess_u = gstate.getPatchDivisionU();
230 	surface.tess_v = gstate.getPatchDivisionV();
231 	surface.type_u = (op >> 16) & 0x3;
232 	surface.type_v = (op >> 18) & 0x3;
233 	surface.num_patches_u = count_u - 3;
234 	surface.num_patches_v = count_v - 3;
235 	surface.primType = gstate.getPatchPrimitiveType();
236 	surface.patchFacing = false;
237 
238 	int num_points = count_u * count_v;
239 	// Make an array of pointers to the control points, to get rid of indices.
240 	std::vector<const SimpleVertex *> points(num_points);
241 	for (int idx = 0; idx < num_points; idx++)
242 		points[idx] = simpleVerts.data() + (!indices.empty() ? indices[idx] : idx);
243 
244 	int patch_div_s = surface.num_patches_u * surface.tess_u;
245 	int patch_div_t = surface.num_patches_v * surface.tess_v;
246 	generatedVerts.resize((patch_div_s + 1) * (patch_div_t + 1));
247 	generatedInds.resize(patch_div_s * patch_div_t * 6);
248 
249 	OutputBuffers output;
250 	output.vertices = generatedVerts.data();
251 	output.indices = generatedInds.data();
252 	output.count = 0;
253 
254 	ControlPoints cpoints;
255 	cpoints.pos = (Vec3f *)AllocateAlignedMemory(sizeof(Vec3f) * num_points, 16);
256 	cpoints.tex = (Vec2f *)AllocateAlignedMemory(sizeof(Vec2f) * num_points, 16);
257 	cpoints.col = (Vec4f *)AllocateAlignedMemory(sizeof(Vec4f) * num_points, 16);
258 	cpoints.Convert(points.data(), num_points);
259 
260 	surface.Init((int)generatedVerts.size());
261 	SoftwareTessellation(output, surface, gstate.vertType, cpoints);
262 	count = output.count;
263 
264 	FreeAlignedMemory(cpoints.pos);
265 	FreeAlignedMemory(cpoints.tex);
266 	FreeAlignedMemory(cpoints.col);
267 }
268 
UpdatePrimPreview(u32 op,int which)269 void CGEDebugger::UpdatePrimPreview(u32 op, int which) {
270 	u32 prim_type = GE_PRIM_INVALID;
271 	int count = 0;
272 	int count_u = 0;
273 	int count_v = 0;
274 
275 	const u32 cmd = op >> 24;
276 	if (cmd == GE_CMD_PRIM) {
277 		prim_type = (op >> 16) & 0x7;
278 		count = op & 0xFFFF;
279 	} else {
280 		const GEPrimitiveType primLookup[] = { GE_PRIM_TRIANGLES, GE_PRIM_LINES, GE_PRIM_POINTS, GE_PRIM_POINTS };
281 		if (gstate.getPatchPrimitiveType() < ARRAY_SIZE(primLookup))
282 			prim_type = primLookup[gstate.getPatchPrimitiveType()];
283 		count_u = (op & 0x00FF) >> 0;
284 		count_v = (op & 0xFF00) >> 8;
285 		count = count_u * count_v;
286 	}
287 
288 	if (prim_type >= 7) {
289 		ERROR_LOG(G3D, "Unsupported prim type: %x", op);
290 		return;
291 	}
292 	if (!gpuDebug) {
293 		ERROR_LOG(G3D, "Invalid debugging environment, shutting down?");
294 		return;
295 	}
296 	which &= previewsEnabled_;
297 	if (count == 0 || which == 0) {
298 		return;
299 	}
300 
301 	const GEPrimitiveType prim = static_cast<GEPrimitiveType>(prim_type);
302 	static std::vector<GPUDebugVertex> vertices;
303 	static std::vector<u16> indices;
304 
305 	if (!gpuDebug->GetCurrentSimpleVertices(count, vertices, indices)) {
306 		ERROR_LOG(G3D, "Vertex preview not yet supported");
307 		return;
308 	}
309 
310 	if (cmd != GE_CMD_PRIM) {
311 		static std::vector<SimpleVertex> generatedVerts;
312 		static std::vector<u16> generatedInds;
313 
314 		static std::vector<SimpleVertex> simpleVerts;
315 		simpleVerts.resize(vertices.size());
316 		for (size_t i = 0; i < vertices.size(); ++i) {
317 			// For now, let's just copy back so we can use TessellateBezierPatch/TessellateSplinePatch...
318 			simpleVerts[i].uv[0] = vertices[i].u;
319 			simpleVerts[i].uv[1] = vertices[i].v;
320 			simpleVerts[i].pos = Vec3Packedf(vertices[i].x, vertices[i].y, vertices[i].z);
321 		}
322 
323 		if (cmd == GE_CMD_BEZIER) {
324 			ExpandBezier(count, op, simpleVerts, indices, generatedVerts, generatedInds);
325 		} else if (cmd == GE_CMD_SPLINE) {
326 			ExpandSpline(count, op, simpleVerts, indices, generatedVerts, generatedInds);
327 		}
328 
329 		vertices.resize(generatedVerts.size());
330 		for (size_t i = 0; i < vertices.size(); ++i) {
331 			vertices[i].u = generatedVerts[i].uv[0];
332 			vertices[i].v = generatedVerts[i].uv[1];
333 			vertices[i].x = generatedVerts[i].pos.x;
334 			vertices[i].y = generatedVerts[i].pos.y;
335 			vertices[i].z = generatedVerts[i].pos.z;
336 		}
337 		indices = generatedInds;
338 	}
339 
340 	if (prim == GE_PRIM_RECTANGLES) {
341 		ExpandRectangles(vertices, indices, count, gpuDebug->GetGState().isModeThrough());
342 	}
343 
344 	float fw, fh;
345 	float x, y;
346 
347 	// TODO: Probably there's a better way and place to do this.
348 	u16 minIndex = 0;
349 	u16 maxIndex = count - 1;
350 	if (!indices.empty()) {
351 		minIndex = 0xFFFF;
352 		maxIndex = 0;
353 		for (int i = 0; i < count; ++i) {
354 			if (minIndex > indices[i]) {
355 				minIndex = indices[i];
356 			}
357 			if (maxIndex < indices[i]) {
358 				maxIndex = indices[i];
359 			}
360 		}
361 	}
362 
363 	auto wrapCoord = [](float &coord) {
364 		if (coord < 0.0f) {
365 			coord += ceilf(-coord);
366 		}
367 		if (coord > 1.0f) {
368 			coord -= floorf(coord);
369 		}
370 	};
371 
372 	const float invTexWidth = 1.0f / gpuDebug->GetGState().getTextureWidth(0);
373 	const float invTexHeight = 1.0f / gpuDebug->GetGState().getTextureHeight(0);
374 	const float invRealTexWidth = 1.0f / gstate_c.curTextureWidth;
375 	const float invRealTexHeight = 1.0f / gstate_c.curTextureHeight;
376 	bool clampS = gpuDebug->GetGState().isTexCoordClampedS();
377 	bool clampT = gpuDebug->GetGState().isTexCoordClampedT();
378 	for (u16 i = minIndex; i <= maxIndex; ++i) {
379 		vertices[i].u *= invTexWidth;
380 		vertices[i].v *= invTexHeight;
381 		if (!clampS)
382 			wrapCoord(vertices[i].u);
383 		if (!clampT)
384 			wrapCoord(vertices[i].v);
385 	}
386 
387 	if (which & 1) {
388 		primaryWindow->Begin();
389 		primaryWindow->GetContentSize(x, y, fw, fh);
390 
391 		glEnable(GL_BLEND);
392 		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
393 		glBlendEquation(GL_FUNC_ADD);
394 		glBindTexture(GL_TEXTURE_2D, 0);
395 		// The surface is upside down, so vertical offsets are negated.
396 		glViewport((GLint)x, (GLint)-(y + fh - primaryWindow->Height()), (GLsizei)fw, (GLsizei)fh);
397 		glScissor((GLint)x, (GLint)-(y + fh - primaryWindow->Height()), (GLsizei)fw, (GLsizei)fh);
398 		BindPreviewProgram(previewProgram);
399 
400 		if (previewVao == 0 && gl_extensions.ARB_vertex_array_object) {
401 			glGenVertexArrays(1, &previewVao);
402 			glBindVertexArray(previewVao);
403 			glEnableVertexAttribArray(previewProgram->a_position);
404 
405 			if (ibuf == 0)
406 				glGenBuffers(1, &ibuf);
407 			if (vbuf == 0)
408 				glGenBuffers(1, &vbuf);
409 			glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibuf);
410 			glBindBuffer(GL_ARRAY_BUFFER, vbuf);
411 
412 			glVertexAttribPointer(previewProgram->a_position, 3, GL_FLOAT, GL_FALSE, sizeof(GPUDebugVertex), (void *)(2 * sizeof(float)));
413 		}
414 
415 		if (vbuf != 0) {
416 			glBindBuffer(GL_ARRAY_BUFFER, vbuf);
417 			glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(GPUDebugVertex), vertices.data(), GL_STREAM_DRAW);
418 		}
419 
420 		if (ibuf != 0 && !indices.empty()) {
421 			glBindVertexArray(previewVao);
422 			glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(u16), indices.data(), GL_STREAM_DRAW);
423 		}
424 
425 		float scale[] = {
426 			480.0f / (float)PSP_CoreParameter().renderWidth,
427 			272.0f / (float)PSP_CoreParameter().renderHeight,
428 		};
429 
430 		Lin::Matrix4x4 ortho;
431 		ortho.setOrtho(-(float)gstate_c.curRTOffsetX, (primaryWindow->TexWidth() - (int)gstate_c.curRTOffsetX) * scale[0], primaryWindow->TexHeight() * scale[1], 0, -1, 1);
432 		glUniformMatrix4fv(previewProgram->u_viewproj, 1, GL_FALSE, ortho.getReadPtr());
433 		if (previewVao != 0) {
434 			glBindVertexArray(previewVao);
435 		} else {
436 			glEnableVertexAttribArray(previewProgram->a_position);
437 			glVertexAttribPointer(previewProgram->a_position, 3, GL_FLOAT, GL_FALSE, sizeof(GPUDebugVertex), (float *)vertices.data() + 2);
438 		}
439 
440 		if (indices.empty()) {
441 			glDrawArrays(glprim[prim], 0, count);
442 		} else {
443 			glDrawElements(glprim[prim], count, GL_UNSIGNED_SHORT, previewVao != 0 ? 0 : indices.data());
444 		}
445 
446 		if (previewVao == 0) {
447 			glDisableVertexAttribArray(previewProgram->a_position);
448 		}
449 
450 		primaryWindow->End();
451 	}
452 
453 	if (which & 2) {
454 		secondWindow->Begin();
455 		secondWindow->GetContentSize(x, y, fw, fh);
456 
457 		glEnable(GL_BLEND);
458 		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
459 		glBlendEquation(GL_FUNC_ADD);
460 		glBindTexture(GL_TEXTURE_2D, 0);
461 		// The surface is upside down, so vertical offsets are flipped.
462 		glViewport((GLint)x, (GLint)-(y + fh - secondWindow->Height()), (GLsizei)fw, (GLsizei)fh);
463 		glScissor((GLint)x, (GLint)-(y + fh - secondWindow->Height()), (GLsizei)fw, (GLsizei)fh);
464 		BindPreviewProgram(texPreviewProgram);
465 
466 		if (texPreviewVao == 0 && vbuf != 0 && ibuf != 0 && gl_extensions.ARB_vertex_array_object) {
467 			glGenVertexArrays(1, &texPreviewVao);
468 			glBindVertexArray(texPreviewVao);
469 			glEnableVertexAttribArray(texPreviewProgram->a_position);
470 
471 			if (ibuf == 0)
472 				glGenBuffers(1, &ibuf);
473 			if (vbuf == 0)
474 				glGenBuffers(1, &vbuf);
475 			glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibuf);
476 			glBindBuffer(GL_ARRAY_BUFFER, vbuf);
477 
478 			glVertexAttribPointer(texPreviewProgram->a_position, 2, GL_FLOAT, GL_FALSE, sizeof(GPUDebugVertex), 0);
479 		}
480 
481 		// TODO: For some reason we have to re-upload the data?
482 		if (vbuf != 0) {
483 			glBindBuffer(GL_ARRAY_BUFFER, vbuf);
484 			glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(GPUDebugVertex), vertices.data(), GL_STREAM_DRAW);
485 		}
486 
487 		if (ibuf != 0 && !indices.empty()) {
488 			glBindVertexArray(texPreviewVao);
489 			glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(u16), indices.data(), GL_STREAM_DRAW);
490 		}
491 
492 		Lin::Matrix4x4 ortho;
493 		ortho.setOrtho(0.0f - (float)gstate_c.curTextureXOffset * invRealTexWidth, 1.0f - (float)gstate_c.curTextureXOffset * invRealTexWidth, 1.0f - (float)gstate_c.curTextureYOffset * invRealTexHeight, 0.0f - (float)gstate_c.curTextureYOffset * invRealTexHeight, -1.0f, 1.0f);
494 		glUniformMatrix4fv(texPreviewProgram->u_viewproj, 1, GL_FALSE, ortho.getReadPtr());
495 		if (texPreviewVao != 0) {
496 			glBindVertexArray(texPreviewVao);
497 			glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibuf);
498 			glBindBuffer(GL_ARRAY_BUFFER, vbuf);
499 			glEnableVertexAttribArray(texPreviewProgram->a_position);
500 			glVertexAttribPointer(texPreviewProgram->a_position, 2, GL_FLOAT, GL_FALSE, sizeof(GPUDebugVertex), 0);
501 		} else {
502 			glEnableVertexAttribArray(texPreviewProgram->a_position);
503 			glVertexAttribPointer(texPreviewProgram->a_position, 2, GL_FLOAT, GL_FALSE, sizeof(GPUDebugVertex), (float *)vertices.data());
504 		}
505 
506 		if (indices.empty()) {
507 			glDrawArrays(glprim[prim], 0, count);
508 		} else {
509 			glDrawElements(glprim[prim], count, GL_UNSIGNED_SHORT, texPreviewVao != 0 ? 0 : indices.data());
510 		}
511 
512 		if (texPreviewVao == 0) {
513 			glDisableVertexAttribArray(texPreviewProgram->a_position);
514 		}
515 
516 		secondWindow->End();
517 	}
518 }
519 
CleanupPrimPreview()520 void CGEDebugger::CleanupPrimPreview() {
521 	if (previewProgram) {
522 		glsl_destroy(previewProgram);
523 	}
524 	if (texPreviewProgram) {
525 		glsl_destroy(texPreviewProgram);
526 	}
527 }
528 
HandleRedraw(int which)529 void CGEDebugger::HandleRedraw(int which) {
530 	if (updating_) {
531 		return;
532 	}
533 
534 	u32 op = PrimPreviewOp();
535 	if (op) {
536 		UpdatePrimPreview(op, which);
537 	}
538 }
539