920 lines33 KBC
1/*
2video_ps3.c: the screen.
3
4The RSX draws the UI: gfx.c hands over a list of quads (gpu.h) and they're
5drawn straight into the display buffer at the display's own resolution
6(1080p when the PS3 is set to it), with one pair of tiny shaders
7(tools/shaders/ui.*). If the RSX can't be used - a self-test at start-up
8fails, or the last start froze during it - the CPU draws the canvas at
91280x720 and it's copied into the display buffer, as before.
10
11The rules (NZ:P PS3's ps3_video.c, Doom64-PS3, TyrQuakeCell and Quake2PS3
12before it; RSX Rally for the drawing):
13 - framebuffers on 1 MB boundaries (the XMB's screenshot froze the console
14 with 64-byte aligned ones: Grid Siege v2.6.9);
15 - no gcmSetWaitFlip: the CPU checks the flip has happened before it
16 touches anything the last frame used (its buffer, vertices, textures);
17 - room in the command buffer is made before each group of commands, with
18 a correct call into libgcm (PSL1GHT's own segment change can leave gaps
19 the RSX runs as garbage: found by rsxkit, used by RSX Rally);
20 - every self-test step is written to disk (synced) before it runs, and a
21 marker file stays until 300 frames have been drawn, so a freeze points
22 at its step and the next start uses the CPU instead;
23 - on the way out, the flip handler goes first.
24*/
25#include <stdio.h>
26#include <stdlib.h>
27#include <string.h>
28#include <stdarg.h>
29#include <malloc.h>
30#include <unistd.h>
31
32#include <ppu-types.h>
33#include <rsx/rsx.h>
34#include <rsx/gcm_sys.h>
35#include <sysutil/video.h>
36#include <sys/file.h>
37#include <lv2/sysfs.h>
38
39#include "../plat.h"
40#include "../input.h"
41#include "../gfx.h"
42#include "../gpu.h"
43#include "ps3.h"
44#include "shaders_ui.h"
45
46#ifndef APP_DIR
47#define APP_DIR "/dev_hdd0/game/PSWD00001/USRDIR"
48#endif
49#define GPU_LOG APP_DIR "/ps3world_gpu_log.txt"
50#define GPU_TRYING APP_DIR "/gpu_trying" // there while the RSX is still on trial
51#define USE_CPU APP_DIR "/use_cpu" // Settings > Picture: the CPU draws
52#define GPU_FAILED APP_DIR "/gpu_failed" // the RSX froze once: the CPU draws (delete it to try again)
53
54#define FB_COUNT 2
55#define CB_SIZE (1 * 1024 * 1024)
56#define IO_SIZE (16 * 1024 * 1024)
57
58static gcmContextData *ctx;
59static void *io_buffer;
60static u32 *fb[FB_COUNT];
61static u32 fb_offset[FB_COUNT];
62static int disp_w, disp_h, disp_pitch;
63static int bar_h; // a 4:3 TV: the 16:9 canvas letterboxed, this many black rows above and below
64static int cur;
65static volatile u32 flips_done;
66static u32 flips_queued;
67static int ready;
68static int *xmap; // canvas column for each screen column (when not 1280x720)
69
70#define CANVAS_BYTES (SCREEN_W * SCREEN_H * 4)
71static uint32_t *cpu_canvas; // the CPU path draws here; copied to the framebuffer
72
73static void FlipHandler(const u32 head)
74{
75 (void)head;
76 flips_done++;
77}
78
79static void Wait_Flips(void)
80{
81 for (int waited = 0; flips_done != flips_queued && waited < 2000; waited++)
82 usleep(100);
83}
84
85static void Flip(void)
86{
87 gcmSetFlip(ctx, cur);
88 rsxFlushBuffer(ctx);
89 flips_queued++;
90 cur ^= 1;
91}
92
93/*
94==================
95The log for the RSX path: each line on disk before the next step
96==================
97*/
98static void Sync_Write(const char *path, const char *text, int trunc)
99{
100 s32 fd = -1;
101 if (sysLv2FsOpen(path, SYS_O_WRONLY | SYS_O_CREAT | (trunc ? SYS_O_TRUNC : SYS_O_APPEND), &fd, 0666, NULL, 0) != 0 || fd < 0)
102 return;
103 u64 w = 0;
104 sysLv2FsWrite(fd, text, strlen(text), &w);
105 sysLv2FsFsync(fd);
106 sysLv2FsClose(fd);
107}
108
109static void Glog(const char *fmt, ...)
110{
111 char line[256], out[300];
112 va_list ap;
113 va_start(ap, fmt);
114 vsnprintf(line, sizeof(line), fmt, ap);
115 va_end(ap);
116 snprintf(out, sizeof(out), "[%.2f] %s\n", plat_time_us() / 1e6, line);
117 Sync_Write(GPU_LOG, out, 0);
118 plat_log("gpu: %s", line);
119}
120
121static int File_Exists(const char *path)
122{
123 s32 fd = -1;
124 if (sysLv2FsOpen(path, SYS_O_RDONLY, &fd, 0, NULL, 0) != 0 || fd < 0) return 0;
125 sysLv2FsClose(fd);
126 return 1;
127}
128
129/*
130==================
131Command buffer room (see the top)
132==================
133*/
134static s32 __attribute__((noinline)) Fifo_Callback(gcmContextData *context, u32 count)
135{
136 register u64 r3 __asm__("r3") = (u64)(uintptr_t)context;
137 register u64 r4 __asm__("r4") = count;
138 u32 opd = (u32)(uintptr_t)context->callback; // a 32-bit function descriptor: entry, TOC
139 __asm__ __volatile__(
140 "stdu 1,-128(1)\n\t"
141 "std 2,40(1)\n\t"
142 "lwz 0,0(%2)\n\t"
143 "lwz 2,4(%2)\n\t"
144 "mtctr 0\n\t"
145 "bctrl\n\t"
146 "ld 2,40(1)\n\t"
147 "addi 1,1,128\n\t"
148 : "+r"(r3), "+r"(r4)
149 : "b"((u64)opd)
150 : "r0", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "ctr", "lr", "xer",
151 "cr0", "cr1", "cr5", "cr6", "cr7", "memory");
152 return (s32)r3;
153}
154
155static void Reserve(u32 words)
156{
157 if (!ctx || ctx->current + words <= ctx->end) return;
158 Fifo_Callback(ctx, words);
159}
160
161/*
162==================
163The RSX path
164==================
165*/
166typedef struct { float x, y, z, w, r, g, b, a, u, v, s, t; } vert_t; // 48 bytes
167#define MAX_VERTS (24576 * 6 + 64)
168
169static int gpu_mode; // the RSX draws the UI
170static vert_t *vb;
171static u32 vb_off;
172static u32 *depth; // display-size depth buffer (unused, but the surface wants one)
173static u32 depth_off;
174// vertex programs: the UI's, and RSX Rally's HUD one (for the self-test)
175typedef struct { const char *name; const rsxVertexProgram *vpo; void *ucode; } vprog_t;
176enum { VP_UI, VP_RR, NUM_VP };
177static vprog_t vprogs[NUM_VP] = {
178 {"ui", (const rsxVertexProgram *)UI_VPO, NULL}, {"rrtex", (const rsxVertexProgram *)RRTEX_VPO, NULL},
179};
180static int cur_vp = VP_UI;
181// fragment programs (they run from RSX memory): the UI's, one without its swizzle, and the
182// self-test's cut-down ones
183typedef struct { const char *name; const rsxFragmentProgram *fpo; u32 off; } fprog_t;
184enum { FP_UI, FP_UI2, FP_COL, FP_T0, FP_T1, FP_RR, FP_TC0, FP_TC1, NUM_FP };
185static fprog_t fprogs[NUM_FP] = {
186 {"ui", (const rsxFragmentProgram *)UI_FPO, 0}, {"ui2", (const rsxFragmentProgram *)UI2_FPO, 0},
187 {"col", (const rsxFragmentProgram *)COL_FPO, 0}, {"t0", (const rsxFragmentProgram *)T0_FPO, 0},
188 {"t1", (const rsxFragmentProgram *)T1_FPO, 0}, {"rrtex", (const rsxFragmentProgram *)RRTEX_FPO, 0},
189 {"tc0", (const rsxFragmentProgram *)TC0_FPO, 0}, {"tc1", (const rsxFragmentProgram *)TC1_FPO, 0},
190};
191static int cur_fp = FP_UI; // the one drawing uses
192static int blend_on = 1;
193static int scissor_flip; // the RSX's scissor counts from the bottom (found by the self-test)
194static int good_frames;
195static const gquad_t *last_q; // the last frame's list, drawn again for each reflip
196static const gbatch_t *last_b;
197static int last_nq, last_nb;
198static u32 label_val = 1;
199#define LABEL_INDEX 253
200
201// where the quads go: a surface and the canvas size its corners stand for
202// (oy: canvas units the canvas is moved down by, for the letterbox's top bar)
203typedef struct { u32 off; int pitch, w, h, fmt; u32 depth_off; int depth_pitch; float cw, ch, oy; } target_t;
204
205// how textures are read: found by the self-test (smooth filtering and plain rows, if they work)
206static int tex_nearest, tex_swz, tex_min_w, tex_min_pitch = 64;
207
208int gpu_tex_alloc(gtex_t *t, int w, int h)
209{
210 int pitch = (w * 4 + 63) & ~63;
211 if (pitch < tex_min_pitch) pitch = tex_min_pitch;
212 void *p = rsxMemalign(128, (u32)pitch * h);
213 u32 off;
214 if (!p) return 0;
215 if (rsxAddressToOffset(p, &off) != 0) { rsxFree(p); return 0; }
216 t->px = p;
217 t->off = off;
218 t->w = w;
219 t->h = h;
220 t->pitch = pitch;
221 return 1;
222}
223
224void gpu_tex_free(gtex_t *t)
225{
226 if (t->px) rsxFree(t->px);
227 t->px = NULL;
228}
229
230void gpu_frame_begin(void)
231{
232 Wait_Flips(); // the last frame is on screen: the RSX is done with all it used
233}
234
235static u32 Remap(void)
236{
237 return (GCM_TEXTURE_REMAP_TYPE_REMAP << GCM_TEXTURE_REMAP_TYPE_B_SHIFT) | (GCM_TEXTURE_REMAP_TYPE_REMAP << GCM_TEXTURE_REMAP_TYPE_G_SHIFT) |
238 (GCM_TEXTURE_REMAP_TYPE_REMAP << GCM_TEXTURE_REMAP_TYPE_R_SHIFT) | (GCM_TEXTURE_REMAP_TYPE_REMAP << GCM_TEXTURE_REMAP_TYPE_A_SHIFT) |
239 (GCM_TEXTURE_REMAP_COLOR_B << GCM_TEXTURE_REMAP_COLOR_B_SHIFT) | (GCM_TEXTURE_REMAP_COLOR_G << GCM_TEXTURE_REMAP_COLOR_G_SHIFT) |
240 (GCM_TEXTURE_REMAP_COLOR_R << GCM_TEXTURE_REMAP_COLOR_R_SHIFT) | (GCM_TEXTURE_REMAP_COLOR_A << GCM_TEXTURE_REMAP_COLOR_A_SHIFT);
241}
242
243static void Bind(int unit, const gtex_t *t)
244{
245 gcmTexture tx;
246 Reserve(128);
247 memset(&tx, 0, sizeof(tx));
248 tx.format = GCM_TEXTURE_FORMAT_A8R8G8B8 | (tex_swz ? GCM_TEXTURE_FORMAT_SWZ : GCM_TEXTURE_FORMAT_LIN);
249 tx.mipmap = 1;
250 tx.dimension = GCM_TEXTURE_DIMS_2D;
251 tx.cubemap = GCM_FALSE;
252 tx.remap = Remap();
253 tx.width = t->w;
254 tx.height = t->h;
255 tx.depth = 1;
256 tx.location = GCM_LOCATION_RSX;
257 tx.pitch = tex_swz ? 0 : t->pitch;
258 tx.offset = t->off;
259 rsxLoadTexture(ctx, unit, &tx);
260 rsxTextureControl(ctx, unit, GCM_TRUE, 0 << 8, 12 << 8, GCM_TEXTURE_MAX_ANISO_1);
261 u8 f = tex_nearest ? GCM_TEXTURE_NEAREST : GCM_TEXTURE_LINEAR;
262 rsxTextureFilter(ctx, unit, 0, f, f, GCM_TEXTURE_CONVOLUTION_QUINCUNX);
263 rsxTextureWrapMode(ctx, unit, GCM_TEXTURE_CLAMP_TO_EDGE, GCM_TEXTURE_CLAMP_TO_EDGE, GCM_TEXTURE_CLAMP_TO_EDGE, 0, GCM_TEXTURE_ZFUNC_LESS, 0);
264}
265
266static void Scissor(const target_t *tg, int x, int y, int w, int h)
267{
268 // canvas units to the surface's pixels (rounded outwards)
269 float sx = tg->w / tg->cw, sy = tg->h / tg->ch;
270 int x0 = (int)(x * sx), y0 = (int)((y + tg->oy) * sy);
271 int x1 = (int)((x + w) * sx + 0.999f), y1 = (int)((y + h + tg->oy) * sy + 0.999f);
272 if (x0 < 0) x0 = 0;
273 if (y0 < 0) y0 = 0;
274 if (x1 > tg->w) x1 = tg->w;
275 if (y1 > tg->h) y1 = tg->h;
276 if (x1 < x0) x1 = x0;
277 if (y1 < y0) y1 = y0;
278 if (scissor_flip) { int t = tg->h - y1; y1 = tg->h - y0; y0 = t; }
279 Reserve(16);
280 rsxSetScissor(ctx, x0, y0, x1 - x0, y1 - y0);
281}
282
283static void Write_Vertices(const target_t *tg, const gquad_t *q, int nq)
284{
285 if (nq * 6 > MAX_VERTS) nq = MAX_VERTS / 6;
286 float kx = 2.0f / tg->cw, ky = 2.0f / tg->ch;
287 vert_t *v = vb;
288 for (int i = 0; i < nq; i++, q++) {
289 float X0 = q->x0 * kx - 1, X1 = q->x1 * kx - 1;
290 float Y0 = 1 - (q->y0 + tg->oy) * ky, Y1 = 1 - (q->y1 + tg->oy) * ky;
291 // corners: top left, top right, bottom left, bottom right; two triangles
292 const float px[4] = {X0, X1, X0, X1}, py[4] = {Y0, Y0, Y1, Y1};
293 const float pu[4] = {q->u0, q->u1, q->u0, q->u1}, pv[4] = {q->v0, q->v0, q->v1, q->v1};
294 const float ps[4] = {q->s0, q->s1, q->s0, q->s1}, pt[4] = {q->t0, q->t0, q->t1, q->t1};
295 static const int order[6] = {0, 1, 2, 1, 3, 2};
296 for (int k = 0; k < 6; k++, v++) {
297 int c = order[k];
298 uint32_t col = q->c[c];
299 v->x = px[c]; v->y = py[c]; v->z = 0; v->w = 1;
300 v->r = ((col >> 16) & 255) / 255.0f;
301 v->g = ((col >> 8) & 255) / 255.0f;
302 v->b = (col & 255) / 255.0f;
303 v->a = (col >> 24) / 255.0f;
304 v->u = pu[c]; v->v = pv[c];
305 v->s = ps[c]; v->t = pt[c];
306 }
307 }
308 __sync_synchronize();
309}
310
311// the surface, the state and the programs: everything a list needs before its first quad
312static void Begin_Target(const target_t *tg, uint32_t clear)
313{
314 gcmSurface sf;
315 Reserve(512);
316 memset(&sf, 0, sizeof(sf));
317 sf.colorFormat = tg->fmt;
318 sf.colorTarget = GCM_SURFACE_TARGET_0;
319 sf.colorLocation[0] = GCM_LOCATION_RSX;
320 sf.colorOffset[0] = tg->off;
321 sf.colorPitch[0] = tg->pitch;
322 for (int i = 1; i < 4; i++) { sf.colorLocation[i] = GCM_LOCATION_RSX; sf.colorOffset[i] = 0; sf.colorPitch[i] = 64; }
323 sf.depthFormat = GCM_SURFACE_ZETA_Z24S8;
324 sf.depthLocation = GCM_LOCATION_RSX;
325 sf.depthOffset = tg->depth_off;
326 sf.depthPitch = tg->depth_pitch;
327 sf.type = GCM_SURFACE_TYPE_LINEAR;
328 sf.antiAlias = GCM_SURFACE_CENTER_1;
329 sf.width = tg->w;
330 sf.height = tg->h;
331 sf.x = 0;
332 sf.y = 0;
333 rsxSetSurface(ctx, &sf);
334 rsxSetColorMask(ctx, GCM_COLOR_MASK_B | GCM_COLOR_MASK_G | GCM_COLOR_MASK_R | GCM_COLOR_MASK_A);
335 rsxSetColorMaskMrt(ctx, 0);
336 f32 scale[4] = {tg->w * 0.5f, tg->h * -0.5f, 0.5f, 0.0f};
337 f32 offset[4] = {tg->w * 0.5f, tg->h * 0.5f, 0.5f, 0.0f};
338 rsxSetViewport(ctx, 0, 0, tg->w, tg->h, 0.0f, 1.0f, scale, offset);
339 for (int i = 0; i < 8; i++) rsxSetViewportClip(ctx, i, tg->w, tg->h);
340 rsxSetZMinMaxControl(ctx, GCM_FALSE, GCM_TRUE, GCM_FALSE);
341 rsxSetScissor(ctx, 0, 0, tg->w, tg->h);
342 rsxSetClearColor(ctx, clear | 0xff000000);
343 rsxSetClearDepthStencil(ctx, 0xffffff00);
344 rsxClearSurface(ctx, GCM_CLEAR_R | GCM_CLEAR_G | GCM_CLEAR_B | GCM_CLEAR_A | GCM_CLEAR_Z | GCM_CLEAR_S);
345
346 Reserve(512);
347 rsxSetDepthTestEnable(ctx, GCM_FALSE);
348 rsxSetDepthWriteEnable(ctx, GCM_FALSE);
349 rsxSetCullFaceEnable(ctx, GCM_FALSE);
350 rsxSetAlphaTestEnable(ctx, GCM_FALSE);
351 rsxSetShadeModel(ctx, GCM_SHADE_MODEL_SMOOTH);
352 rsxSetUserClipPlaneControl(ctx, GCM_USER_CLIP_PLANE_DISABLE, GCM_USER_CLIP_PLANE_DISABLE, GCM_USER_CLIP_PLANE_DISABLE,
353 GCM_USER_CLIP_PLANE_DISABLE, GCM_USER_CLIP_PLANE_DISABLE, GCM_USER_CLIP_PLANE_DISABLE);
354 rsxSetBlendEnable(ctx, blend_on ? GCM_TRUE : GCM_FALSE);
355 rsxSetBlendFunc(ctx, GCM_SRC_ALPHA, GCM_ONE_MINUS_SRC_ALPHA, GCM_ONE, GCM_ONE_MINUS_SRC_ALPHA);
356 rsxSetBlendEquation(ctx, GCM_FUNC_ADD, GCM_FUNC_ADD);
357 rsxLoadVertexProgram(ctx, vprogs[cur_vp].vpo, vprogs[cur_vp].ucode);
358 rsxLoadFragmentProgramLocation(ctx, fprogs[cur_fp].fpo, fprogs[cur_fp].off, GCM_LOCATION_RSX);
359 rsxInvalidateTextureCache(ctx, GCM_INVALIDATE_TEXTURE);
360
361 // every input off, then the four the program reads, all inside vb
362 Reserve(256);
363 for (int i = 0; i < 16; i++)
364 rsxBindVertexArrayAttrib(ctx, i, 0, 0, 0, 0, GCM_VERTEX_DATA_TYPE_F32, GCM_LOCATION_RSX);
365 rsxBindVertexArrayAttrib(ctx, GCM_VERTEX_ATTRIB_POS, 0, vb_off, sizeof(vert_t), 4, GCM_VERTEX_DATA_TYPE_F32, GCM_LOCATION_RSX);
366 rsxBindVertexArrayAttrib(ctx, GCM_VERTEX_ATTRIB_COLOR0, 0, vb_off + 16, sizeof(vert_t), 4, GCM_VERTEX_DATA_TYPE_F32, GCM_LOCATION_RSX);
367 rsxBindVertexArrayAttrib(ctx, GCM_VERTEX_ATTRIB_TEX0, 0, vb_off + 32, sizeof(vert_t), 2, GCM_VERTEX_DATA_TYPE_F32, GCM_LOCATION_RSX);
368 rsxBindVertexArrayAttrib(ctx, GCM_VERTEX_ATTRIB_TEX1, 0, vb_off + 40, sizeof(vert_t), 2, GCM_VERTEX_DATA_TYPE_F32, GCM_LOCATION_RSX);
369 rsxInvalidateVertexCache(ctx);
370}
371
372// the batches (vertices already written)
373static void Draw_Batches(const target_t *tg, const gbatch_t *b, int nb, int nq)
374{
375 const gtex_t *bound[2] = {NULL, NULL};
376 int sc[4] = {-1, -1, -1, -1};
377 for (int k = 0; k < nb; k++) {
378 const gbatch_t *bt = &b[k];
379 int first = bt->first, count = bt->count;
380 if (first >= nq || count <= 0) continue;
381 if (first + count > nq) count = nq - first;
382 if ((first + count) * 6 > MAX_VERTS) continue;
383 if (bt->tex0 != bound[0]) { Bind(0, bt->tex0); bound[0] = bt->tex0; }
384 if (bt->tex1 != bound[1]) { Bind(1, bt->tex1); bound[1] = bt->tex1; }
385 if (sc[0] != bt->sx || sc[1] != bt->sy || sc[2] != bt->sw || sc[3] != bt->sh) {
386 Scissor(tg, bt->sx, bt->sy, bt->sw, bt->sh);
387 sc[0] = bt->sx; sc[1] = bt->sy; sc[2] = bt->sw; sc[3] = bt->sh;
388 }
389 Reserve(32 + count * 6 / 256 * 2);
390 rsxDrawVertexArray(ctx, GCM_TYPE_TRIANGLES, first * 6, count * 6);
391 }
392}
393
394static target_t Screen_Target(void)
395{
396 target_t tg = {fb_offset[cur], disp_pitch, disp_w, disp_h, GCM_SURFACE_X8R8G8B8, depth_off, disp_w * 4, SCREEN_W, SCREEN_H, 0};
397 if (bar_h) { // the surface stands for a taller canvas, the 1280x720 in its middle
398 tg.ch = (float)SCREEN_H * disp_h / (disp_h - 2 * bar_h);
399 tg.oy = bar_h * tg.ch / disp_h;
400 }
401 return tg;
402}
403
404// frames and reflips both count: the UI only draws when something changes. 120 (2 s, after the self-test
405// passed): a hang later than that is the app's, not the RSX's (1.2.1's Update freeze, at 4 s, was blamed on it)
406static void Frame_Good(void)
407{
408 if (++good_frames == 120) {
409 sysLv2FsUnlink(GPU_TRYING);
410 Glog("120 frames: the RSX stays on");
411 }
412}
413
414void gpu_frame_submit(const gquad_t *q, int nq, const gbatch_t *b, int nb)
415{
416 if (!gpu_mode || !ready) return;
417 target_t tg = Screen_Target();
418 Write_Vertices(&tg, q, nq);
419 Begin_Target(&tg, 0x000000);
420 Draw_Batches(&tg, b, nb, nq);
421 Flip();
422 last_q = q; last_nq = nq; last_b = b; last_nb = nb;
423 Frame_Good();
424}
425
426/*
427==================
428Self-test: a small surface the CPU reads back
429==================
430*/
431#define TW 64
432#define TH 64
433static u32 *test_px, *test_depth;
434static u32 test_off, test_depth_off;
435
436static int Finish(int max_us)
437{
438 Reserve(16);
439 rsxSetWriteBackendLabel(ctx, LABEL_INDEX, label_val);
440 __sync_synchronize();
441 rsxFlushBuffer(ctx);
442 for (int waited = 0; *(vu32 *)gcmGetLabelAddress(LABEL_INDEX) != label_val; waited += 50) {
443 if (waited > max_us) { label_val++; return 0; }
444 usleep(50);
445 }
446 label_val++;
447 __sync_synchronize();
448 return 1;
449}
450
451static u32 Test_Pixel(int x, int y) { return ((volatile u32 *)test_px)[y * TW + x] & 0xffffff; }
452static u32 Test_Raw(int x, int y) { return ((volatile u32 *)test_px)[y * TW + x]; }
453
454static int Near(u32 a, u32 b)
455{
456 for (int sh = 0; sh <= 16; sh += 8)
457 if (abs((int)((a >> sh) & 255) - (int)((b >> sh) & 255)) > 12) return 0;
458 return 1;
459}
460
461// draws a list on the test surface, waits for it
462static int Test_Draw(const gquad_t *q, int nq, const gbatch_t *b, int nb, uint32_t clear)
463{
464 target_t tg = {test_off, TW * 4, TW, TH, GCM_SURFACE_A8R8G8B8, test_depth_off, TW * 4, TW, TH};
465 Write_Vertices(&tg, q, nq);
466 Begin_Target(&tg, clear);
467 Draw_Batches(&tg, b, nb, nq);
468 return Finish(300000);
469}
470
471static gquad_t Test_Quad(float x0, float y0, float x1, float y1, uint32_t c)
472{
473 gquad_t q;
474 memset(&q, 0, sizeof(q));
475 q.x0 = x0; q.y0 = y0; q.x1 = x1; q.y1 = y1;
476 q.u0 = 0; q.v0 = 0; q.u1 = 1; q.v1 = 1;
477 q.s0 = 0; q.t0 = 0; q.s1 = 1; q.t1 = 1;
478 q.c[0] = q.c[1] = q.c[2] = q.c[3] = c;
479 return q;
480}
481
482static void Fill_Test_Textures(gtex_t *white, gtex_t *pat, gtex_t *half)
483{
484 for (int j = 0; j < 4; j++)
485 for (int i = 0; i < 4; i++) {
486 ((u32 *)white->px)[j * (white->pitch / 4) + i] = 0xffffffff;
487 // quadrants: red, green / blue, white
488 ((u32 *)pat->px)[j * (pat->pitch / 4) + i] = j < 2 ? (i < 2 ? 0xffff0000 : 0xff00ff00) : (i < 2 ? 0xff0000ff : 0xffffffff);
489 // clear on the left, solid on the right
490 ((u32 *)half->px)[j * (half->pitch / 4) + i] = i < 2 ? 0x00ffffff : 0xffffffff;
491 }
492 __sync_synchronize();
493}
494
495static int Self_Test(char *why, int len)
496{
497 gtex_t white, pat, half;
498 if (!gpu_tex_alloc(&white, 4, 4) || !gpu_tex_alloc(&pat, 4, 4) || !gpu_tex_alloc(&half, 4, 4)) {
499 snprintf(why, len, "no RSX memory for the test textures");
500 return 0;
501 }
502 Fill_Test_Textures(&white, &pat, &half);
503 gquad_t q[2];
504 gbatch_t b;
505
506 Glog("test 1: label");
507 if (!Finish(300000)) { snprintf(why, len, "test 1 (label) timed out"); return 0; }
508
509 // a red quad through each program in turn (the cut-down ones say which part fails)
510 static const struct { int fp, blend; const char *what; } steps[] = {
511 {FP_COL, 0, "colour only, no blending"}, {FP_COL, 1, "colour only, blended"},
512 {FP_T0, 1, "first texture"}, {FP_T1, 1, "second texture"},
513 {FP_UI, 1, "the UI's program"}, {FP_UI2, 1, "the UI's program without the swizzle"},
514 };
515 int works[6] = {0};
516 for (int k = 0; k < 6; k++) {
517 Glog("test 2.%d: a red quad, %s", k + 1, steps[k].what);
518 cur_fp = steps[k].fp;
519 blend_on = steps[k].blend;
520 q[0] = Test_Quad(0, 0, TW, TH, 0xffff0000);
521 b = (gbatch_t){0, 1, &white, &white, 0, 0, TW, TH};
522 if (!Test_Draw(q, 1, &b, 1, 0x123456)) { snprintf(why, len, "test 2.%d (%s) timed out", k + 1, steps[k].what); return 0; }
523 u32 px = Test_Pixel(32, 32);
524 works[k] = Near(px, 0xff0000);
525 Glog("test 2.%d: read %06x (%s)", k + 1, (unsigned)px, works[k] ? "good" : "wanted ff0000");
526 }
527 blend_on = 1;
528 if (!works[2]) {
529 // what the texture programs see: the coordinates as colour, RSX Rally's own HUD programs,
530 // and ours unblended (black: the texture reads as nothing; red: it reads)
531 gtex_t big;
532 if (gpu_tex_alloc(&big, 64, 64)) {
533 for (int j = 0; j < 64; j++)
534 for (int i = 0; i < 64; i++) ((u32 *)big.px)[j * (big.pitch / 4) + i] = 0xffffffff;
535 __sync_synchronize();
536 static const struct { int vp, fp, nearest; const char *what; } d[] = {
537 {VP_UI, FP_TC0, 0, "first texture coordinate as colour (want about 7f7f00, alpha ff)"},
538 {VP_UI, FP_TC1, 0, "second texture coordinate as colour"},
539 {VP_RR, FP_RR, 1, "RSX Rally's HUD programs, 64x64 white, nearest (want ffffff)"},
540 {VP_UI, FP_RR, 1, "our vertex program, RSX Rally's fragment program"},
541 {VP_RR, FP_RR, 0, "RSX Rally's HUD programs, smooth"},
542 {VP_UI, FP_T0, 0, "ours, unblended (black: texture read as nothing)"},
543 };
544 blend_on = 0;
545 for (int k = 0; k < 6; k++) {
546 cur_vp = d[k].vp;
547 cur_fp = d[k].fp;
548 tex_nearest = d[k].nearest;
549 q[0] = Test_Quad(0, 0, TW, TH, 0xffff0000);
550 b = (gbatch_t){0, 1, &big, &big, 0, 0, TW, TH};
551 int ok = Test_Draw(q, 1, &b, 1, 0x123456);
552 Glog("test 4.%d: %s: read %08x%s", k + 1, d[k].what, (unsigned)Test_Raw(32, 32), ok ? "" : " (timed out)");
553 }
554 blend_on = 1;
555 cur_vp = VP_UI;
556 tex_nearest = 0;
557 gpu_tex_free(&big);
558 }
559 // textures read nothing: try the ways RSX Rally's work (nearest filtering, bigger,
560 // swizzled) and keep the first that draws
561 static const struct { int size, nearest, swz, pitch; const char *what; } ways[] = {
562 {4, 1, 0, 64, "4x4, plain rows, nearest"}, {4, 0, 0, 256, "4x4, rows 256 bytes apart, smooth"},
563 {4, 0, 0, 1024, "4x4, rows 1024 bytes apart, smooth"}, {64, 0, 0, 64, "64x64, plain rows, smooth"},
564 {64, 1, 0, 64, "64x64, plain rows, nearest"}, {256, 1, 0, 64, "256x256, plain rows, nearest"},
565 {4, 0, 1, 64, "4x4, swizzled, smooth"}, {64, 0, 1, 64, "64x64, swizzled, smooth"},
566 };
567 int found = -1;
568 for (int k = 0; k < 8; k++) {
569 gtex_t t;
570 tex_min_pitch = ways[k].pitch;
571 if (!gpu_tex_alloc(&t, ways[k].size, ways[k].size)) { Glog("test 3.%d: no memory", k + 1); continue; }
572 for (int j = 0; j < t.h; j++)
573 for (int i = 0; i < t.w; i++) ((u32 *)t.px)[j * (t.pitch / 4) + i] = 0xffffffff; // (all white: the layout doesn't matter)
574 __sync_synchronize();
575 tex_nearest = ways[k].nearest;
576 tex_swz = ways[k].swz;
577 cur_fp = FP_T0;
578 q[0] = Test_Quad(0, 0, TW, TH, 0xffff0000);
579 b = (gbatch_t){0, 1, &t, &t, 0, 0, TW, TH};
580 int ok = Test_Draw(q, 1, &b, 1, 0x123456);
581 u32 px = Test_Pixel(32, 32);
582 Glog("test 3.%d: texture %s: read %06x%s", k + 1, ways[k].what, (unsigned)px, ok ? "" : " (timed out)");
583 gpu_tex_free(&t);
584 if (ok && Near(px, 0xff0000)) { found = k; break; }
585 }
586 tex_nearest = tex_swz = 0;
587 tex_min_pitch = 64;
588 if (found < 0) { snprintf(why, len, "no way of reading textures worked"); return 0; }
589 if (ways[found].swz) { snprintf(why, len, "textures only work swizzled (%s): not done yet", ways[found].what); return 0; }
590 tex_nearest = ways[found].nearest;
591 tex_min_w = ways[found].size;
592 tex_min_pitch = ways[found].pitch;
593 if (tex_min_w > 4 && ways[found].pitch == 64) {
594 snprintf(why, len, "textures only work at %dx%d and up: not done yet", tex_min_w, tex_min_w);
595 return 0;
596 }
597 Glog("textures: %s", ways[found].what);
598 // the test textures again, made the way that works
599 gpu_tex_free(&white); gpu_tex_free(&pat); gpu_tex_free(&half);
600 if (!gpu_tex_alloc(&white, 4, 4) || !gpu_tex_alloc(&pat, 4, 4) || !gpu_tex_alloc(&half, 4, 4)) { snprintf(why, len, "no memory"); return 0; }
601 Fill_Test_Textures(&white, &pat, &half);
602 // the programs again, the way that works
603 for (int k = 2; k < 6; k++) {
604 cur_fp = steps[k].fp;
605 gtex_t t;
606 if (!gpu_tex_alloc(&t, 4, 4)) break;
607 for (int j = 0; j < t.h; j++)
608 for (int i = 0; i < t.w; i++) ((u32 *)t.px)[j * (t.pitch / 4) + i] = 0xffffffff;
609 __sync_synchronize();
610 q[0] = Test_Quad(0, 0, TW, TH, 0xffff0000);
611 b = (gbatch_t){0, 1, &t, &t, 0, 0, TW, TH};
612 Test_Draw(q, 1, &b, 1, 0x123456);
613 u32 px = Test_Pixel(32, 32);
614 works[k] = Near(px, 0xff0000);
615 Glog("test 2.%d again: read %06x (%s)", k + 1, (unsigned)px, works[k] ? "good" : "wanted ff0000");
616 gpu_tex_free(&t);
617 }
618 }
619 if (works[4]) cur_fp = FP_UI;
620 else if (works[5]) cur_fp = FP_UI2;
621 else { snprintf(why, len, "no program drew the red quad (see the steps above)"); return 0; }
622 Glog("drawing with the \"%s\" program", fprogs[cur_fp].name);
623
624 Glog("test 3: texture");
625 q[0] = Test_Quad(0, 0, TW, TH, 0xffffffff);
626 b = (gbatch_t){0, 1, &pat, &white, 0, 0, TW, TH};
627 if (!Test_Draw(q, 1, &b, 1, 0)) { snprintf(why, len, "test 3 (texture) timed out"); return 0; }
628 u32 p0 = Test_Pixel(16, 16), p1 = Test_Pixel(48, 16), p2 = Test_Pixel(16, 48), p3 = Test_Pixel(48, 48);
629 if (!Near(p0, 0xff0000) || !Near(p1, 0x00ff00) || !Near(p2, 0x0000ff) || !Near(p3, 0xffffff)) {
630 snprintf(why, len, "test 3 read %06x %06x %06x %06x", (unsigned)p0, (unsigned)p1, (unsigned)p2, (unsigned)p3);
631 return 0;
632 }
633
634 Glog("test 4: second texture's alpha, and blending");
635 q[0] = Test_Quad(0, 0, TW, TH, 0xffffffff);
636 q[1] = Test_Quad(0, 0, TW, TH, 0x80ff0000);
637 gbatch_t bb[2] = {{0, 1, &white, &half, 0, 0, TW, TH}, {1, 1, &white, &white, 0, 0, TW, TH}};
638 if (!Test_Draw(q, 2, bb, 2, 0x00ff00)) { snprintf(why, len, "test 4 timed out"); return 0; }
639 // left: green under half red; right: white under half red
640 p0 = Test_Pixel(8, 32); p1 = Test_Pixel(56, 32);
641 if (!Near(p0, 0x807f00) || !Near(p1, 0xff7f7f)) { snprintf(why, len, "test 4 read %06x %06x", (unsigned)p0, (unsigned)p1); return 0; }
642
643 Glog("test 5: scissor");
644 q[0] = Test_Quad(0, 0, TW, TH, 0xffff0000);
645 b = (gbatch_t){0, 1, &white, &white, 0, 0, TW, TH / 2};
646 scissor_flip = 0;
647 if (!Test_Draw(q, 1, &b, 1, 0)) { snprintf(why, len, "test 5 timed out"); return 0; }
648 p0 = Test_Pixel(32, 8); p1 = Test_Pixel(32, 56);
649 if (Near(p0, 0x000000) && Near(p1, 0xff0000)) scissor_flip = 1; // it counts from the bottom
650 else if (!(Near(p0, 0xff0000) && Near(p1, 0x000000))) { snprintf(why, len, "test 5 read %06x %06x", (unsigned)p0, (unsigned)p1); return 0; }
651 Glog("scissor from the %s", scissor_flip ? "bottom" : "top");
652
653 gpu_tex_free(&white);
654 gpu_tex_free(&pat);
655 gpu_tex_free(&half);
656 return 1;
657}
658
659static int Gpu_Init(void)
660{
661 char why[160] = "";
662 Sync_Write(GPU_LOG, "", 1);
663 Glog("init: display %dx%d", disp_w, disp_h);
664 vb = rsxMemalign(128, MAX_VERTS * sizeof(vert_t));
665 depth = rsxMemalign(128, disp_w * 4 * disp_h);
666 test_px = rsxMemalign(128, TW * TH * 4);
667 test_depth = rsxMemalign(128, TW * TH * 4);
668 if (!vb || !depth || !test_px || !test_depth) { Glog("no RSX memory"); return 0; }
669 if (rsxAddressToOffset(vb, &vb_off) || rsxAddressToOffset(depth, &depth_off) ||
670 rsxAddressToOffset(test_px, &test_off) || rsxAddressToOffset(test_depth, &test_depth_off)) {
671 Glog("no RSX offsets");
672 return 0;
673 }
674 for (int i = 0; i < NUM_VP; i++) {
675 u32 vpsize = 0;
676 if (vprogs[i].vpo->magic != (('V' << 8) | 'P')) { Glog("bad vertex program %s", vprogs[i].name); return 0; }
677 rsxVertexProgramGetUCode(vprogs[i].vpo, &vprogs[i].ucode, &vpsize);
678 if (!vprogs[i].ucode || !vpsize) { Glog("empty vertex program %s", vprogs[i].name); return 0; }
679 }
680 for (int i = 0; i < NUM_FP; i++) {
681 void *ucode = NULL;
682 u32 size = 0;
683 if (fprogs[i].fpo->magic != (('F' << 8) | 'P')) { Glog("bad fragment program %s", fprogs[i].name); return 0; }
684 rsxFragmentProgramGetUCode(fprogs[i].fpo, &ucode, &size);
685 void *mem = size ? rsxMemalign(64, size) : NULL; // a fragment program runs from RSX memory
686 if (!ucode || !mem || rsxAddressToOffset(mem, &fprogs[i].off)) { Glog("no RSX memory for program %s", fprogs[i].name); return 0; }
687 memcpy(mem, ucode, size);
688 }
689 __sync_synchronize();
690 if (!Self_Test(why, sizeof(why))) { Glog("self-test failed: %s", why); return 0; }
691 Glog("self-test passed");
692 return 1;
693}
694
695/*
696==================
697Start-up
698==================
699*/
700// the display at a mode, and its two framebuffers (any old ones let go of first)
701static int Setup_Display(s32 vid_res, u8 vid_aspect)
702{
703 videoResolution res;
704 videoConfiguration vconfig;
705 videoState state;
706 if (videoGetResolution(vid_res, &res) != 0) {
707 plat_log("video: videoGetResolution failed");
708 return 0;
709 }
710 for (int i = 0; i < FB_COUNT; i++)
711 if (fb[i]) { rsxFree(fb[i]); fb[i] = NULL; }
712 disp_w = res.width;
713 disp_h = res.height;
714 disp_pitch = disp_w * 4;
715 // a 4:3 picture: the 16:9 canvas across its full width is 3/4 of its height
716 bar_h = vid_aspect == VIDEO_ASPECT_4_3 ? disp_h / 8 : 0;
717
718 memset(&vconfig, 0, sizeof(vconfig));
719 vconfig.resolution = vid_res;
720 vconfig.format = VIDEO_BUFFER_FORMAT_XRGB;
721 vconfig.pitch = disp_pitch;
722 vconfig.aspect = vid_aspect;
723 if (videoConfigure(0, &vconfig, NULL, 0) != 0) {
724 plat_log("video: videoConfigure failed");
725 return 0;
726 }
727 for (int waited = 0; waited < 300; waited++) {
728 usleep(10000);
729 if (videoGetState(0, 0, &state) != 0 || state.state != 3)
730 break;
731 }
732 gcmSetFlipMode(GCM_FLIP_VSYNC);
733 for (int i = 0; i < FB_COUNT; i++) {
734 fb[i] = (u32 *)rsxMemalign(1024 * 1024, disp_pitch * disp_h);
735 if (!fb[i]) {
736 plat_log("video: no RSX memory for framebuffer %d", i);
737 return 0;
738 }
739 memset(fb[i], 0, disp_pitch * disp_h);
740 rsxAddressToOffset(fb[i], &fb_offset[i]);
741 gcmSetDisplayBuffer(i, fb_offset[i], disp_pitch, disp_w, disp_h);
742 }
743 cur = 0;
744 return 1;
745}
746
747int ps3_video_init(void)
748{
749 s32 vid_res = VIDEO_RESOLUTION_720;
750 u8 vid_aspect = VIDEO_ASPECT_16_9;
751 videoState state;
752
753 // L1 held while it starts: the CPU draws this time. R1: the RSX is tried again, even
754 // after a freeze (the pads take a moment to answer)
755 uint32_t held = 0;
756 for (int i = 0; i < 15 && !(held & (BTN_L1 | BTN_R1)); i++) {
757 held = plat_input_held();
758 usleep(20000);
759 }
760 if (held & BTN_R1) {
761 plat_log("video: R1 held: the RSX is tried again");
762 plat_renderer_set_cpu(0);
763 sysLv2FsUnlink(GPU_TRYING);
764 }
765
766 // the RSX, unless it froze last time (the marker's still there) or once before
767 int try_gpu = !File_Exists(GPU_FAILED) && !File_Exists(USE_CPU);
768 if (try_gpu && File_Exists(GPU_TRYING)) {
769 sysLv2FsRename(GPU_TRYING, GPU_FAILED);
770 plat_log("video: the RSX didn't make it through the last start: the CPU draws (hold R1 while it starts to try again)");
771 try_gpu = 0;
772 } else if (!try_gpu) {
773 plat_log("video: the CPU draws (%s is there; hold R1 while it starts to try the RSX again)", GPU_FAILED);
774 }
775 if (try_gpu && (held & BTN_L1)) {
776 plat_log("video: L1 held: the CPU draws this time");
777 try_gpu = 0;
778 }
779
780 io_buffer = memalign(1024 * 1024, IO_SIZE);
781 if (!io_buffer || rsxInit(&ctx, CB_SIZE, IO_SIZE, io_buffer) != 0 || !ctx) {
782 plat_log("video: rsxInit failed");
783 return 0;
784 }
785 videoGetState(0, 0, &state);
786 int have_720 = videoGetResolutionAvailability(VIDEO_PRIMARY, VIDEO_RESOLUTION_720, VIDEO_ASPECT_16_9, 0);
787 if (try_gpu) {
788 // the RSX draws at whatever the PS3 is set to (1080p if it is)
789 vid_res = state.displayMode.resolution;
790 vid_aspect = state.displayMode.aspect;
791 } else if (!have_720) {
792 vid_res = state.displayMode.resolution;
793 vid_aspect = state.displayMode.aspect;
794 plat_log("video: 720p not available, using the TV's mode (%d)", (int)vid_res);
795 }
796 if (!Setup_Display(vid_res, vid_aspect))
797 return 0;
798 gcmSetFlipHandler(FlipHandler);
799 flips_done = flips_queued = 0;
800
801 if (try_gpu) {
802 Sync_Write(GPU_TRYING, "trying the RSX\n", 1);
803 if (Gpu_Init()) {
804 gpu_mode = 1;
805 gfx_gpu_enable((disp_h - 2 * bar_h) / (float)SCREEN_H);
806 if (!gfx_gpu()) { Glog("no RSX memory for the atlas"); gpu_mode = 0; }
807 }
808 if (!gpu_mode) {
809 sysLv2FsUnlink(GPU_TRYING); // (failed cleanly: no freeze, try again next time)
810 // the CPU's canvas is 1280x720: back to 720p if the TV takes it (copying it out
811 // stretched to 1080p took 33 ms a frame)
812 if (have_720 && (disp_w != SCREEN_W || disp_h != SCREEN_H)) {
813 Finish(300000);
814 if (!Setup_Display(VIDEO_RESOLUTION_720, VIDEO_ASPECT_16_9))
815 return 0;
816 }
817 }
818 }
819 if (!gpu_mode) {
820 if (disp_w != SCREEN_W || disp_h != SCREEN_H) {
821 xmap = malloc(disp_w * sizeof(int));
822 for (int x = 0; x < disp_w; x++)
823 xmap[x] = x * SCREEN_W / disp_w;
824 }
825 // the CPU copies each finished frame into the framebuffer (an RSX copy
826 // from IO-mapped memory was tried in 0.3.1: it hung the RSX at the first
827 // frame and the screen stayed black)
828 cpu_canvas = memalign(128, CANVAS_BYTES);
829 if (!cpu_canvas) {
830 plat_log("video: no memory for the canvas");
831 return 0;
832 }
833 }
834 ready = 1;
835 plat_log("video: %dx%d%s, drawn by the %s", disp_w, disp_h, bar_h ? " (4:3, letterboxed)" : "", gpu_mode ? "RSX" : "CPU");
836 return 1;
837}
838
839int ps3_video_gpu(void) { return gpu_mode; }
840
841int plat_renderer_gpu_now(void) { return gpu_mode; }
842int plat_renderer_cpu(void) { return File_Exists(USE_CPU) || File_Exists(GPU_FAILED); }
843void plat_renderer_set_cpu(int cpu)
844{
845 if (cpu) {
846 s32 fd = -1;
847 if (sysLv2FsOpen(USE_CPU, SYS_O_WRONLY | SYS_O_CREAT | SYS_O_TRUNC, &fd, 0644, NULL, 0) == 0 && fd >= 0) sysLv2FsClose(fd);
848 } else {
849 // the chip again: and a freeze last time is forgiven (it's tried, and checked, as at first)
850 sysLv2FsUnlink(USE_CPU);
851 sysLv2FsUnlink(GPU_FAILED);
852 }
853}
854
855uint32_t *plat_canvas_next(void)
856{
857 return gpu_mode ? NULL : cpu_canvas;
858}
859
860void ps3_video_present(const uint32_t *src)
861{
862 if (!ready || gpu_mode || !src)
863 return; // (the RSX path flipped when the list was handed over)
864 // the buffer we're about to fill must not be on its way to the screen
865 Wait_Flips();
866
867 u32 *dst = fb[cur];
868 if (!xmap) {
869 memcpy(dst, src, SCREEN_W * SCREEN_H * 4);
870 } else {
871 int ch = disp_h - 2 * bar_h; // the rows the canvas fills (all, unless letterboxed)
872 for (int y = 0; y < disp_h; y++) {
873 u32 *d = dst + y * (disp_pitch / 4);
874 if (y < bar_h || y >= bar_h + ch) {
875 memset(d, 0, disp_w * 4);
876 continue;
877 }
878 const uint32_t *row = src + ((y - bar_h) * SCREEN_H / ch) * SCREEN_W;
879 for (int x = 0; x < disp_w; x++)
880 d[x] = row[xmap[x]];
881 }
882 }
883 Flip();
884}
885
886// shows the last frame again: while the XMB is up the system draws it on our
887// flips, so they must keep coming (stopping them froze the screen with the
888// XMB never appearing). Each one is a fresh copy of the last frame into the
889// other buffer: re-flipping the same buffer let the overlay draw over what
890// it had already drawn there, and it flickered. On the RSX path the last
891// list is drawn again (nothing it uses has changed: textures are only let go
892// of when the next frame starts).
893void ps3_video_reflip(void)
894{
895 if (!ready || !flips_queued)
896 return;
897 if (gpu_mode) {
898 if (!last_q) return;
899 Wait_Flips();
900 target_t tg = Screen_Target();
901 Begin_Target(&tg, 0x000000);
902 Draw_Batches(&tg, last_b, last_nb, last_nq);
903 Flip();
904 Frame_Good();
905 return;
906 }
907 if (cpu_canvas) ps3_video_present(cpu_canvas);
908}
909
910void ps3_video_shutdown(void)
911{
912 if (!ready)
913 return;
914 ready = 0;
915 if (gpu_mode)
916 sysLv2FsUnlink(GPU_TRYING); // it got to a clean quit: no freeze
917 gcmSetFlipHandler(NULL);
918 for (int waited = 0; flips_done != flips_queued && waited < 2000; waited++)
919 usleep(100);
920}