libgui 2.01
libgui is a GUI library for the GameCube, Wii, and Wii U
 
Loading...
Searching...
No Matches
WutVideoDriver.cpp
1/****************************************************************************
2 * Platform Abstraction Layer (WUT driver)
3 * Daryl Borth 2026
4 * WutVideoDriver.cpp
5 ***************************************************************************/
6#include <algorithm>
7#include <cmath>
8#include <cstring>
9#include <malloc.h>
10#include <new>
11
12#include <coreinit/memdefaultheap.h>
13#include <coreinit/alarm.h>
14#include <coreinit/time.h>
15#include <gx2/clear.h>
16#include <gx2/context.h>
17#include <gx2/display.h>
18#include <gx2/draw.h>
19#include <gx2/enum.h>
20#include <gx2/event.h>
21#include <gx2/mem.h>
22#include <gx2/registers.h>
23#include <gx2/sampler.h>
24#include <gx2/surface.h>
25#include <gx2/swap.h>
26#include <gx2/texture.h>
27#include <whb/gfx.h>
28#include <proc_ui/procui.h>
29
30#include "../Platform.h"
31#include "WutVideoDriver.h"
32#include "shaders/Texture2DShader.h"
33#include "shaders/ColorShader.h"
34
35namespace
36{
37 inline float DegToRad(float degrees)
38 {
39 return degrees * (3.14159265358979323846f / 180.0f);
40 }
41
42 void PixelRectToNdc(float x, float y, float w, float h, float scaleX, float scaleY, int designWidth, int designHeight, float offset[3], float scale[3])
43 {
44 float centerPxX = x + w * 0.5f;
45 float centerPxY = y + h * 0.5f;
46
47 offset[0] = (centerPxX / designWidth) * 2.0f - 1.0f;
48 offset[1] = 1.0f - (centerPxY / designHeight) * 2.0f;
49 offset[2] = 0.0f;
50
51 scale[0] = (w * scaleX) / designWidth;
52 scale[1] = (h * scaleY) / designHeight;
53 scale[2] = 1.0f;
54 }
55
56 //Rotates a quad about its own center in *canvas-pixel* space and maps the
57 //corners to NDC. Doing it here (rather than in the vertex shader) keeps the
58 //rotation rigid regardless of canvas aspect - the compiled shader bakes in
59 //a 16:9 correction.
60 void RotatedQuadToNdc(float x, float y, float w, float h, float scaleX, float scaleY, float degrees, int designWidth, int designHeight, float corners[8])
61 {
62 const float cx = x + w * 0.5f;
63 const float cy = y + h * 0.5f;
64 const float hw = w * scaleX * 0.5f;
65 const float hh = h * scaleY * 0.5f;
66 const float rad = DegToRad(degrees);
67 const float c = cosf(rad);
68 const float s = sinf(rad);
69
70 static const float lx[4] = { -1.0f, 1.0f, 1.0f, -1.0f };
71 static const float ly[4] = { -1.0f, -1.0f, 1.0f, 1.0f }; // +1 = top of the quad
72
73 for(int i = 0; i < 4; i++)
74 {
75 float dx = lx[i] * hw;
76 float dy = -ly[i] * hh; // pixel space is y-down
77 float px = cx + dx * c - dy * s;
78 float py = cy + dx * s + dy * c;
79
80 corners[i * 2 + 0] = (px / designWidth) * 2.0f - 1.0f;
81 corners[i * 2 + 1] = 1.0f - (py / designHeight) * 2.0f;
82 }
83 }
84
85 // Flat-color draws take their color from colorIntensity, so the vertex
86 // color attribute is always solid white. Persistent so the pointer stays
87 // valid for the GPU-side copy ColorShader makes.
88 const uint8_t * WhiteVtxs()
89 {
90 static uint8_t whiteVtxs[ColorShader::cuColorVtxsSize];
91 static bool vtxsInit = false;
92 if(!vtxsInit)
93 {
94 memset(whiteVtxs, 0xFF, sizeof(whiteVtxs)); // 255 = Solid White
95 GX2Invalidate(GX2_INVALIDATE_MODE_CPU, whiteVtxs, sizeof(whiteVtxs));
96 vtxsInit = true;
97 }
98 return whiteVtxs;
99 }
100
101 volatile uint32_t systemFrameTimer = 0;
102 OSAlarm frameTimerAlarm;
103
104 void FrameTimerAlarmHandler(OSAlarm *, OSContext *)
105 {
106 __atomic_fetch_add(&systemFrameTimer, 1, __ATOMIC_RELAXED);
107 }
108
109 OSTime FrameTimerInterval()
110 {
111 if(GX2GetSystemTVScanMode() == GX2_TV_SCAN_MODE_576I)
112 return OSSecondsToTicks(1) / 50;
113
114 return (OSSecondsToTicks(1) * 1001) / 60000;
115 }
116}
117
118/****************************************************************************
119 * WutVideoDriver
120 ***************************************************************************/
121
122WutVideoDriver::WutVideoDriver()
123 : screenWidth(0), screenHeight(0), clearColor{0, 0, 0, 255}
124 , imageRenderer(nullptr), glyphRenderer(nullptr)
125{
126}
127
128WutVideoDriver::~WutVideoDriver()
129{
130 delete imageRenderer;
131 imageRenderer = nullptr;
132 delete glyphRenderer;
133 glyphRenderer = nullptr;
134 free(meshPositions);
135 free(meshColors);
136
137 freeRetiredMeshes();
138 delete retireLock;
139 retireLock = nullptr;
140}
141
142void WutVideoDriver::init(int width, int height)
143{
144 WHBGfxInit();
145
146 screenWidth = width;
147 screenHeight = height;
148
149 // The colour buffers libwhb allocated are the authoritative physical target sizes
150 const GX2ColorBuffer * tvBuffer = WHBGfxGetTVColourBuffer();
151 const GX2ColorBuffer * drcBuffer = WHBGfxGetDRCColourBuffer();
152 targetWidth[(int)OutputTarget::TV] = tvBuffer->surface.width ? (int)tvBuffer->surface.width : width;
153 targetHeight[(int)OutputTarget::TV] = tvBuffer->surface.height ? (int)tvBuffer->surface.height : height;
154 targetWidth[(int)OutputTarget::DRC] = drcBuffer->surface.width ? (int)drcBuffer->surface.width : width;
155 targetHeight[(int)OutputTarget::DRC] = drcBuffer->surface.height ? (int)drcBuffer->surface.height : height;
156
157 computeUIScale();
158
159 drawQueue.reserve(1024);
160 retireLock = new Mutex();
161
162 meshPositions = static_cast<float *>(memalign(GX2_VERTEX_BUFFER_ALIGNMENT, cuMeshArenaVertices * 3 * sizeof(float)));
163 meshColors = static_cast<uint8_t *>(memalign(GX2_VERTEX_BUFFER_ALIGNMENT, cuMeshArenaVertices * 4 * sizeof(uint8_t)));
164
165 imageRenderer = new WutImageRenderer(this);
166 glyphRenderer = new WutGlyphRenderer(this);
167
168 OSCreateAlarm(&frameTimerAlarm);
169 const OSTime interval = FrameTimerInterval();
170 OSSetPeriodicAlarm(&frameTimerAlarm, OSGetTime() + interval, interval, FrameTimerAlarmHandler);
171
172 prepareFrame();
173}
174
175void WutVideoDriver::computeUIScale()
176{
177 int tvWidth, tvHeight;
178 switch(GX2GetSystemTVScanMode())
179 {
180 case GX2_TV_SCAN_MODE_480I:
181 case GX2_TV_SCAN_MODE_480P:
182 if(GX2GetSystemTVAspectRatio() == GX2_ASPECT_RATIO_16_9)
183 {
184 tvWidth = 854;
185 tvHeight = 480;
186 }
187 else
188 {
189 tvWidth = 640;
190 tvHeight = 480;
191 }
192 break;
193 case GX2_TV_SCAN_MODE_1080I:
194 case GX2_TV_SCAN_MODE_1080P:
195 tvWidth = 1920;
196 tvHeight = 1080;
197 break;
198 case GX2_TV_SCAN_MODE_720P:
199 default:
200 tvWidth = 1280;
201 tvHeight = 720;
202 break;
203 }
204
205 // The GamePad's DRC screen is always this fixed size regardless of TV mode.
206 const int drcWidth = 854;
207 const int drcHeight = 480;
208
209 // PixelRectToNdc stretches the 640x480 canvas to fill each target independently
210 // per axis. uiScale only measures the worst (largest) of those four per-axis
211 // stretch ratios
212 uiScale = std::max({
213 (float)tvWidth / screenWidth, (float)tvHeight / screenHeight,
214 (float)drcWidth / screenWidth, (float)drcHeight / screenHeight
215 });
216}
217
219{
220 OSCancelAlarm(&frameTimerAlarm);
221 WHBGfxShutdown();
222}
223
225{
226 return platform->getStatus() == Status::Running;
227}
228
229void WutVideoDriver::prepareFrame(unsigned targets)
230{
231 // A new frame clears both targets below, so anything still queued from
232 // before this point would have been wiped by the clear anyway.
233 drawQueue.clear();
234 meshUsed = 0;
235 meshFlushed = 0;
236
237 if(!isForeground())
238 {
239 gpuFramesInFlight = false; // GX2 context is gone/reinitialised; stale timestamps are meaningless
240 return;
241 }
242
243 waitForIdle();
244
245 // Every frame that was submitted has now finished, so nothing can still be reading a mesh
246 // that was let go of before this frame began
247 freeRetiredMeshes();
248
249 auto drawPass = [&]() {
250 WHBGfxClearColor(clearColor.r / 255.0f, clearColor.g / 255.0f, clearColor.b / 255.0f, clearColor.a / 255.0f);
251 GX2SetDepthOnlyControl(GX2_DISABLE, GX2_DISABLE, GX2_COMPARE_FUNC_ALWAYS);
252 GX2SetColorControl(GX2_LOGIC_OP_COPY, 0xFF, GX2_DISABLE, GX2_ENABLE);
253 GX2SetCullOnlyControl(GX2_FRONT_FACE_CCW, GX2_DISABLE, GX2_DISABLE);
254 GX2SetBlendControl(GX2_RENDER_TARGET_0, GX2_BLEND_MODE_SRC_ALPHA, GX2_BLEND_MODE_INV_SRC_ALPHA, GX2_BLEND_COMBINE_MODE_ADD, GX2_DISABLE, GX2_BLEND_MODE_SRC_ALPHA, GX2_BLEND_MODE_INV_SRC_ALPHA, GX2_BLEND_COMBINE_MODE_ADD);
255 };
256
257 if(targets & (1u << (unsigned)OutputTarget::TV))
258 {
259 WHBGfxBeginRenderTV();
260 drawPass();
261 }
262 if(targets & (1u << (unsigned)OutputTarget::DRC))
263 {
264 WHBGfxBeginRenderDRC();
265 drawPass();
266 }
267
268 // Every color-shaded draw this frame gets its own slot in ColorShader's
269 // GX2R buffer (see ColorShader.h) - rewind the counter here, once per
270 // frame, before anything draws into it.
273}
274
276{
277 uint32_t swapCount, flipCount;
278 OSTime lastFlip, lastVsync;
279 GX2GetSwapStatus(&swapCount, &flipCount, &lastFlip, &lastVsync);
280
281 if(flipCount < swapCount)
282 return true;
283
284 return gpuFramesInFlight && GX2GetRetiredTimeStamp() < lastSubmitTimeStamp;
285}
286
288{
289 if(!isForeground())
290 {
291 gpuFramesInFlight = false; // GX2 context is gone/reinitialised; stale timestamps are meaningless
292 return;
293 }
294
295 if(gpuFramesInFlight)
296 {
297 if(GX2GetRetiredTimeStamp() < lastSubmitTimeStamp)
298 GX2WaitTimeStamp(lastSubmitTimeStamp);
299 gpuFramesInFlight = false;
300 }
301}
302
304{
305 presentBuffer();
306 prepareFrame();
307}
308
310{
311 if(drawQueue.size() >= cuMaxQueuedDraws)
313
314 drawQueue.push_back(cmd);
315}
316
317void WutVideoDriver::queueMesh(const MeshVertex* vertices, size_t count)
318{
319 count -= count % 3;
320 if(!vertices || count == 0 || !meshPositions || !meshColors || count > cuMeshArenaVertices - meshUsed)
321 return;
322
323 const float invW = 2.0f / screenWidth;
324 const float invH = 2.0f / screenHeight;
325 float * pos = meshPositions + (size_t)meshUsed * 3;
326 uint8_t * col = meshColors + (size_t)meshUsed * 4;
327
328 for(size_t i = 0; i < count; i++)
329 {
330 const MeshVertex& v = vertices[i];
331 pos[i * 3 + 0] = v.x * invW - 1.0f;
332 pos[i * 3 + 1] = 1.0f - v.y * invH;
333 pos[i * 3 + 2] = 0.0f;
334 col[i * 4 + 0] = v.c.r;
335 col[i * 4 + 1] = v.c.g;
336 col[i * 4 + 2] = v.c.b;
337 col[i * 4 + 3] = v.c.a;
338 }
339
340 WutDrawCmd cmd;
341 cmd.kind = WutDrawCmd::Kind::Mesh;
342 cmd.slot = 0;
343 cmd.texture = nullptr;
344 cmd.sampler = nullptr;
345 cmd.angle = 0.0f;
346 cmd.first = meshUsed;
347 cmd.count = (uint32_t)count;
348 meshUsed += (uint32_t)count;
349 queueDraw(cmd);
350}
351
352void WutVideoDriver::queueMeshInstance(const WutMesh* mesh, float x, float y, float scaleX, float scaleY)
353{
354 if(!mesh || mesh->count == 0)
355 return;
356
357 // The mesh's positions are NDC for an instance at the canvas origin (see createMesh()), so
358 // placing it is the shader's own offset and scale: ndc = position * scale + offset
359 const float invW = 2.0f / screenWidth;
360 const float invH = 2.0f / screenHeight;
361
362 WutDrawCmd cmd;
364 cmd.slot = 0;
365 cmd.texture = nullptr;
366 cmd.sampler = nullptr;
367 cmd.angle = 0.0f;
368 cmd.offset[0] = x * invW - 1.0f;
369 cmd.offset[1] = 1.0f - y * invH;
370 cmd.offset[2] = 0.0f;
371 cmd.scale[0] = scaleX;
372 cmd.scale[1] = scaleY;
373 cmd.scale[2] = 1.0f;
374 cmd.colorIntensity[0] = cmd.colorIntensity[1] = cmd.colorIntensity[2] = cmd.colorIntensity[3] = 1.0f;
375 cmd.first = 0;
376 cmd.count = mesh->count;
377 cmd.mesh = mesh;
378 queueDraw(cmd);
379}
380
382{
383 if(!mesh)
384 return;
385
386 if(retireLock)
387 retireLock->lock();
388 mesh->next = retiredMeshes;
389 retiredMeshes = mesh;
390 if(retireLock)
391 retireLock->unlock();
392}
393
394void WutVideoDriver::freeRetiredMeshes()
395{
396 if(retireLock)
397 retireLock->lock();
398 WutMesh * list = retiredMeshes;
399 retiredMeshes = nullptr;
400 if(retireLock)
401 retireLock->unlock();
402
403 while(list)
404 {
405 WutMesh * next = list->next;
406 free(list->positions);
407 free(list->colors);
408 delete list;
409 list = next;
410 }
411}
412
413void WutVideoDriver::replayDrawQueue() const
414{
415 Texture2DShader * textureShader = Texture2DShader::instance();
416 ColorShader * colorShader = ColorShader::instance();
417
418 static const float identityOffset[3] = { 0.0f, 0.0f, 0.0f };
419 static const float identityScale[3] = { 1.0f, 1.0f, 1.0f };
420
421 enum class BoundShader { None, Texture, Color };
422 BoundShader bound = BoundShader::None;
423 bool defaultQuadBound = false;
424 bool meshBound = false; // ColorShader currently has the mesh arena bound as its vertex streams
425 const GX2Texture * lastTexture = nullptr;
426 const GX2Sampler * lastSampler = nullptr;
427
428 for(const WutDrawCmd& cmd : drawQueue)
429 {
430 if(cmd.kind == WutDrawCmd::Kind::MeshInstance)
431 {
432 if(bound != BoundShader::Color)
433 {
434 colorShader->setShaders();
435 bound = BoundShader::Color;
436 }
437 // Its own vertex streams and placement; the arena must be bound again afterwards
438 colorShader->bindMesh(cmd.mesh->positions, cmd.mesh->colors, cmd.mesh->count, cmd.offset, cmd.scale);
439 ColorShader::drawMesh(0, cmd.count);
440 meshBound = false;
441 continue;
442 }
443
444 if(cmd.kind == WutDrawCmd::Kind::Mesh)
445 {
446 if(bound != BoundShader::Color)
447 {
448 colorShader->setShaders();
449 bound = BoundShader::Color;
450 meshBound = false;
451 }
452 if(!meshBound)
453 {
454 colorShader->bindMesh(meshPositions, meshColors, cuMeshArenaVertices);
455 meshBound = true;
456 }
457 ColorShader::drawMesh(cmd.first, cmd.count);
458 continue;
459 }
460
461 if(cmd.kind == WutDrawCmd::Kind::Color)
462 {
463 if(bound != BoundShader::Color)
464 {
465 colorShader->setShaders();
466 bound = BoundShader::Color;
467 }
468 meshBound = false; // the quad draw below rebinds the vertex streams
469
470 colorShader->setAttributeBuffer(WhiteVtxs());
471 colorShader->setAngle(0.0f);
472 colorShader->setOffset(cmd.offset);
473 colorShader->setScale(cmd.scale);
474 colorShader->setColorIntensity(cmd.colorIntensity);
475 colorShader->draw(GX2_PRIMITIVE_MODE_QUADS, 4);
476 continue;
477 }
478
479 if(bound != BoundShader::Texture)
480 {
481 textureShader->setShaders();
482 textureShader->clearBlur();
483 bound = BoundShader::Texture;
484 meshBound = false;
485 defaultQuadBound = false;
486 lastTexture = nullptr;
487 lastSampler = nullptr;
488 }
489
491 {
492 textureShader->setRotatedAttributeBuffer(cmd.slot);
493 defaultQuadBound = false;
494 textureShader->setAngle(0.0f);
495 textureShader->setOffset(identityOffset);
496 textureShader->setScale(identityScale);
497 }
498 else
499 {
500 if(!defaultQuadBound)
501 {
502 textureShader->setAttributeBuffer();
503 defaultQuadBound = true;
504 }
505 textureShader->setAngle(cmd.angle);
506 textureShader->setOffset(cmd.offset);
507 textureShader->setScale(cmd.scale);
508 }
509
510 textureShader->setColorIntensity(cmd.colorIntensity);
511
512 if(cmd.texture != lastTexture || cmd.sampler != lastSampler)
513 {
514 textureShader->setTextureAndSampler(cmd.texture, cmd.sampler);
515 lastTexture = cmd.texture;
516 lastSampler = cmd.sampler;
517 }
518
519 textureShader->draw(GX2_PRIMITIVE_MODE_QUADS, 4);
520 }
521}
522
524{
525 if(drawQueue.empty())
526 return;
527
528 if(!isForeground())
529 {
530 drawQueue.clear();
531 return;
532 }
533
534 // Make the vertices written since the last flush visible to the GPU
535 if(meshUsed > meshFlushed)
536 {
537 GX2Invalidate(GX2_INVALIDATE_MODE_CPU_ATTRIBUTE_BUFFER, meshPositions + (size_t)meshFlushed * 3, (meshUsed - meshFlushed) * 3 * sizeof(float));
538 GX2Invalidate(GX2_INVALIDATE_MODE_CPU_ATTRIBUTE_BUFFER, meshColors + (size_t)meshFlushed * 4, (meshUsed - meshFlushed) * 4 * sizeof(uint8_t));
539 meshFlushed = meshUsed;
540 }
541
542 if(uiTargets & (1u << (unsigned)OutputTarget::TV))
543 {
544 WHBGfxBeginRenderTV();
545 replayDrawQueue();
546 }
547 if(uiTargets & (1u << (unsigned)OutputTarget::DRC))
548 {
549 WHBGfxBeginRenderDRC();
550 replayDrawQueue();
551 }
552
553 drawQueue.clear();
554}
555
556void WutVideoDriver::presentBuffer()
557{
558 if(!isForeground())
559 {
560 drawQueue.clear();
561 return;
562 }
563
565
566 WHBGfxBeginRender();
567 WHBGfxFinishRenderTV();
568 WHBGfxFinishRenderDRC();
569
570 GX2SwapScanBuffers();
571 GX2Flush();
572 GX2SetTVEnable(TRUE);
573 GX2SetDRCEnable(TRUE);
574 lastSubmitTimeStamp = GX2GetLastSubmittedTimeStamp();
575 gpuFramesInFlight = true;
576}
577
578// The alarm handler increments the timer (possibly on another core), so reads
579// go through an atomic load.
581{
582 return __atomic_load_n(&systemFrameTimer, __ATOMIC_RELAXED);
583}
584
586{
587 clearColor = color;
588}
589
591{
592 return GX2GetSystemTVScanMode() == GX2_TV_SCAN_MODE_576I ? 50 : 60;
593}
594
596{
597 return GX2GetSystemTVScanMode() == GX2_TV_SCAN_MODE_576I ? (1.0f / 50.0f) : (1.0f / 60.0f);
598}
599
600/****************************************************************************
601 * WutImageRenderer
602 ***************************************************************************/
603
605 : driver(driver_)
606{
607 GX2InitSampler(&sampler, GX2_TEX_CLAMP_MODE_CLAMP, GX2_TEX_XY_FILTER_MODE_LINEAR);
608}
609
610void * WutImageRenderer::createTexture(int width, int height)
611{
612 if(width <= 0 || height <= 0)
613 return nullptr;
614
615 GX2Texture * texture = new GX2Texture();
616 GX2InitTexture(texture, width, height, 1, 0, GX2_SURFACE_FORMAT_UNORM_R8_G8_B8_A8, GX2_SURFACE_DIM_TEXTURE_2D, GX2_TILE_MODE_LINEAR_ALIGNED);
617
618 GX2CalcSurfaceSizeAndAlignment(&texture->surface);
619 GX2InitTextureRegs(texture);
620
621 texture->surface.image = MEMAllocFromDefaultHeapEx(texture->surface.imageSize, texture->surface.alignment);
622 if(!texture->surface.image)
623 {
624 delete texture;
625 return nullptr;
626 }
627
628 return texture;
629}
630
631void WutImageRenderer::loadTextureData(void * texture, const uint8_t * rgba, int width, int height)
632{
633 if(!texture || !rgba || width <= 0 || height <= 0)
634 return;
635
636 GX2Texture * tex = static_cast<GX2Texture *>(texture);
637 if(!tex->surface.image)
638 return;
639
640 uint8_t * dst = static_cast<uint8_t *>(tex->surface.image);
641 const uint32_t dstStride = tex->surface.pitch * 4;
642 const uint32_t srcStride = static_cast<uint32_t>(width) * 4;
643 for(int y = 0; y < height; y++)
644 memcpy(dst + y * dstStride, rgba + y * srcStride, srcStride);
645
646 GX2Invalidate(GX2_INVALIDATE_MODE_CPU_TEXTURE, tex->surface.image, tex->surface.imageSize);
647}
648
649bool WutImageRenderer::canReuseTexture(int allocWidth, int allocHeight, int width, int height) const
650{
651 return width == allocWidth && height == allocHeight;
652}
653
655{
656 if(!texture)
657 return;
658
659 driver->flushDrawQueue();
660
661 GX2Texture * tex = static_cast<GX2Texture *>(texture);
662 if(tex->surface.image)
663 MEMFreeToDefaultHeap(tex->surface.image);
664 delete tex;
665}
666
667void WutImageRenderer::drawTexture(void * texture, float xpos, float ypos, uint16_t width, uint16_t height, float degrees, float scaleX, float scaleY, uint8_t alpha)
668{
669 if(!texture || !driver->isForeground())
670 return;
671
672 WutDrawCmd cmd;
673 cmd.kind = WutDrawCmd::Kind::Texture;
674 cmd.slot = 0;
675 cmd.texture = static_cast<GX2Texture *>(texture);
676 cmd.sampler = &sampler;
677 cmd.angle = 0.0f;
678 PixelRectToNdc(xpos, ypos, width, height, scaleX, scaleY, driver->getScreenWidth(), driver->getScreenHeight(), cmd.offset, cmd.scale);
679 cmd.colorIntensity[0] = 1.0f;
680 cmd.colorIntensity[1] = 1.0f;
681 cmd.colorIntensity[2] = 1.0f;
682 cmd.colorIntensity[3] = alpha / 255.0f;
683
684 // Rotated draws get pre-rotated corners and an identity shader transform.
685 // If the per-frame slots ever run out, fall back to the shader's own
686 // rotation rather than dropping the draw.
687 if(degrees != 0.0f)
688 {
689 float corners[8];
690 RotatedQuadToNdc(xpos, ypos, width, height, scaleX, scaleY, degrees, driver->getScreenWidth(), driver->getScreenHeight(), corners);
691
692 uint32_t rotatedSlot = 0;
693 if(Texture2DShader::instance()->uploadRotatedQuad(corners, rotatedSlot))
694 {
696 cmd.slot = rotatedSlot;
697 }
698 else
699 {
700 cmd.angle = DegToRad(degrees);
701 }
702 }
703
704 driver->queueDraw(cmd);
705}
706
707void WutImageRenderer::drawMesh(const MeshVertex* vertices, size_t count)
708{
709 if(!driver->isForeground())
710 return;
711
712 driver->queueMesh(vertices, count);
713}
714
715void * WutImageRenderer::createMesh(const MeshVertex* vertices, size_t count)
716{
717 count -= count % 3;
718 if(!vertices || count == 0)
719 return nullptr;
720
721 WutMesh * m = new (std::nothrow) WutMesh;
722 if(!m)
723 return nullptr;
724 m->positions = static_cast<float *>(memalign(GX2_VERTEX_BUFFER_ALIGNMENT, count * 3 * sizeof(float)));
725 m->colors = static_cast<uint8_t *>(memalign(GX2_VERTEX_BUFFER_ALIGNMENT, count * 4 * sizeof(uint8_t)));
726 m->count = (uint32_t)count;
727 m->next = nullptr;
728 if(!m->positions || !m->colors)
729 {
730 free(m->positions);
731 free(m->colors);
732 delete m;
733 return nullptr;
734 }
735
736 // NDC for an instance at the canvas origin at scale 1, without the -1/+1 shift: a draw adds
737 // that, and its own placement, through the shader's offset and scale (queueMeshInstance())
738 const float invW = 2.0f / driver->getScreenWidth();
739 const float invH = 2.0f / driver->getScreenHeight();
740 for(size_t i = 0; i < count; i++)
741 {
742 const MeshVertex& v = vertices[i];
743 m->positions[i * 3 + 0] = v.x * invW;
744 m->positions[i * 3 + 1] = -v.y * invH;
745 m->positions[i * 3 + 2] = 0.0f;
746 m->colors[i * 4 + 0] = v.c.r;
747 m->colors[i * 4 + 1] = v.c.g;
748 m->colors[i * 4 + 2] = v.c.b;
749 m->colors[i * 4 + 3] = v.c.a;
750 }
751
752 // written once, here, and only read by the GPU after this
753 GX2Invalidate(GX2_INVALIDATE_MODE_CPU_ATTRIBUTE_BUFFER, m->positions, count * 3 * sizeof(float));
754 GX2Invalidate(GX2_INVALIDATE_MODE_CPU_ATTRIBUTE_BUFFER, m->colors, count * 4 * sizeof(uint8_t));
755 return m;
756}
757
759{
760 // Draws recorded this frame, or a frame the GPU is still running, may read it
761 driver->retireMesh(static_cast<WutMesh *>(mesh));
762}
763
764void WutImageRenderer::drawMeshInstance(void * mesh, float x, float y, float scaleX, float scaleY)
765{
766 if(!mesh || !driver->isForeground())
767 return;
768
769 driver->queueMeshInstance(static_cast<const WutMesh *>(mesh), x, y, scaleX, scaleY);
770}
771
772void WutImageRenderer::drawRectangle(float x, float y, float width, float height, PixelColor color)
773{
774 if(!driver->isForeground())
775 return;
776
777 WutDrawCmd cmd;
778 cmd.kind = WutDrawCmd::Kind::Color;
779 cmd.slot = 0;
780 cmd.texture = nullptr;
781 cmd.sampler = nullptr;
782 cmd.angle = 0.0f;
783 PixelRectToNdc(x, y, width, height, 1.0f, 1.0f, driver->getScreenWidth(), driver->getScreenHeight(), cmd.offset, cmd.scale);
784 cmd.colorIntensity[0] = color.r / 255.0f;
785 cmd.colorIntensity[1] = color.g / 255.0f;
786 cmd.colorIntensity[2] = color.b / 255.0f;
787 cmd.colorIntensity[3] = color.a / 255.0f;
788
789 driver->queueDraw(cmd);
790}
791
792/****************************************************************************
793 * WutGlyphRenderer
794 ***************************************************************************/
795
797 : driver(driver_)
798{
799 GX2InitSampler(&sampler, GX2_TEX_CLAMP_MODE_CLAMP, GX2_TEX_XY_FILTER_MODE_LINEAR);
800}
801
802void * WutGlyphRenderer::createTexture(uint16_t width, uint16_t height)
803{
804 GX2Texture * texture = new GX2Texture();
805 GX2InitTexture(texture, width == 0 ? 1 : width, height == 0 ? 1 : height, 1, 0, GX2_SURFACE_FORMAT_UNORM_R8, GX2_SURFACE_DIM_TEXTURE_2D, GX2_TILE_MODE_LINEAR_ALIGNED);
806
807 GX2CalcSurfaceSizeAndAlignment(&texture->surface);
808
809 texture->surface.image = MEMAllocFromDefaultHeapEx(texture->surface.imageSize, texture->surface.alignment);
810 if(!texture->surface.image)
811 {
812 delete texture;
813 return nullptr;
814 }
815 memset(texture->surface.image, 0x00, texture->surface.imageSize);
816
817 // Broadcast the single R8 channel to R,G,B,A on sample (compMap's four
818 // 8-bit fields each select source component 0/R) - same trick as the
819 // Wii driver's GX_TF_I4 intensity textures, so Texture2DShader's
820 // texture*colorIntensity modulate produces anti-aliased, colorable
821 // glyphs without a dedicated single-channel shader.
822 texture->compMap = 0x00000000;
823 GX2InitTextureRegs(texture);
824
825 return texture;
826}
827
828void WutGlyphRenderer::loadTextureData(void * texturePtr, FT_Bitmap * bitmap)
829{
830 if(!texturePtr || !bitmap || !bitmap->buffer)
831 return;
832
833 GX2Texture * texture = static_cast<GX2Texture *>(texturePtr);
834 if(!texture->surface.image)
835 return;
836
837 uint8_t * dst = static_cast<uint8_t *>(texture->surface.image);
838 uint8_t * src = bitmap->buffer;
839
840 uint32_t copyWidth = (bitmap->width < texture->surface.width) ? bitmap->width : texture->surface.width;
841 uint32_t copyHeight = (bitmap->rows < texture->surface.height) ? bitmap->rows : texture->surface.height;
842
843 uint32_t srcPitch = bitmap->pitch < 0 ? -bitmap->pitch : bitmap->pitch;
844 if(srcPitch == 0)
845 srcPitch = bitmap->width;
846
847 for(uint32_t y = 0; y < copyHeight; y++)
848 memcpy(dst + y * texture->surface.pitch, src + y * srcPitch, copyWidth);
849
850 GX2Invalidate(GX2_INVALIDATE_MODE_CPU_TEXTURE, texture->surface.image, texture->surface.imageSize);
851}
852
853void WutGlyphRenderer::destroyTexture(void * texturePtr)
854{
855 if(!texturePtr)
856 return;
857
858 driver->flushDrawQueue();
859
860 GX2Texture * texture = static_cast<GX2Texture *>(texturePtr);
861 if(texture->surface.image)
862 MEMFreeToDefaultHeap(texture->surface.image);
863 delete texture;
864}
865
866void WutGlyphRenderer::drawQuad(void * texturePtr, int16_t screenX, int16_t screenY, uint16_t width, uint16_t height, const PixelColor& color)
867{
868 if(!texturePtr || !driver->isForeground())
869 return;
870
871 WutDrawCmd cmd;
872 cmd.kind = WutDrawCmd::Kind::Texture;
873 cmd.slot = 0;
874 cmd.texture = static_cast<GX2Texture *>(texturePtr);
875 cmd.sampler = &sampler;
876 cmd.angle = 0.0f;
877 PixelRectToNdc(screenX, screenY, width, height, 1.0f, 1.0f, driver->getScreenWidth(), driver->getScreenHeight(), cmd.offset, cmd.scale);
878 cmd.colorIntensity[0] = color.r / 255.0f;
879 cmd.colorIntensity[1] = color.g / 255.0f;
880 cmd.colorIntensity[2] = color.b / 255.0f;
881 cmd.colorIntensity[3] = color.a / 255.0f;
882
883 driver->queueDraw(cmd);
884}
885
886void WutGlyphRenderer::drawFeature(int16_t screenX, int16_t screenY, uint16_t width, uint16_t height, const PixelColor& color)
887{
888 if(!driver->isForeground())
889 return;
890
891 WutDrawCmd cmd;
892 cmd.kind = WutDrawCmd::Kind::Color;
893 cmd.slot = 0;
894 cmd.texture = nullptr;
895 cmd.sampler = nullptr;
896 cmd.angle = 0.0f;
897 PixelRectToNdc(screenX, screenY, width, height, 1.0f, 1.0f, driver->getScreenWidth(), driver->getScreenHeight(), cmd.offset, cmd.scale);
898 cmd.colorIntensity[0] = color.r / 255.0f;
899 cmd.colorIntensity[1] = color.g / 255.0f;
900 cmd.colorIntensity[2] = color.b / 255.0f;
901 cmd.colorIntensity[3] = color.a / 255.0f;
902
903 driver->queueDraw(cmd);
904}
void setOffset(const float offset[3])
Sets the NDC position of the quad's center.
void resetFrame()
Call once per frame, before any draws - rewinds the per-draw color slot counter so this frame starts ...
Definition ColorShader.h:86
void bindMesh(const float *positions, const uint8_t *colors, uint32_t vertexCapacity, const float *offset=nullptr, const float *scale=nullptr)
Binds caller-owned vertex streams for mesh draws and sets the uniforms so vertices pass through uncha...
void setColorIntensity(const float colorIntensity[4])
Sets the RGBA color the quad is filled with.
void setAttributeBuffer(const uint8_t *colorAttr)
Definition ColorShader.h:99
static void drawMesh(uint32_t firstVertex, uint32_t vertexCount)
Draws triangles from the streams bound with bindMesh().
void setShaders() const
Binds the fetch, vertex and pixel shaders for subsequent draws.
Definition ColorShader.h:72
void setAngle(float angleRadians)
Sets the quad rotation in radians.
static const uint32_t cuColorVtxsSize
Size in bytes of the four per-vertex colors of one quad.
Definition ColorShader.h:54
void setScale(const float scale[3])
Sets the NDC half-extents of the quad.
static ColorShader * instance()
Definition ColorShader.h:57
A simple mutual-exclusion lock.
Definition Mutex.h:14
void unlock()
Releases the mutex. Must be called by the thread that locked it.
Definition Mutex.cpp:28
void lock()
Blocks until the mutex is acquired. Not recursive: locking twice from one thread deadlocks.
Definition Mutex.cpp:22
static void draw(int32_t primitive=GX2_PRIMITIVE_MODE_QUADS, uint32_t vtxCount=4)
Issues the draw call for the currently bound shaders and attribute buffers.
Definition Shader.h:81
void setOffset(const float offset[3])
void setRotatedAttributeBuffer(uint32_t slot) const
Binds a slot from uploadRotatedQuad() as the position stream (plus the shared texcoords).
void setAttributeBuffer() const
Binds the quad position and texture-coordinate buffers.
void resetFrame()
Call once per frame, before any draws - rewinds the rotated-quad slot counter (see WutVideoDriver::pr...
static Texture2DShader * instance()
void clearBlur() const
The compiled pixel shader reads a per-draw blur-direction uniform that this driver doesn't expose as ...
void setTextureAndSampler(const GX2Texture *texture, const GX2Sampler *sampler) const
Binds the texture to sample and the sampler describing its filtering.
void setColorIntensity(const float colorIntensity[4])
void setScale(const float scale[3])
void setAngle(float angleRadians)
void setShaders() const
Binds the fetch, vertex and pixel shaders for subsequent draws.
GX2-backed GlyphRenderer for GuiTextRenderer, using Texture2DShader for glyph quads and ColorShader f...
void drawQuad(void *texture, int16_t screenX, int16_t screenY, uint16_t width, uint16_t height, const PixelColor &color) override
Draws a glyph texture as a quad tinted with color.
void drawFeature(int16_t screenX, int16_t screenY, uint16_t width, uint16_t height, const PixelColor &color) override
Draws a solid-color rectangle used for text decoration (underline/strike-through).
void destroyTexture(void *texture) override
Destroys a texture created by createTexture().
WutGlyphRenderer(WutVideoDriver *driver)
void * createTexture(uint16_t width, uint16_t height) override
Allocates an empty single-channel glyph texture. Returns an opaque handle.
void loadTextureData(void *texture, FT_Bitmap *bitmap) override
Uploads a rasterized FreeType glyph bitmap into a texture from createTexture().
GX2-backed ImageRenderer for GuiImage/GuiImageData, using Texture2DShader.
void drawMeshInstance(void *mesh, float x, float y, float scaleX, float scaleY) override
Draws a mesh from createMesh() fully opaque, as its vertex colors say.
void destroyTexture(void *texture) override
Destroys a texture created by createTexture.
void destroyMesh(void *mesh) override
Releases a mesh from createMesh().
void loadTextureData(void *texture, const uint8_t *rgba, int width, int height) override
Loads raw RGBA8 pixels into the pre-allocated texture.
void * createTexture(int width, int height) override
Allocates an empty hardware-aligned texture buffer.
void drawRectangle(float x, float y, float width, float height, PixelColor color) override
Draws a solid-color rectangle (alpha-blended using color.a).
void drawMesh(const MeshVertex *vertices, size_t count) override
Draws a triangle list: three vertices per triangle, each with its own color.
void * createMesh(const MeshVertex *vertices, size_t count) override
Hands a triangle list to the backend to keep in its own ready-to-draw form (a GX display list,...
void drawTexture(void *texture, float xpos, float ypos, uint16_t width, uint16_t height, float degrees, float scaleX, float scaleY, uint8_t alpha) override
Draws a texture created by createTexture.
bool canReuseTexture(int allocWidth, int allocHeight, int width, int height) const override
Whether a texture allocated for allocWidth x allocHeight can be reloaded in place.
WutImageRenderer(WutVideoDriver *driver)
Wii U VideoDriver: GX2 + libwhb's WHBGfx* helpers.
uint32_t getFrameTimer() override
void clearScreen(const PixelColor &color) override
Clears the current frame buffer.
void queueDraw(const WutDrawCmd &cmd)
Records a UI draw for the next flushDrawQueue().
bool isPresentPending() const
True while the last presented frame has not finished: not yet on screen (the swap is waiting for the ...
void retireMesh(WutMesh *mesh)
Sets a mesh aside to be freed once the GPU is done with every frame that might draw it.
void queueMesh(const MeshVertex *vertices, size_t count)
Copies a triangle list into this frame's vertex arena (converting positions to NDC) and records a Mes...
float getDeltaTime() const override
void render() override
Flushes the current frame to the screen and swaps buffers.
int getRefreshRate() const override
void init(int width, int height) override
void queueMeshInstance(const WutMesh *mesh, float x, float y, float scaleX, float scaleY)
Records a draw of a mesh made by WutImageRenderer::createMesh().
void shutdown() override
Releases the video backend. Call after every other user of the renderers has finished.
void flushDrawQueue()
Replays every recorded UI draw into the TV context, then the same list into the GamePad context,...
void waitForIdle()
Waits until the GPU has retired the last submitted frame.
bool isForeground() const
False once the OS has taken away the foreground (HOME menu overlay, forced exit, etc....
A single 2D vertex with its own color, in design-canvas coordinates.
Definition Mesh.h:27
float y
Y position (design-canvas pixels).
Definition Mesh.h:29
float x
X position (design-canvas pixels).
Definition Mesh.h:28
PixelColor c
Straight (non-premultiplied) RGBA color.
Definition Mesh.h:30
8-bit-per-channel RGBA color used by the video interfaces.
Definition Mesh.h:16
uint8_t g
Green color component.
Definition Mesh.h:18
uint8_t r
Red color component.
Definition Mesh.h:17
uint8_t b
Blue color component.
Definition Mesh.h:19
uint8_t a
Alpha component.
Definition Mesh.h:20
One recorded UI draw (image, glyph quad or flat rectangle).
@ Mesh
triangle list from the mesh arena through ColorShader (see WutVideoDriver::queueMesh())
@ TextureRotated
textured quad whose corners were pre-rotated into a Texture2DShader rotated slot
@ Texture
textured quad through Texture2DShader, shader-side angle/offset/scale
@ Color
flat-color quad through ColorShader
@ MeshInstance
a WutMesh through ColorShader, placed by the shader-side offset/scale (see WutVideoDriver::queueMeshI...
float offset[3]
NDC position (unused by TextureRotated)
const GX2Sampler * sampler
Texture/TextureRotated only.
float angle
Texture only, radians.
float scale[3]
NDC half-extents (unused by TextureRotated)
uint32_t slot
TextureRotated only: slot from Texture2DShader::uploadRotatedQuad()
uint32_t first
Mesh only: index of the first vertex in the arena.
uint32_t count
Mesh only: number of vertices.
const GX2Texture * texture
Texture/TextureRotated only.
const WutMesh * mesh
MeshInstance only.
A mesh kept in GPU-readable vertex buffers across frames (ImageRenderer::createMesh()).
WutMesh * next
Link in the retired list.
uint32_t count
Number of vertices.
uint8_t * colors
RGBA8 per vertex.
float * positions
3 floats per vertex (x, y, 0)