| 1 | /* |
| 2 | video_ps3.c: the screen. |
| 3 |
|
| 4 | The RSX draws the UI: gfx.c hands over a list of quads (gpu.h) and they're |
| 5 | drawn 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 |
| 8 | fails, or the last start froze during it - the CPU draws the canvas at |
| 9 | 1280x720 and it's copied into the display buffer, as before. |
| 10 |
|
| 11 | The rules (NZ:P PS3's ps3_video.c, Doom64-PS3, TyrQuakeCell and Quake2PS3 |
| 12 | before 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 |
|
| 58 | static gcmContextData *ctx; |
| 59 | static void *io_buffer; |
| 60 | static u32 *fb[FB_COUNT]; |
| 61 | static u32 fb_offset[FB_COUNT]; |
| 62 | static int disp_w, disp_h, disp_pitch; |
| 63 | static int bar_h; // a 4:3 TV: the 16:9 canvas letterboxed, this many black rows above and below |
| 64 | static int cur; |
| 65 | static volatile u32 flips_done; |
| 66 | static u32 flips_queued; |
| 67 | static int ready; |
| 68 | static int *xmap; // canvas column for each screen column (when not 1280x720) |
| 69 |
|
| 70 | #define CANVAS_BYTES (SCREEN_W * SCREEN_H * 4) |
| 71 | static uint32_t *cpu_canvas; // the CPU path draws here; copied to the framebuffer |
| 72 |
|
| 73 | static void FlipHandler(const u32 head) |
| 74 | { |
| 75 | (void)head; |
| 76 | flips_done++; |
| 77 | } |
| 78 |
|
| 79 | static void Wait_Flips(void) |
| 80 | { |
| 81 | for (int waited = 0; flips_done != flips_queued && waited < 2000; waited++) |
| 82 | usleep(100); |
| 83 | } |
| 84 |
|
| 85 | static void Flip(void) |
| 86 | { |
| 87 | gcmSetFlip(ctx, cur); |
| 88 | rsxFlushBuffer(ctx); |
| 89 | flips_queued++; |
| 90 | cur ^= 1; |
| 91 | } |
| 92 |
|
| 93 | /* |
| 94 | ================== |
| 95 | The log for the RSX path: each line on disk before the next step |
| 96 | ================== |
| 97 | */ |
| 98 | static 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 |
|
| 109 | static 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 |
|
| 121 | static 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 | ================== |
| 131 | Command buffer room (see the top) |
| 132 | ================== |
| 133 | */ |
| 134 | static 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 |
|
| 155 | static void Reserve(u32 words) |
| 156 | { |
| 157 | if (!ctx || ctx->current + words <= ctx->end) return; |
| 158 | Fifo_Callback(ctx, words); |
| 159 | } |
| 160 |
|
| 161 | /* |
| 162 | ================== |
| 163 | The RSX path |
| 164 | ================== |
| 165 | */ |
| 166 | typedef 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 |
|
| 169 | static int gpu_mode; // the RSX draws the UI |
| 170 | static vert_t *vb; |
| 171 | static u32 vb_off; |
| 172 | static u32 *depth; // display-size depth buffer (unused, but the surface wants one) |
| 173 | static u32 depth_off; |
| 174 | // vertex programs: the UI's, and RSX Rally's HUD one (for the self-test) |
| 175 | typedef struct { const char *name; const rsxVertexProgram *vpo; void *ucode; } vprog_t; |
| 176 | enum { VP_UI, VP_RR, NUM_VP }; |
| 177 | static vprog_t vprogs[NUM_VP] = { |
| 178 | {"ui", (const rsxVertexProgram *)UI_VPO, NULL}, {"rrtex", (const rsxVertexProgram *)RRTEX_VPO, NULL}, |
| 179 | }; |
| 180 | static 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 |
| 183 | typedef struct { const char *name; const rsxFragmentProgram *fpo; u32 off; } fprog_t; |
| 184 | enum { FP_UI, FP_UI2, FP_COL, FP_T0, FP_T1, FP_RR, FP_TC0, FP_TC1, NUM_FP }; |
| 185 | static 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 | }; |
| 191 | static int cur_fp = FP_UI; // the one drawing uses |
| 192 | static int blend_on = 1; |
| 193 | static int scissor_flip; // the RSX's scissor counts from the bottom (found by the self-test) |
| 194 | static int good_frames; |
| 195 | static const gquad_t *last_q; // the last frame's list, drawn again for each reflip |
| 196 | static const gbatch_t *last_b; |
| 197 | static int last_nq, last_nb; |
| 198 | static 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) |
| 203 | typedef 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) |
| 206 | static int tex_nearest, tex_swz, tex_min_w, tex_min_pitch = 64; |
| 207 |
|
| 208 | int 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 |
|
| 224 | void gpu_tex_free(gtex_t *t) |
| 225 | { |
| 226 | if (t->px) rsxFree(t->px); |
| 227 | t->px = NULL; |
| 228 | } |
| 229 |
|
| 230 | void gpu_frame_begin(void) |
| 231 | { |
| 232 | Wait_Flips(); // the last frame is on screen: the RSX is done with all it used |
| 233 | } |
| 234 |
|
| 235 | static 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 |
|
| 243 | static 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 |
|
| 266 | static 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 |
|
| 283 | static 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 |
| 312 | static 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) |
| 373 | static 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 |
|
| 394 | static 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) |
| 406 | static 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 |
|
| 414 | void 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 | ================== |
| 428 | Self-test: a small surface the CPU reads back |
| 429 | ================== |
| 430 | */ |
| 431 | #define TW 64 |
| 432 | #define TH 64 |
| 433 | static u32 *test_px, *test_depth; |
| 434 | static u32 test_off, test_depth_off; |
| 435 |
|
| 436 | static 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 |
|
| 451 | static u32 Test_Pixel(int x, int y) { return ((volatile u32 *)test_px)[y * TW + x] & 0xffffff; } |
| 452 | static u32 Test_Raw(int x, int y) { return ((volatile u32 *)test_px)[y * TW + x]; } |
| 453 |
|
| 454 | static 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 |
| 462 | static 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 |
|
| 471 | static 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 |
|
| 482 | static 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 |
|
| 495 | static 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 |
|
| 659 | static 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 | ================== |
| 697 | Start-up |
| 698 | ================== |
| 699 | */ |
| 700 | // the display at a mode, and its two framebuffers (any old ones let go of first) |
| 701 | static 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 |
|
| 747 | int 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 |
|
| 839 | int ps3_video_gpu(void) { return gpu_mode; } |
| 840 |
|
| 841 | int plat_renderer_gpu_now(void) { return gpu_mode; } |
| 842 | int plat_renderer_cpu(void) { return File_Exists(USE_CPU) || File_Exists(GPU_FAILED); } |
| 843 | void 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 |
|
| 855 | uint32_t *plat_canvas_next(void) |
| 856 | { |
| 857 | return gpu_mode ? NULL : cpu_canvas; |
| 858 | } |
| 859 |
|
| 860 | void 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). |
| 893 | void 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 |
|
| 910 | void 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 | } |