mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-25 18:05:12 -04:00
388 lines
19 KiB
C
388 lines
19 KiB
C
/* Frame generation on a real, hidden OpenGL context
|
|
* (docs/FRAME_GENERATION.md). Run by run_gl_frame_gen.py with the render
|
|
* thread on and frame generation off (argv[2] == 0) or on (1, forced: every
|
|
* flip generates, whatever the surplus). A 30 Hz "game" double-buffers at
|
|
* x = 0 / x = 512 and presents every VBlank (each game frame twice), paced at
|
|
* 60 Hz: a fill, textured / shaded / flat triangles that move a little each
|
|
* game frame, a fixed HUD rect, render-to-texture and a VRAM copy outside the
|
|
* display, and every fifth frame a triangle that jumps (never matched). The
|
|
* triangles carry projection sources (fixture_sources) as gpu.c records them
|
|
* for GTE-projected geometry, so in-between frames follow the camera.
|
|
* argv[3] == "wide" presents through a native-wide surface instead of VRAM;
|
|
* argv[5] == "late" flips to each frame one VBlank after the next one has
|
|
* started drawing (as R4 does).
|
|
* Printed: real (digest of every distinct real presented image, in order; a
|
|
* present that shows the same image again is not counted: generation replaces
|
|
* those with in-between frames), presents, generated, and
|
|
* with generation on the endpoint checks: the in-between image composed at
|
|
* phase 1 equals the newer real frame's image, at phase 0 the older one's
|
|
* (the last two game frames move without a jump, so every triangle pairs).
|
|
* Original source-owned scene; no retail payload. */
|
|
static void fixture_present(int generated);
|
|
#define GL_PRESENT_TEST_HOOK(gen) fixture_present(gen)
|
|
/* argv[6] == "pt": the present thread is on; every image it puts in the
|
|
* window (read back after its copy, before its swap) must be the image the
|
|
* composing side presented, in the same order. */
|
|
static void fixture_window(int slot);
|
|
#define GL_PRESENT_THREAD_TEST_HOOK(slot) fixture_window(slot)
|
|
#include "gpu_gl_renderer.c"
|
|
#include "gpu_hd_texture_stubs.inc"
|
|
#include "mod_texture_banks.c"
|
|
/* gpu_render.c (the facade) observes draws for HD texture packs: no pack here. */
|
|
int gpu_hd_textures_active(void){return 0;}
|
|
int gpu_hd_textures_dump_enabled(void){return 0;}
|
|
void gpu_hd_textures_observe_draw(uint16_t tp,uint16_t cx,uint16_t cy,const int l[4],
|
|
uint32_t w,int semi){(void)tp;(void)cx;(void)cy;(void)l;(void)w;(void)semi;}
|
|
void gpu_hd_textures_set_vram(const uint16_t* v){(void)v;}
|
|
void gpu_hd_textures_track_upload(int x,int y,int w,int h,const uint16_t* words){
|
|
(void)x;(void)y;(void)w;(void)h;(void)words;}
|
|
void gpu_hd_textures_invalidate(int x,int y,int w,int h){(void)x;(void)y;(void)w;(void)h;}
|
|
void gpu_hd_textures_begin_upload(int x,int y,int w,int h){(void)x;(void)y;(void)w;(void)h;}
|
|
void gpu_hd_textures_begin_copy(int sx,int sy,int dx,int dy,int w,int h){
|
|
(void)sx;(void)sy;(void)dx;(void)dy;(void)w;(void)h;}
|
|
void gpu_hd_textures_end_copy(void){}
|
|
uint32_t psx_mod_gpu_dma_memory_alloc(uint32_t n,uint32_t a){(void)n;(void)a;return 0;}
|
|
uint32_t psx_mod_read_word(uint32_t a){(void)a;return 0;}
|
|
int g_psx_vram_dirty_tracking=0;
|
|
uint64_t s_frame_count=0;
|
|
void gpu_vram_dirty_mark_row_impl(uint32_t y){(void)y;}
|
|
void gpu_vram_dirty_mark_rect(int x,int y,int w,int h){(void)x;(void)y;(void)w;(void)h;}
|
|
void gpu_vram_dirty_mark_all(void){}
|
|
int psx_netplay_active(void){return 0;}
|
|
const GpuRenderBackend *vk_backend_get(void){return NULL;}
|
|
int gpu_display_is_depth24(void){return 0;}
|
|
void gpu_get_display_info(GpuDisplayInfo *out){memset(out,0,sizeof(*out));out->width=320;out->height=240;}
|
|
/* Guest state the backend reads per primitive. It changes with every
|
|
* primitive here, so a replay that read it live (instead of the value
|
|
* captured when the primitive was recorded) would draw a different image. */
|
|
static int g_prim_id;
|
|
static int g_bg_full;
|
|
int psx_ws_prim_in_backdrop(void){return (g_prim_id % 3) == 0;}
|
|
int psx_ws_prim_is_tagged(void){return (g_prim_id % 5) == 0;}
|
|
int gpu_ws_nw_flat_backdrop_enabled(void){return 0;}
|
|
int gpu_ws_background_requires_full_composite(void){return g_bg_full;}
|
|
int g_ws_tex_edge_pct=0;
|
|
void psx_ws_dbg_gate_frame_snapshot(void){}
|
|
void gpu_depth24_upload_span_reset(void){}
|
|
int gpu_ws_netplay_local_viewport_width(void){return 0;}
|
|
int render_pass_netplay_enabled(void){return 0;}
|
|
uint64_t psx_cycle_count=0;
|
|
uint32_t g_psx_vblank_cycles=564480u;
|
|
/* Frontend hooks of the present path (host overlays, pacing, rings). */
|
|
int host_osd_image(const uint32_t **p,int *w,int *h){*p=NULL;*w=*h=0;return 0;}
|
|
int host_osd_volume_image(const uint32_t **p,int *w,int *h){*p=NULL;*w=*h=0;return 0;}
|
|
int host_osd_needs_present(void){return 0;}
|
|
void host_osd_present_done(void){}
|
|
int psx_rewind_overlay_image(const uint32_t **p,int *w,int *h){*p=NULL;*w=*h=0;return 0;}
|
|
float psx_rewind_slide(void){return 0.0f;}
|
|
int psx_savestate_menu_overlay_image(const uint32_t **p,int *w,int *h){*p=NULL;*w=*h=0;return 0;}
|
|
int present_shot_take(char *out,int n){(void)out;(void)n;return 0;}
|
|
void present_shot_done(int ok){(void)ok;}
|
|
void psx_host_sleep_ms(uint32_t ms){(void)ms;}
|
|
/* Called on every present. On the render thread it also stands in for a slow
|
|
* swap, so replay runs well behind recording and anything it read live from
|
|
* the "guest" (instead of what was recorded) would be a later value. */
|
|
int psx_present_vsync_owns_cadence(void){return 0;}
|
|
void latency_ring_mark(LatencyStage s){(void)s;}
|
|
void gpu_timeline_note(uint8_t k,uint32_t a,uint32_t b){(void)k;(void)a;(void)b;}
|
|
|
|
static uint16_t vram[1024*512];
|
|
static int checks, failures;
|
|
static void check(int ok, const char *label) {
|
|
checks++;
|
|
if (!ok) { fprintf(stderr, "FAIL %s\n", label); failures++; }
|
|
}
|
|
static uint64_t fnv(const void *pp, size_t n, uint64_t h) {
|
|
const uint8_t *p = (const uint8_t *)pp;
|
|
for (size_t i = 0; i < n; i++) { h ^= p[i]; h *= 0x100000001b3ull; }
|
|
return h;
|
|
}
|
|
|
|
/* Presented images: every swap (render thread or not) passes here first. */
|
|
static uint64_t real_seq = 0xcbf29ce484222325ull;
|
|
static uint64_t n_real = 0, n_gen = 0;
|
|
static uint32_t *img_last[2]; /* the last two real images */
|
|
static int img_w, img_h;
|
|
static uint64_t read_back(uint32_t *dst) {
|
|
int ww = 0, wh = 0;
|
|
SDL_GL_GetDrawableSize(s_win, &ww, &wh);
|
|
img_w = ww; img_h = wh;
|
|
p_glBindFramebuffer(PSXGL_READ_FRAMEBUFFER, 0);
|
|
glPixelStorei(GL_PACK_ALIGNMENT, 4);
|
|
glReadPixels(0, 0, ww, wh, GL_RGBA, GL_UNSIGNED_BYTE, dst);
|
|
return fnv(dst, (size_t)ww * wh * 4, 0xcbf29ce484222325ull);
|
|
}
|
|
#define MAX_SHOWN 4096
|
|
static int pt_mode;
|
|
static uint64_t composed_d[MAX_SHOWN], window_d[MAX_SHOWN];
|
|
static int n_composed, n_window;
|
|
static void fixture_window(int slot) {
|
|
(void)slot;
|
|
int ww = 0, wh = 0;
|
|
SDL_GL_GetDrawableSize(s_win, &ww, &wh);
|
|
uint32_t *px = (uint32_t *)malloc((size_t)ww * wh * 4);
|
|
p_glBindFramebuffer_raw(PSXGL_READ_FRAMEBUFFER, 0);
|
|
glReadBuffer(GL_BACK);
|
|
glPixelStorei(GL_PACK_ALIGNMENT, 4);
|
|
glReadPixels(0, 0, ww, wh, GL_RGBA, GL_UNSIGNED_BYTE, px);
|
|
if (n_window < MAX_SHOWN)
|
|
window_d[n_window] = fnv(px, (size_t)ww * wh * 4, 0xcbf29ce484222325ull);
|
|
n_window++;
|
|
free(px);
|
|
}
|
|
static void fixture_present(int generated) {
|
|
if (pt_mode) {
|
|
int ww = 0, wh = 0;
|
|
SDL_GL_GetDrawableSize(s_win, &ww, &wh);
|
|
uint32_t *px = (uint32_t *)malloc((size_t)ww * wh * 4);
|
|
p_glBindFramebuffer(PSXGL_READ_FRAMEBUFFER, 0);
|
|
glPixelStorei(GL_PACK_ALIGNMENT, 4);
|
|
glReadPixels(0, 0, ww, wh, GL_RGBA, GL_UNSIGNED_BYTE, px);
|
|
if (n_composed < MAX_SHOWN)
|
|
composed_d[n_composed] = fnv(px, (size_t)ww * wh * 4, 0xcbf29ce484222325ull);
|
|
n_composed++;
|
|
free(px);
|
|
}
|
|
if (generated) { n_gen++; return; }
|
|
int ww = 0, wh = 0;
|
|
SDL_GL_GetDrawableSize(s_win, &ww, &wh);
|
|
for (int i = 0; i < 2; i++)
|
|
if (!img_last[i]) img_last[i] = (uint32_t *)malloc((size_t)ww * wh * 4);
|
|
uint32_t *t = img_last[0]; img_last[0] = img_last[1]; img_last[1] = t;
|
|
static uint64_t last_d = 0;
|
|
uint64_t d = read_back(img_last[1]);
|
|
if (d == last_d) { /* the same image shown again: not a new real frame */
|
|
uint32_t *u = img_last[0]; img_last[0] = img_last[1]; img_last[1] = u;
|
|
return;
|
|
}
|
|
last_d = d;
|
|
if (getenv("FG_DUMP")) {
|
|
printf("R %llu %016llx\n", (unsigned long long)n_real, (unsigned long long)d);
|
|
char fn[64]; snprintf(fn, sizeof fn, "real%02llu.rgba", (unsigned long long)n_real);
|
|
FILE *f = fopen(fn, "wb"); fwrite(img_last[1], 4, (size_t)ww * wh, f); fclose(f);
|
|
}
|
|
real_seq = fnv(&d, sizeof d, real_seq);
|
|
n_real++;
|
|
}
|
|
|
|
#define FRAME_W 320
|
|
#define FRAME_H 240
|
|
|
|
static void textures(void) {
|
|
static uint16_t page[64*64], clut[16];
|
|
for (int i = 0; i < 64*64; i++) page[i] = (uint16_t)((i * 0x1357u) ^ (i >> 3));
|
|
for (int i = 0; i < 16; i++) clut[i] = (uint16_t)(i ? (0x0421u * (uint16_t)i) | ((i & 1) << 15) : 0);
|
|
/* Outside both buffers (x 0..319, 512..831; y 0..239). */
|
|
for (int i = 0; i < 64*64; i++) vram[(i / 64) * 1024 + 896 + i % 64] = page[i];
|
|
gr_vram_transfer_in(896, 0, 64, 64, page);
|
|
gr_vram_transfer_in(512, 300, 16, 1, clut);
|
|
}
|
|
|
|
static int wide_mode;
|
|
/* What gpu.c records for a GTE-projected triangle (gl_renderer_fg_source):
|
|
* vertex identities and camera-space positions that project (H = z = 1000)
|
|
* to the triangle's screen positions relative to its buffer. */
|
|
/* Structurally different: a channel off by more than 48 (sub-pixel
|
|
* placement at a high scale resamples textures by a few LSB). */
|
|
static int px_far(uint32_t a, uint32_t b) {
|
|
for (int c = 0; c < 24; c += 8) { int d = (int)((a >> c) & 255) - (int)((b >> c) & 255); if (d > 48 || d < -48) return 1; }
|
|
return 0;
|
|
}
|
|
|
|
static void fixture_sources(int i, int bx, int x, int y) {
|
|
static const int off[4][6] = {
|
|
{ 0, 0, 30, 2, 3, 32 }, { 0, 0, 28, 0, 0, 28 }, { 0, 0, 26, 4, 6, 30 }, { 0, 0, 24, 20, -4, 26 } };
|
|
const int *o = off[i % 4];
|
|
uint32_t id[3]; int32_t pc[9], h[3], xs[3], ys[3];
|
|
for (int k = 0; k < 3; k++) {
|
|
id[k] = (uint32_t)(i * 3 + k + 1);
|
|
xs[k] = x + o[2 * k]; ys[k] = y + o[2 * k + 1];
|
|
pc[3 * k] = xs[k] - bx; pc[3 * k + 1] = ys[k]; pc[3 * k + 2] = 1000; h[k] = 1000;
|
|
}
|
|
gl_renderer_fg_source(id, pc, h, xs, ys);
|
|
}
|
|
|
|
/* One game frame drawn into buffer bx. Positions move by whole pixels with
|
|
* g; every fifth frame triangle 0 jumps across the screen. */
|
|
static void game_frame_part(int g, int bx, int part) {
|
|
g_prim_id = -1;
|
|
gr_set_draw_area(bx, 0, bx + FRAME_W - 1, FRAME_H - 1);
|
|
gr_set_draw_offset(bx, 0);
|
|
if (wide_mode) gr_wide_set_target(bx);
|
|
if (part == 0) {
|
|
gr_fill_rect(bx, 0, FRAME_W, FRAME_H, 0x1084);
|
|
if (wide_mode) gr_wide_clear(bx, 0, FRAME_H, 0x1084);
|
|
}
|
|
gr_set_color_modulation(128, 128, 128, 0);
|
|
gr_set_semi_transparency(0, 0);
|
|
for (int i = part ? 20 : 0; i < (part ? 40 : 20); i++) {
|
|
/* The camera pans one pixel per game frame (every triangle moves
|
|
* alike); triangle 5 is an object moving on its own. */
|
|
int x = bx + 10 + (i % 8) * 36 + (i == 5 ? (g * 3) % 9 : g % 9) - 4;
|
|
int y = 12 + (i / 8) * 42 + (i == 5 ? g % 5 : 0);
|
|
if (i == 0 && g % 5 == 0) { x = bx + 250 - (g * 37) % 200; y = 180; }
|
|
fixture_sources(i, bx, x, y);
|
|
switch (i % 4) {
|
|
case 0: gr_draw_shaded_textured_triangle(x, y, 0, 0, 0x808080, x + 30, y + 2, 63, 4, 0x6090b0,
|
|
x + 3, y + 32, 8, 63, 0xb09060, 512, 300, 0x000e, 0); break;
|
|
case 1: gr_draw_textured_triangle(x, y, 4, 4, x + 28, y, 60, 4, x, y + 28, 4, 60, 512, 300, 0x000e); break;
|
|
case 2: gr_draw_gouraud_triangle(x, y, 0x7c00, x + 26, y + 4, 0x03e0, x + 6, y + 30, 0x001f); break;
|
|
default: gr_draw_flat_triangle(x, y, x + 24, y + 20, x - 4, y + 26, (uint16_t)(0x2108 * (i & 3) + 0x0421)); break;
|
|
}
|
|
}
|
|
if (part == 0) return;
|
|
gr_draw_flat_rect(bx + 8, 220, 80, 12, 0x7fff); /* HUD */
|
|
gr_draw_flat_rect(900, 300, 32, 32, (uint16_t)(0x03e0 + g)); /* render to texture */
|
|
gr_copy_rect(bx + 10, 10, 900, 400, 16, 16); /* copy out of the display */
|
|
}
|
|
|
|
static void present(int bx) {
|
|
if (wide_mode) (void)gl_renderer_present_wide_fbo(bx, 0, FRAME_H, 0);
|
|
else gl_renderer_present_vram(bx, 0, FRAME_W, FRAME_H, 0, 0);
|
|
gl_renderer_render_thread_frame_boundary();
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
int scale = argc > 1 ? atoi(argv[1]) : 2;
|
|
int fg = argc > 2 && argv[2][0] == '1';
|
|
wide_mode = argc > 3 && !strcmp(argv[3], "wide");
|
|
int frames = argc > 4 ? atoi(argv[4]) : 24;
|
|
/* "late": the game flips to a frame one VBlank after it starts drawing
|
|
* the next one (as R4 does), so a flip is not a list boundary. */
|
|
int late = argc > 5 && !strcmp(argv[5], "late");
|
|
pt_mode = argc > 6 && !strcmp(argv[6], "pt");
|
|
if (SDL_Init(SDL_INIT_VIDEO) != 0) {
|
|
fprintf(stderr, "SKIP no video (%s)\n", SDL_GetError());
|
|
return 77;
|
|
}
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
|
#if defined(__APPLE__)
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG);
|
|
#endif
|
|
SDL_Window *win = SDL_CreateWindow("Frame generation hidden test", 0, 0, 320, 240,
|
|
SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN);
|
|
if (!win) { fprintf(stderr, "SKIP no window (%s)\n", SDL_GetError()); return 77; }
|
|
{
|
|
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
|
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
|
|
SDL_GLContext probe = SDL_GL_CreateContext(win);
|
|
if (!probe) {
|
|
fprintf(stderr, "SKIP no GL 3.3 core context (%s)\n", SDL_GetError());
|
|
return 77;
|
|
}
|
|
SDL_GL_MakeCurrent(win, NULL);
|
|
SDL_GL_DeleteContext(probe);
|
|
}
|
|
gr_set_backend(GR_BACKEND_OPENGL);
|
|
gr_init(vram);
|
|
gr_set_scale(scale);
|
|
gl_renderer_set_swap_interval(0);
|
|
if (!gl_renderer_init_context(win)) { fprintf(stderr, "FAIL context\n"); return 2; }
|
|
textures();
|
|
if (wide_mode) gr_wide_configure(426, 53);
|
|
if (pt_mode) gl_renderer_set_present_thread(1, 3);
|
|
check(gl_renderer_render_thread_start(2) == 1, "render thread started");
|
|
if (pt_mode) check(gl_renderer_present_thread_active(), "present thread started");
|
|
if (fg) {
|
|
#ifdef _WIN32
|
|
_putenv("PSX_FRAME_GEN_FORCE=1");
|
|
#else
|
|
setenv("PSX_FRAME_GEN_FORCE", "1", 1);
|
|
#endif
|
|
gl_renderer_set_frame_generation(1);
|
|
check(gl_renderer_frame_generation() == 1, "frame generation on");
|
|
}
|
|
gl_renderer_frame_gen_configure(120.0, 59.94);
|
|
for (int g = 0; g < frames; g++) {
|
|
const int bx = (g & 1) ? 512 : 0, prev = bx ^ 512;
|
|
if (late) {
|
|
game_frame_part(g, bx, 0);
|
|
present(prev); /* flips to the previous frame */
|
|
SDL_Delay(16);
|
|
game_frame_part(g, bx, 1);
|
|
present(prev);
|
|
SDL_Delay(16);
|
|
continue;
|
|
}
|
|
game_frame_part(g, bx, 0);
|
|
game_frame_part(g, bx, 1);
|
|
present(bx); /* the game frame's first VBlank: a flip */
|
|
SDL_Delay(16);
|
|
present(bx); /* its second: the same buffer again */
|
|
SDL_Delay(16);
|
|
}
|
|
SDL_Delay(60);
|
|
gl_renderer_render_thread_sync("test"); /* the render thread parks; we hold the context */
|
|
if (fg) {
|
|
char js[1024];
|
|
gl_renderer_frame_gen_json(js, sizeof js);
|
|
printf("fg {%s}\n", js);
|
|
/* Endpoints: compose (no swap) at phase 1 and 0 between the last two
|
|
* game frames and compare with their real images. */
|
|
int ww = 0, wh = 0;
|
|
SDL_GL_GetDrawableSize(s_win, &ww, &wh);
|
|
uint32_t *img = (uint32_t *)malloc((size_t)ww * wh * 4);
|
|
check(img_last[0] && img_last[1], "two real images kept");
|
|
check(fg_generate(1.0, 0) == 1, "compose at phase 1");
|
|
uint64_t d1 = read_back(img);
|
|
uint64_t r1 = fnv(img_last[1], (size_t)ww * wh * 4, 0xcbf29ce484222325ull);
|
|
if (getenv("FG_DUMP")) {
|
|
FILE *f = fopen("p1.rgba", "wb"); fwrite(img, 4, (size_t)ww * wh, f); fclose(f);
|
|
f = fopen("r1.rgba", "wb"); fwrite(img_last[1], 4, (size_t)ww * wh, f); fclose(f);
|
|
f = fopen("r0.rgba", "wb"); fwrite(img_last[0], 4, (size_t)ww * wh, f); fclose(f);
|
|
printf("dump %dx%d\n", ww, wh);
|
|
}
|
|
check(d1 == r1, "phase 1 equals the newer real frame");
|
|
check(fg_generate(0.0, 0) == 1, "compose at phase 0");
|
|
uint64_t d0 = read_back(img);
|
|
uint64_t r0 = fnv(img_last[0], (size_t)ww * wh * 4, 0xcbf29ce484222325ull);
|
|
/* Every projected triangle is where the older frame had it; what the
|
|
* in-between camera uncovers (here: the background beside a moved
|
|
* triangle, through its transparent texels) shows the newer frame
|
|
* (frame_gen.h). So each phase-0 pixel is the older frame's, or the
|
|
* newer frame's where they differ. */
|
|
if (getenv("FG_DUMP")) { FILE *f = fopen("p0.rgba", "wb"); fwrite(img, 4, (size_t)ww * wh, f); fclose(f); }
|
|
long diff = 0, neither = 0;
|
|
for (int i = 0; i < ww * wh; i++)
|
|
if (img[i] != img_last[0][i]) { diff++; neither += px_far(img[i], img_last[0][i]) && px_far(img[i], img_last[1][i]); }
|
|
printf("phase0_diff_px=%ld (neither frame %ld) of %d camera=%u object=%u\n", diff, neither, ww * wh,
|
|
s_fg_fit.camera, s_fg_fit.object);
|
|
(void)d0; (void)r0;
|
|
check(s_fg_fit.ok && s_fg_fit.camera + s_fg_fit.object == 120, "every projected vertex placed");
|
|
check(neither * 1000 < (long)ww * wh, "phase 0: the older real frame, uncovered areas the newer (<0.1%)");
|
|
check(fg_generate(0.5, 0) == 1, "compose at phase 0.5");
|
|
uint64_t dm = read_back(img);
|
|
if (getenv("FG_DUMP")) {
|
|
FILE *f = fopen("pm.rgba", "wb"); fwrite(img, 4, (size_t)ww * wh, f); fclose(f);
|
|
}
|
|
check(dm != r0 && dm != r1, "phase 0.5 is neither endpoint");
|
|
/* With an HD texture pack the CPU raster is native VRAM's authority;
|
|
* an in-between frame is presentation only and must not draw there. */
|
|
gl_renderer_set_hd_texture_mode(1);
|
|
static uint16_t before[1024 * 512];
|
|
memcpy(before, s_vram, sizeof before);
|
|
check(fg_generate(0.5, 0) == 1, "compose at phase 0.5 under HD authority");
|
|
check(memcmp(before, s_vram, sizeof before) == 0,
|
|
"generated frame leaves native VRAM unchanged under HD authority");
|
|
gl_renderer_set_hd_texture_mode(0);
|
|
free(img);
|
|
check(n_gen > 0, "generated frames were presented");
|
|
}
|
|
check(glGetError() == GL_NO_ERROR, "GL error");
|
|
printf("real=%016llx\n", (unsigned long long)real_seq);
|
|
printf("presents=%llu generated=%llu\n", (unsigned long long)n_real, (unsigned long long)n_gen);
|
|
gl_renderer_shutdown(); /* stops the render and present threads: all shown */
|
|
if (pt_mode) {
|
|
int same = n_composed == n_window && n_composed <= MAX_SHOWN && n_composed > 0;
|
|
for (int i = 0; same && i < n_composed; i++) same = composed_d[i] == window_d[i];
|
|
printf("pt composed=%d shown=%d\n", n_composed, n_window);
|
|
check(same, "the window shows every composed image, in order");
|
|
}
|
|
printf("checks=%d failures=%d\n", checks, failures);
|
|
SDL_DestroyWindow(win);
|
|
SDL_Quit();
|
|
return failures ? 1 : 0;
|
|
}
|