mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 10:19:47 -04:00
Preserve active display field during interlaced raster drawing
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
# Interlaced primitive drawing
|
||||
|
||||
The GPU preserves one row parity while drawing in 480-line interlaced mode
|
||||
when GP0(E1h) prohibits drawing to the display area. The display origin and
|
||||
current field select that parity. Progressive modes and the draw-to-display
|
||||
override draw both parities. Fills, copies and VRAM transfers keep both fields.
|
||||
|
||||
`gpu_raster_skipped_row()` reads current GPU state for each primitive. The
|
||||
renderer facade applies the field mask with native-row clip rectangles and
|
||||
restores the original clip. This also preserves the corresponding rows in
|
||||
scaled surfaces. Each clipped triangle retains its precision and perspective
|
||||
metadata, which hardware backends otherwise consume on the first submission.
|
||||
No title address or title detection selects this behavior.
|
||||
|
||||
Pro Pinball: Timeshock! USA SLUS-00639 repeatedly draws and reads a test pixel
|
||||
at (1020,511) during startup. Frozen source 63446a28 always returned the drawn
|
||||
pixel and stayed black. This correction lets the guest's existing field test
|
||||
finish. The same disc, BIOS and settings then reach the table.
|
||||
|
||||
The field latch uses the runtime's existing vertical-blank model. This change
|
||||
does not claim scanline-accurate timing. Row clipping can increase submissions
|
||||
for large interlaced primitives; no general performance claim is made.
|
||||
|
||||
## Verification
|
||||
|
||||
Configure `runtime/` with `BUILD_TESTING=ON`, then run
|
||||
`ctest --test-dir <build> -R gpu_interlace_render_test --output-on-failure`.
|
||||
The fixture covers the mode predicate, origin/field parity, all nine primitive
|
||||
families, clip restoration, transfers, mode changes, scaled output and
|
||||
triangle metadata consumed by a backend. The original renderer fails the
|
||||
same row-preservation assertions.
|
||||
|
||||
## Prior art
|
||||
|
||||
- [PSX-SPX GPU specification](https://psx-spx.consoledev.net/graphicsprocessingunitgpu/)
|
||||
describes interlace affecting draw commands.
|
||||
- [DuckStation difficult games](https://github.com/stenzek/duckstation/wiki/Difficult-to-Emulate-Games)
|
||||
identifies required line skipping for Pro Pinball startup.
|
||||
|
||||
These sources supplied behavioral evidence. The implementation and regression
|
||||
fixture were written independently. Exact title run receipts and route limits
|
||||
are maintained by the Timeshock correction project.
|
||||
@@ -440,6 +440,15 @@ if(BUILD_TESTING)
|
||||
target_include_directories(gpu_sw_edges_test PRIVATE src)
|
||||
add_test(NAME gpu_sw_edges_test COMMAND gpu_sw_edges_test)
|
||||
|
||||
add_executable(gpu_interlace_render_test
|
||||
tests/test_gpu_interlace_render.c src/gpu_render.c
|
||||
src/gpu_sw_renderer.c src/gpu_vram_dirty.c)
|
||||
target_include_directories(gpu_interlace_render_test PRIVATE include)
|
||||
if(NOT MSVC)
|
||||
target_link_libraries(gpu_interlace_render_test PRIVATE m)
|
||||
endif()
|
||||
add_test(NAME gpu_interlace_render_test COMMAND gpu_interlace_render_test)
|
||||
|
||||
add_executable(gpu_vk_upload_alignment_test
|
||||
tests/test_gpu_vk_upload_alignment.c)
|
||||
target_include_directories(gpu_vk_upload_alignment_test PRIVATE src)
|
||||
|
||||
@@ -16,7 +16,8 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
void gpu_init(void);
|
||||
uint32_t gpu_read_gpustat(void); /* 0x1F801814 read */
|
||||
uint32_t gpu_read_gpustat(void);
|
||||
int gpu_raster_skipped_row(void); /* 0x1F801814 read */
|
||||
uint32_t gpu_read_gpuread(void); /* 0x1F801810 read */
|
||||
void gpu_write_gp0(uint32_t val); /* 0x1F801810 write */
|
||||
void gpu_write_gp1(uint32_t val); /* 0x1F801814 write */
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
#ifndef PSX_GPU_INTERLACE_H
|
||||
#define PSX_GPU_INTERLACE_H
|
||||
|
||||
/* GP0 raster primitives skip the active field only in 480i mode with
|
||||
* drawing-to-display prohibited. VRAM transfer commands do not use this rule.
|
||||
* Return -1 for progressive drawing, otherwise the native VRAM row parity
|
||||
* that must remain unchanged. The display origin participates in that parity. */
|
||||
static inline int psx_gpu_raster_skipped_row(unsigned interlace,
|
||||
unsigned high_vertical_resolution, unsigned draw_to_display,
|
||||
unsigned display_y, unsigned field) {
|
||||
if (!interlace || !high_vertical_resolution || draw_to_display)
|
||||
return -1;
|
||||
return (int)((display_y + field) & 1u);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -11,6 +11,7 @@
|
||||
*/
|
||||
|
||||
#include "gpu.h"
|
||||
#include "gpu_interlace.h"
|
||||
#include "display_scanout.h"
|
||||
#include "pgxp.h"
|
||||
#include "mod_memory.h"
|
||||
@@ -2732,6 +2733,14 @@ void gpu_init(void) {
|
||||
|
||||
/* ---- GPUSTAT read (0x1F801814) ---- */
|
||||
|
||||
/* Primitive rasterization must leave the active display field untouched in
|
||||
* 480-line interlaced mode unless GP0(E1h).10 permits drawing to it.
|
||||
* Transfers, fills, and copies are not raster primitives. */
|
||||
int gpu_raster_skipped_row(void) {
|
||||
return psx_gpu_raster_skipped_row(vertical_interlace, vres,
|
||||
draw_to_display, display_area_y, lcf);
|
||||
}
|
||||
|
||||
uint32_t gpu_read_gpustat(void) {
|
||||
/* Advance vblank when polled enough times from within a single function.
|
||||
* This handles BIOS VSYNC wait loops that poll LCF in tight loops.
|
||||
|
||||
+63
-13
@@ -7,10 +7,11 @@
|
||||
* gl_backend_get(); if that returns NULL (GL unavailable / init failed) we
|
||||
* stay on software so a misconfigured [video] renderer never bricks boot.
|
||||
*
|
||||
* The software backend table points straight at the existing sw_* functions,
|
||||
* so the software path is byte-for-byte unchanged. */
|
||||
* Primitive dispatch also applies the GPU's interlaced field mask through
|
||||
* the backend clip, at native VRAM row granularity. */
|
||||
|
||||
#include "gpu_render.h"
|
||||
#include "gpu.h"
|
||||
#include "gpu_sw_renderer.h"
|
||||
#include <stdio.h>
|
||||
|
||||
@@ -64,6 +65,11 @@ extern const GpuRenderBackend *vk_backend_get(void);
|
||||
|
||||
static const GpuRenderBackend *g_b = &SW_BACKEND;
|
||||
static GrBackend g_effective = GR_BACKEND_SOFTWARE;
|
||||
/* Backends consume triangle metadata on submission. Re-arm it for each
|
||||
* clipped row belonging to the same interlaced primitive. */
|
||||
static int g_pc_enabled, g_pq_enabled;
|
||||
static int32_t g_pc[6];
|
||||
static float g_pq[3];
|
||||
|
||||
void gr_set_backend(GrBackend backend) {
|
||||
if (backend == GR_BACKEND_OPENGL) {
|
||||
@@ -105,48 +111,92 @@ void gr_set_texture_window(uint32_t raw) { g_b->set_texture_window(r
|
||||
void gr_set_color_modulation(int r, int g, int b, int raw) { g_b->set_color_modulation(r, g, b, raw); }
|
||||
void gr_set_precise_triangle(int enabled, int32_t x0, int32_t y0, int32_t x1, int32_t y1,
|
||||
int32_t x2, int32_t y2) {
|
||||
g_pc_enabled = enabled;
|
||||
g_pc[0]=x0; g_pc[1]=y0; g_pc[2]=x1; g_pc[3]=y1; g_pc[4]=x2; g_pc[5]=y2;
|
||||
if (g_b->set_precise_triangle)
|
||||
g_b->set_precise_triangle(enabled, x0, y0, x1, y1, x2, y2);
|
||||
}
|
||||
void gr_set_perspective_triangle(int enabled, float q0, float q1, float q2) {
|
||||
g_pq_enabled=enabled; g_pq[0]=q0; g_pq[1]=q1; g_pq[2]=q2;
|
||||
if (g_b->set_perspective_triangle)
|
||||
g_b->set_perspective_triangle(enabled, q0, q1, q2);
|
||||
}
|
||||
void gr_fill_rect(int x, int y, int w, int h, uint16_t c) { g_b->fill_rect(x, y, w, h, c); }
|
||||
void gr_copy_rect(int sx, int sy, int dx, int dy, int w, int h) { g_b->copy_rect(sx, sy, dx, dy, w, h); }
|
||||
/* Apply field masking through each backend's native-coordinate clip. This
|
||||
* preserves the same rows in canonical VRAM and supersampled surfaces, and
|
||||
* leaves non-raster transfers untouched. Bounds are already offset by gpu.c.
|
||||
* Query live GPU state per primitive so reset/restore/mode changes cannot
|
||||
* leave a stale renderer-side field latch. */
|
||||
static int gr_min(int a, int b) { return a < b ? a : b; }
|
||||
static int gr_max(int a, int b) { return a > b ? a : b; }
|
||||
static void gr_rearm_triangle(void) {
|
||||
if (g_b->set_precise_triangle)
|
||||
g_b->set_precise_triangle(g_pc_enabled,g_pc[0],g_pc[1],g_pc[2],g_pc[3],g_pc[4],g_pc[5]);
|
||||
if (g_b->set_perspective_triangle)
|
||||
g_b->set_perspective_triangle(g_pq_enabled,g_pq[0],g_pq[1],g_pq[2]);
|
||||
}
|
||||
#define GR_TRIANGLE_ROWS(ylo, yhi, draw_call) do { \
|
||||
/* Precise positions can cross the integer primitive bounds. */ \
|
||||
int lo = (ylo), hi = (yhi); \
|
||||
if (g_pc_enabled) { lo = 0; hi = 511; } \
|
||||
GR_RASTER_ROWS(lo, hi, (gr_rearm_triangle(), draw_call)); \
|
||||
g_pc_enabled = g_pq_enabled = 0; \
|
||||
gr_rearm_triangle(); \
|
||||
} while (0)
|
||||
#define GR_RASTER_ROWS(ylo, yhi, draw_call) do { \
|
||||
int skip = gpu_raster_skipped_row(); \
|
||||
if (skip < 0) { draw_call; } else { \
|
||||
int cx1, cy1, cx2, cy2; \
|
||||
g_b->get_draw_area(&cx1, &cy1, &cx2, &cy2); \
|
||||
int first = gr_max(gr_max((ylo), cy1), 0); \
|
||||
int last = gr_min(gr_min((yhi), cy2), 511); \
|
||||
for (int row = first; row <= last; ++row) { \
|
||||
if ((row & 1) == skip) continue; \
|
||||
g_b->set_draw_area(cx1, row, cx2, row); \
|
||||
draw_call; \
|
||||
} \
|
||||
g_b->set_draw_area(cx1, cy1, cx2, cy2); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
void gr_draw_flat_triangle(int x0, int y0, int x1, int y1, int x2, int y2, uint16_t c) {
|
||||
g_b->draw_flat_triangle(x0, y0, x1, y1, x2, y2, c);
|
||||
GR_TRIANGLE_ROWS(gr_min(y0, gr_min(y1, y2)), gr_max(y0, gr_max(y1, y2)), g_b->draw_flat_triangle(x0, y0, x1, y1, x2, y2, c));
|
||||
}
|
||||
void gr_draw_gouraud_triangle(int x0, int y0, uint16_t c0, int x1, int y1, uint16_t c1,
|
||||
int x2, int y2, uint16_t c2) {
|
||||
g_b->draw_gouraud_triangle(x0, y0, c0, x1, y1, c1, x2, y2, c2);
|
||||
GR_TRIANGLE_ROWS(gr_min(y0, gr_min(y1, y2)), gr_max(y0, gr_max(y1, y2)), g_b->draw_gouraud_triangle(x0, y0, c0, x1, y1, c1, x2, y2, c2));
|
||||
}
|
||||
void gr_draw_textured_triangle(int x0, int y0, int u0, int v0, int x1, int y1, int u1, int v1,
|
||||
int x2, int y2, int u2, int v2,
|
||||
uint16_t clut_x, uint16_t clut_y, uint16_t texpage) {
|
||||
g_b->draw_textured_triangle(x0, y0, u0, v0, x1, y1, u1, v1, x2, y2, u2, v2,
|
||||
clut_x, clut_y, texpage);
|
||||
GR_TRIANGLE_ROWS(gr_min(y0, gr_min(y1, y2)), gr_max(y0, gr_max(y1, y2)), g_b->draw_textured_triangle(x0, y0, u0, v0, x1, y1, u1, v1, x2, y2, u2, v2,
|
||||
clut_x, clut_y, texpage));
|
||||
}
|
||||
void gr_draw_shaded_textured_triangle(int x0, int y0, int u0, int v0, uint32_t c0,
|
||||
int x1, int y1, int u1, int v1, uint32_t c1,
|
||||
int x2, int y2, int u2, int v2, uint32_t c2,
|
||||
uint16_t clut_x, uint16_t clut_y,
|
||||
uint16_t texpage, int raw) {
|
||||
g_b->draw_shaded_textured_triangle(x0, y0, u0, v0, c0, x1, y1, u1, v1, c1,
|
||||
x2, y2, u2, v2, c2, clut_x, clut_y, texpage, raw);
|
||||
GR_TRIANGLE_ROWS(gr_min(y0, gr_min(y1, y2)), gr_max(y0, gr_max(y1, y2)), g_b->draw_shaded_textured_triangle(x0, y0, u0, v0, c0, x1, y1, u1, v1, c1,
|
||||
x2, y2, u2, v2, c2, clut_x, clut_y, texpage, raw));
|
||||
}
|
||||
void gr_draw_flat_rect(int x, int y, int w, int h, uint16_t c) {
|
||||
GR_RASTER_ROWS(y, y + h - 1, g_b->draw_flat_rect(x, y, w, h, c));
|
||||
}
|
||||
void gr_draw_flat_rect(int x, int y, int w, int h, uint16_t c) { g_b->draw_flat_rect(x, y, w, h, c); }
|
||||
void gr_draw_textured_rect(int x, int y, int w, int h, int u, int v,
|
||||
uint16_t clut_x, uint16_t clut_y, uint16_t texpage) {
|
||||
g_b->draw_textured_rect(x, y, w, h, u, v, clut_x, clut_y, texpage);
|
||||
GR_RASTER_ROWS(y, y + h - 1, g_b->draw_textured_rect(x, y, w, h, u, v, clut_x, clut_y, texpage));
|
||||
}
|
||||
void gr_draw_textured_rect_scaled(int x, int y, int w, int h, int u0, int v0, int u1, int v1,
|
||||
uint16_t clut_x, uint16_t clut_y, uint16_t texpage) {
|
||||
g_b->draw_textured_rect_scaled(x, y, w, h, u0, v0, u1, v1, clut_x, clut_y, texpage);
|
||||
GR_RASTER_ROWS(y, y + h - 1, g_b->draw_textured_rect_scaled(x, y, w, h, u0, v0, u1, v1, clut_x, clut_y, texpage));
|
||||
}
|
||||
void gr_draw_line(int x0, int y0, int x1, int y1, uint16_t c) {
|
||||
GR_RASTER_ROWS(gr_min(y0, y1), gr_max(y0, y1), g_b->draw_line(x0, y0, x1, y1, c));
|
||||
}
|
||||
void gr_draw_line(int x0, int y0, int x1, int y1, uint16_t c) { g_b->draw_line(x0, y0, x1, y1, c); }
|
||||
void gr_draw_shaded_line(int x0, int y0, uint16_t c0, int x1, int y1, uint16_t c1) {
|
||||
g_b->draw_shaded_line(x0, y0, c0, x1, y1, c1);
|
||||
GR_RASTER_ROWS(gr_min(y0, y1), gr_max(y0, y1), g_b->draw_shaded_line(x0, y0, c0, x1, y1, c1));
|
||||
}
|
||||
int gr_render_display(uint32_t *o, int p, int dx, int dy, int dw, int dh) {
|
||||
return g_b->render_display(o, p, dx, dy, dw, dh);
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
#include "gpu_render.h"
|
||||
#include "gpu_interlace.h"
|
||||
#include "gpu_sw_renderer.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static uint16_t vram[1024 * 512];
|
||||
static uint32_t hires[32*4*32*4];
|
||||
static int skipped_row;
|
||||
static int failures;
|
||||
static int mock_enabled, mock_pc, mock_pq, mock_submissions, mock_bad;
|
||||
static void mock_precise(int e,int32_t x0,int32_t y0,int32_t x1,int32_t y1,int32_t x2,int32_t y2) {
|
||||
(void)x0;(void)y0;(void)x1;(void)y1;(void)x2;(void)y2;mock_pc=e;
|
||||
}
|
||||
static void mock_perspective(int e,float q0,float q1,float q2) {
|
||||
(void)q0;(void)q1;(void)q2;mock_pq=e;
|
||||
}
|
||||
static void mock_triangle(int x0,int y0,int x1,int y1,int x2,int y2,uint16_t c) {
|
||||
(void)x0;(void)y0;(void)x1;(void)y1;(void)x2;(void)y2;(void)c;
|
||||
++mock_submissions;if(!mock_pc || !mock_pq)++mock_bad;
|
||||
mock_pc=mock_pq=0; /* GPU backends consume these per submission. */
|
||||
}
|
||||
static const GpuRenderBackend mock_backend={
|
||||
.name="metadata fixture",.get_draw_area=sw_get_draw_area,.set_draw_area=sw_set_draw_area,
|
||||
.set_precise_triangle=mock_precise,.set_perspective_triangle=mock_perspective,
|
||||
.draw_flat_triangle=mock_triangle
|
||||
};
|
||||
/* This fixture keeps optional widescreen presentation disabled. */
|
||||
int g_ws_bd_stretch_pct, g_ws_bd_stretch_on;
|
||||
int psx_ws_prim_in_backdrop(void) { return 0; }
|
||||
int gpu_raster_skipped_row(void) { return skipped_row; }
|
||||
const GpuRenderBackend *gl_backend_get(void) { return mock_enabled ? &mock_backend : NULL; }
|
||||
const GpuRenderBackend *vk_backend_get(void) { return NULL; }
|
||||
#define CHECK(c) do { if (!(c)) { \
|
||||
fprintf(stderr, "FAIL line %d: %s\n", __LINE__, #c); ++failures; \
|
||||
} } while (0)
|
||||
|
||||
static void reset_canvas(void) {
|
||||
skipped_row = -1;
|
||||
gr_fill_rect(0, 0, 32, 32, 0);
|
||||
gr_set_draw_area(0, 0, 31, 31);
|
||||
gr_set_semi_transparency(0, 0);
|
||||
gr_set_mask_bits(0, 0);
|
||||
gr_set_color_modulation(128, 128, 128, 1);
|
||||
}
|
||||
|
||||
static void draw_kind(int kind) {
|
||||
switch (kind) {
|
||||
case 0: gr_draw_flat_rect(2, 2, 8, 8, 0x7FFF); break;
|
||||
case 1: gr_draw_flat_triangle(2, 2, 10, 2, 2, 10, 0x7FFF); break;
|
||||
case 2: gr_draw_gouraud_triangle(2, 2, 0x7FFF, 10, 2, 0x7FFF, 2, 10, 0x7FFF); break;
|
||||
case 3: gr_draw_line(2, 2, 2, 9, 0x7FFF); break;
|
||||
case 4: gr_draw_shaded_line(2, 2, 0x7FFF, 2, 9, 0x7FFF); break;
|
||||
case 5: gr_draw_textured_rect(2, 2, 8, 8, 0, 0, 0, 0, 0x104); break;
|
||||
case 6: gr_draw_textured_rect_scaled(2, 2, 8, 8, 0, 0, 8, 8, 0, 0, 0x104); break;
|
||||
case 7: gr_draw_textured_triangle(2, 2, 0, 0, 10, 2, 8, 0, 2, 10, 0, 8, 0, 0, 0x104); break;
|
||||
case 8: gr_draw_shaded_textured_triangle(2, 2, 0, 0, 0x808080, 10, 2, 8, 0, 0x808080,
|
||||
2, 10, 0, 8, 0x808080, 0, 0, 0x104, 1); break;
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
for (unsigned bits = 0; bits < 32; ++bits) {
|
||||
unsigned i=bits&1, h=(bits>>1)&1, allow=(bits>>2)&1;
|
||||
unsigned y=(bits>>3)&1, field=(bits>>4)&1;
|
||||
CHECK(psx_gpu_raster_skipped_row(i,h,allow,y,field) ==
|
||||
((i && h && !allow) ? (int)(y ^ field) : -1));
|
||||
}
|
||||
for (int scale = 1; scale <= 4; scale *= 4) {
|
||||
gr_init(vram); gr_set_scale(scale);
|
||||
gr_fill_rect(256, 0, 16, 16, 0x7FFF);
|
||||
for (int kind = 0; kind < 9; ++kind) {
|
||||
for (int skip = -1; skip <= 1; ++skip) {
|
||||
reset_canvas(); skipped_row=skip; draw_kind(kind);
|
||||
for (int y=2; y<9; ++y) {
|
||||
CHECK((vram[y*1024+2] != 0) == (skip < 0 || (y&1) != skip));
|
||||
}
|
||||
int x1,y1,x2,y2; gr_get_draw_area(&x1,&y1,&x2,&y2);
|
||||
CHECK(x1==0 && y1==0 && x2==31 && y2==31);
|
||||
if (kind == 0) {
|
||||
gr_render_display_hires(hires,32*scale*4,0,0,32,32);
|
||||
for (int y=2*scale;y<9*scale;++y)
|
||||
CHECK(((hires[y*32*scale+3*scale]&0xFFFFFF)!=0) ==
|
||||
(skip<0 || ((y/scale)&1)!=skip));
|
||||
}
|
||||
}
|
||||
}
|
||||
reset_canvas(); skipped_row=1;
|
||||
gr_set_draw_area(3,3,8,8); gr_draw_flat_rect(0,0,10,10,0x7FFF);
|
||||
CHECK(vram[4*1024+3]==0x7FFF && vram[3*1024+3]==0 && vram[4*1024+2]==0);
|
||||
/* Fill/copy/upload/readback must retain BOTH fields. */
|
||||
gr_fill_rect(0,0,16,16,0x1234);
|
||||
gr_copy_rect(0,0,16,0,16,16);
|
||||
uint16_t in[4]={1,2,3,4},out[4]={0};
|
||||
gr_vram_transfer_in(0,0,2,2,in); gr_vram_transfer_out(0,0,2,2,out);
|
||||
CHECK(memcmp(in,out,sizeof in)==0 && vram[17]==0x1234 && vram[1024+17]==0x1234);
|
||||
/* A mode switch restores drawing to both fields immediately. */
|
||||
reset_canvas(); skipped_row=0; gr_draw_flat_rect(2,2,1,2,0x7FFF);
|
||||
skipped_row=-1; gr_draw_flat_rect(2,2,1,2,0x4321);
|
||||
CHECK(vram[2*1024+2]==0x4321 && vram[3*1024+2]==0x4321);
|
||||
}
|
||||
/* A single enhanced triangle keeps its metadata through all row clips. */
|
||||
mock_enabled=1;gr_set_backend(GR_BACKEND_OPENGL);skipped_row=0;
|
||||
gr_set_draw_area(0,0,31,31);
|
||||
gr_set_precise_triangle(1,0,0,10*65536,0,0,10*65536);
|
||||
gr_set_perspective_triangle(1,1.0f,0.5f,0.25f);
|
||||
gr_draw_flat_triangle(0,0,10,0,0,10,0x7FFF);
|
||||
CHECK(mock_submissions==16 && mock_bad==0 && !mock_pc && !mock_pq);
|
||||
gr_set_backend(GR_BACKEND_SOFTWARE);
|
||||
if (failures) return 1;
|
||||
puts("PASS: field predicate, all raster primitives, clipping, transfers, mode switch, scales 1/4");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user