GLdc/tests/test_golden_rendering.h
2026-06-22 09:05:44 +01:00

244 lines
9.1 KiB
C++

#pragma once
#include "tools/test.h"
#include "tools/gl_test.h"
#include "tools/golden.h"
#include <stdint.h>
#include <GL/gl.h>
#include <GL/glkos.h>
#include "GL/private.h"
/* =========================================================================
* GoldenRenderingTests
*
* End-to-end "golden image" tests. Each test issues real GL draw calls, then
* rasterises the resulting TA poly-lists with the deterministic CPU rasteriser
* in tools/golden.h and compares the output to a committed PPM reference under
* tests/goldens/.
*
* These guard the whole submission pipeline — vertex transform, viewport
* mapping, colour/UV handling, primitive assembly, list routing (opaque vs
* transparent) and texture sampling — against regressions.
*
* To (re)generate the references run the test binary with GLDC_UPDATE_GOLDENS=1.
* =========================================================================*/
class GoldenRenderingTests : public GLTestCase {
public:
/* The video mode is 640x480 and the viewport y-flip is relative to that
* height, so to capture a WxH image at the top-left of the framebuffer we
* place the viewport at (0, 480 - H). */
static const int VIDEO_H = 480;
static const int W = 96;
static const int H = 96;
void set_up() {
GLTestCase::set_up();
glViewport(0, VIDEO_H - H, W, H);
}
/* A single white triangle on a black background. */
void test_solid_white_triangle() {
golden::Image img(W, H);
img.clear(0, 0, 0);
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
glBegin(GL_TRIANGLES);
glVertex3f(-0.8f, -0.8f, 0.0f);
glVertex3f( 0.8f, -0.8f, 0.0f);
glVertex3f( 0.0f, 0.8f, 0.0f);
glEnd();
golden::rasterize_all_lists(img);
assert_true(golden::check(img, "solid_white_triangle"));
}
/* Gouraud-shaded triangle: red/green/blue corners interpolated. */
void test_gouraud_triangle() {
golden::Image img(W, H);
img.clear(0, 0, 0);
glBegin(GL_TRIANGLES);
glColor4f(1.0f, 0.0f, 0.0f, 1.0f); glVertex3f(-0.8f, -0.8f, 0.0f);
glColor4f(0.0f, 1.0f, 0.0f, 1.0f); glVertex3f( 0.8f, -0.8f, 0.0f);
glColor4f(0.0f, 0.0f, 1.0f, 1.0f); glVertex3f( 0.0f, 0.8f, 0.0f);
glEnd();
golden::rasterize_all_lists(img);
assert_true(golden::check(img, "gouraud_triangle"));
}
/* A full-screen-ish coloured quad built from a triangle strip. */
void test_colored_quad_strip() {
golden::Image img(W, H);
img.clear(16, 16, 16);
glBegin(GL_TRIANGLE_STRIP);
glColor4f(1.0f, 0.0f, 0.0f, 1.0f); glVertex3f(-0.7f, 0.7f, 0.0f);
glColor4f(1.0f, 1.0f, 0.0f, 1.0f); glVertex3f(-0.7f, -0.7f, 0.0f);
glColor4f(0.0f, 1.0f, 0.0f, 1.0f); glVertex3f( 0.7f, 0.7f, 0.0f);
glColor4f(0.0f, 0.0f, 1.0f, 1.0f); glVertex3f( 0.7f, -0.7f, 0.0f);
glEnd();
golden::rasterize_all_lists(img);
assert_true(golden::check(img, "colored_quad_strip"));
}
/* Two overlapping opaque triangles: painter's order must put the second
* (green) on top of the first (red). */
void test_overlapping_opaque_painter_order() {
golden::Image img(W, H);
img.clear(0, 0, 0);
glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
glBegin(GL_TRIANGLES);
glVertex3f(-0.8f, -0.6f, 0.0f);
glVertex3f( 0.4f, -0.6f, 0.0f);
glVertex3f(-0.2f, 0.8f, 0.0f);
glEnd();
glColor4f(0.0f, 1.0f, 0.0f, 1.0f);
glBegin(GL_TRIANGLES);
glVertex3f(-0.4f, -0.8f, 0.0f);
glVertex3f( 0.8f, -0.8f, 0.0f);
glVertex3f( 0.2f, 0.6f, 0.0f);
glEnd();
golden::rasterize_all_lists(img);
assert_true(golden::check(img, "overlapping_opaque"));
}
/* A half-transparent quad over an opaque red triangle: routed to the
* transparent list and alpha-blended by the rasteriser. */
void test_transparent_over_opaque() {
golden::Image img(W, H);
img.clear(0, 0, 0);
/* Opaque red triangle (OP_LIST). */
glDisable(GL_BLEND);
glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
glBegin(GL_TRIANGLES);
glVertex3f(-0.8f, -0.8f, 0.0f);
glVertex3f( 0.8f, -0.8f, 0.0f);
glVertex3f( 0.0f, 0.8f, 0.0f);
glEnd();
/* 50% blue quad on top (TR_LIST). */
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glColor4f(0.0f, 0.0f, 1.0f, 0.5f);
glBegin(GL_TRIANGLE_STRIP);
glVertex3f(-0.5f, 0.5f, 0.0f);
glVertex3f(-0.5f, -0.5f, 0.0f);
glVertex3f( 0.5f, 0.5f, 0.0f);
glVertex3f( 0.5f, -0.5f, 0.0f);
glEnd();
golden::rasterize_all_lists(img);
assert_true(golden::check(img, "transparent_over_opaque"));
}
/* A Gouraud quad coloured via a GL_BGRA GL_UNSIGNED_BYTE array (OpenGL 1.4),
* blended over a coloured background. This exercises the whole BGRA path end
* to end: the red/blue channel swap must land the right colour at each
* corner, and the per-vertex alpha must drive the blend against the
* background. If BGRA decoded as RGBA, or alpha were ignored, the rendered
* output would differ from the golden. */
void test_bgra_vertex_colors_with_alpha() {
golden::Image img(W, H);
img.clear(40, 40, 40); /* dark grey so blending is visible */
GLfloat verts[] = {
-0.8f, 0.8f, 0.0f,
-0.8f, -0.8f, 0.0f,
0.8f, 0.8f, 0.0f,
0.8f, -0.8f, 0.0f,
};
/* GL_BGRA memory order is B, G, R, A -> colour R, G, B, A.
* Distinct corners with decreasing alpha down/right. */
GLubyte colors[] = {
/* B G R A -> colour */
0, 0, 255, 255, /* opaque red */
0, 255, 0, 192, /* green, ~75% alpha */
255, 0, 0, 128, /* blue, 50% alpha */
0, 255, 255, 64, /* yellow, 25% alpha */
};
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glVertexPointer(3, GL_FLOAT, 0, verts);
glColorPointer(GL_BGRA, GL_UNSIGNED_BYTE, 0, colors);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
golden::rasterize_all_lists(img);
assert_true(golden::check(img, "bgra_vertex_colors_alpha"));
}
/* A textured quad. A 2x2 RGB texture (red/green/blue/white) is mapped over
* a quad; nearest filtering gives four solid colour cells. */
void test_textured_quad() {
golden::Image img(W, H);
img.clear(0, 0, 0);
/* 8x8 (the PVR minimum) split into four 4x4 colour quadrants:
* red / green / blue / white. */
GLubyte texels[8 * 8 * 3];
for(int y = 0; y < 8; ++y) {
for(int x = 0; x < 8; ++x) {
GLubyte r = 0, g = 0, b = 0;
if(y < 4 && x < 4) { r = 255; } /* top-left red */
else if(y < 4) { g = 255; } /* top-right green */
else if(x < 4) { b = 255; } /* bottom-left blue */
else { r = g = b = 255; } /* bottom-right white */
int i = (y * 8 + x) * 3;
texels[i + 0] = r; texels[i + 1] = g; texels[i + 2] = b;
}
}
GLuint tex = 0;
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 8, 8, 0, GL_RGB, GL_UNSIGNED_BYTE, texels);
assert_equal(glGetError(), GL_NO_ERROR);
glEnable(GL_TEXTURE_2D);
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
glBegin(GL_TRIANGLE_STRIP);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-0.8f, 0.8f, 0.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-0.8f, -0.8f, 0.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f( 0.8f, 0.8f, 0.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f( 0.8f, -0.8f, 0.0f);
glEnd();
golden::rasterize_all_lists(img, _glGetBoundTexture());
assert_true(golden::check(img, "textured_quad"));
glDeleteTextures(1, &tex);
}
/* The same triangle drawn with a coloured background must leave the
* background untouched outside the triangle (clear-colour preservation). */
void test_triangle_preserves_background() {
golden::Image img(W, H);
img.clear(40, 80, 120);
glColor4f(1.0f, 1.0f, 0.0f, 1.0f);
glBegin(GL_TRIANGLES);
glVertex3f(-0.5f, -0.5f, 0.0f);
glVertex3f( 0.5f, -0.5f, 0.0f);
glVertex3f( 0.0f, 0.5f, 0.0f);
glEnd();
golden::rasterize_all_lists(img);
assert_true(golden::check(img, "triangle_on_background"));
}
};