Skip to content

Commit e45e6aa

Browse files
committed
OpenGL / OpenGL ES Tests
1 parent ebc9d79 commit e45e6aa

23 files changed

Lines changed: 501 additions & 0 deletions

File tree

tests/emscripten/glRenderSmokeTest/bin/data/.gitkeep

Whitespace-only changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../gl/glRenderSmokeTest/src/GLSmokeTestCore.h
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../gl/glRenderSmokeTest/src/main.cpp
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../gl/glRenderSmokeTest/src/ofApp.cpp
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../gl/glRenderSmokeTest/src/ofApp.h
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# GL render smoke test
2+
3+
Exercises the code paths touched by the OpenGL ES 3.0/3.1 support work:
4+
an indexed `ofVboMesh` drawn both directly (`drawElements`) and instanced
5+
(`drawElementsInstanced`), a tessellated `ofPath` (tess2 / `ofIndexType`),
6+
and an `ofFbo` round trip with a pixel readback to catch garbage/blank
7+
triangles caused by an index-type/size mismatch.
8+
9+
The actual test logic lives in `src/GLSmokeTestCore.h` and is shared,
10+
unmodified, by every platform target (desktop here, `tests/emscripten/glRenderSmokeTest`,
11+
`tests/ios/glRenderSmokeTest`) — only the platform bridge (`ofApp`/`main`)
12+
differs.
13+
14+
On desktop this is a normal windowed openFrameworks app picked up
15+
automatically by the existing `tests/` CI harness (`scripts/ci/*/run_tests.sh`):
16+
it runs a fixed number of frames, logs `GL_SMOKE_TEST RESULT=PASS` or
17+
`RESULT=FAIL reason=...`, and exits with a matching process exit code.
18+
19+
To run manually:
20+
21+
```sh
22+
cd tests/gl/glRenderSmokeTest
23+
cp ../../../scripts/templates/osx/Makefile . # or linux/linux64 template
24+
cp ../../../scripts/templates/osx/config.make .
25+
make Debug
26+
make RunDebug
27+
```

tests/gl/glRenderSmokeTest/bin/data/.gitkeep

Whitespace-only changes.
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
#pragma once
2+
3+
#include "ofMain.h"
4+
5+
// Shared by every platform target of glRenderSmokeTest (desktop, Emscripten,
6+
// iOS, Android). Exercises the code paths touched by the OpenGL ES 3.0/3.1
7+
// support work: indexed ofVboMesh draw + drawInstanced (drawElements /
8+
// drawElementsInstanced), a tessellated ofPath (tess2 / ofIndexType), and an
9+
// ofFbo round trip, on whatever GL/GLES context the host window created.
10+
//
11+
// No custom shader is used deliberately: the GLSL version/#define story
12+
// differs per platform (desktop core profile vs GLES2 vs GLES3 "300 es" vs
13+
// Emscripten) and isn't what this PR touches. Using the renderer's own
14+
// default shader keeps the test focused on the index-type and tess2 paths
15+
// while still exercising the ES-version #ifdef branches in
16+
// ofGLProgrammableRenderer indirectly (every draw call goes through them).
17+
class GLSmokeTestCore {
18+
public:
19+
static constexpr int framesToRun = 12;
20+
21+
void allocate() {
22+
release();
23+
24+
indexedMesh.setMode(OF_PRIMITIVE_TRIANGLES);
25+
indexedMesh.addVertices({
26+
{-60.f, -60.f, 0.f}, {60.f, -60.f, 0.f},
27+
{60.f, 60.f, 0.f}, {-60.f, 60.f, 0.f}
28+
});
29+
indexedMesh.addColors({ofColor::red, ofColor::green, ofColor::blue, ofColor::yellow});
30+
indexedMesh.addIndices({0, 1, 2, 0, 2, 3});
31+
32+
tessellatedPath.clear();
33+
tessellatedPath.setFilled(true);
34+
tessellatedPath.setFillColor(ofColor(255, 140, 20));
35+
tessellatedPath.setPolyWindingMode(OF_POLY_WINDING_ODD);
36+
for (int i = 0; i < 12; ++i) {
37+
const float radius = (i % 2 == 0) ? 55.f : 22.f;
38+
const float angle = ofDegToRad(i * 30.f - 90.f);
39+
const glm::vec2 point(std::cos(angle) * radius, std::sin(angle) * radius);
40+
if (i == 0) {
41+
tessellatedPath.moveTo(point);
42+
} else {
43+
tessellatedPath.lineTo(point);
44+
}
45+
}
46+
tessellatedPath.close();
47+
48+
ofFbo::Settings settings;
49+
settings.width = 128;
50+
settings.height = 128;
51+
settings.internalformat = GL_RGBA;
52+
settings.useDepth = false;
53+
settings.numSamples = 0;
54+
fbo.allocate(settings);
55+
56+
allocated = fbo.isAllocated();
57+
if (!allocated) {
58+
fail("ofFbo failed to allocate");
59+
}
60+
}
61+
62+
void release() {
63+
fbo.clear();
64+
allocated = false;
65+
}
66+
67+
void drawFrame() {
68+
if (!allocated) {
69+
fail("resources not allocated");
70+
return;
71+
}
72+
73+
clearGLErrors();
74+
75+
fbo.begin();
76+
ofClear(0, 0, 0, 255);
77+
ofSetColor(255);
78+
ofPushMatrix();
79+
ofTranslate(fbo.getWidth() * 0.5f, fbo.getHeight() * 0.5f);
80+
indexedMesh.draw(); // ofGLProgrammableRenderer::draw(ofVboMesh) -> drawElements
81+
indexedMesh.drawInstanced(OF_MESH_FILL, 2); // forces the drawElementsInstanced path (primCount > 1)
82+
ofPopMatrix();
83+
fbo.end();
84+
checkGLErrors("after mesh draw");
85+
86+
ofSetColor(255);
87+
fbo.draw(0.f, 0.f, static_cast<float>(fbo.getWidth()), static_cast<float>(fbo.getHeight()));
88+
89+
ofPushMatrix();
90+
ofTranslate(180.f, 160.f);
91+
tessellatedPath.draw(); // tess2 triangulation -> ofIndexType-sized indices
92+
ofPopMatrix();
93+
checkGLErrors("after path draw");
94+
95+
if (frame == framesToRun - 1) {
96+
verifyPixels();
97+
}
98+
++frame;
99+
}
100+
101+
bool finished() const {
102+
return frame >= framesToRun;
103+
}
104+
105+
bool passed() const {
106+
return allocated && glErrorCount == 0 && pixelCheckPassed && failureReason.empty();
107+
}
108+
109+
std::string resultLine() const {
110+
if (passed()) {
111+
return "GL_SMOKE_TEST RESULT=PASS frames=" + ofToString(frame);
112+
}
113+
std::string reason = failureReason;
114+
if (reason.empty() && glErrorCount > 0) {
115+
reason = "glGetError seen " + ofToString(glErrorCount) + " time(s), last=" + ofToHex(lastGLError);
116+
}
117+
if (reason.empty() && !pixelCheckPassed) {
118+
reason = "FBO readback did not contain the expected non-background color";
119+
}
120+
return "GL_SMOKE_TEST RESULT=FAIL reason=" + reason;
121+
}
122+
123+
private:
124+
void fail(const std::string & reason) {
125+
if (failureReason.empty()) {
126+
failureReason = reason;
127+
}
128+
ofLogError("glRenderSmokeTest") << reason;
129+
}
130+
131+
void clearGLErrors() {
132+
while (glGetError() != GL_NO_ERROR) {
133+
}
134+
}
135+
136+
void checkGLErrors(const std::string & where) {
137+
GLenum error = glGetError();
138+
while (error != GL_NO_ERROR) {
139+
++glErrorCount;
140+
lastGLError = error;
141+
ofLogError("glRenderSmokeTest") << "glGetError=" << ofToHex(error) << " " << where;
142+
error = glGetError();
143+
}
144+
}
145+
146+
void verifyPixels() {
147+
ofPixels pixels;
148+
fbo.readToPixels(pixels);
149+
if (!pixels.isAllocated()) {
150+
fail("FBO readToPixels produced no pixels");
151+
return;
152+
}
153+
const ofColor center = pixels.getColor(pixels.getWidth() / 2, pixels.getHeight() / 2);
154+
// Background is cleared to black; any of the mesh's red/green/blue/yellow
155+
// vertex colors interpolating in means the indexed draw actually reached
156+
// the GPU with correctly-sized indices instead of reading garbage/nothing.
157+
pixelCheckPassed = (center.r > 10 || center.g > 10 || center.b > 10);
158+
if (!pixelCheckPassed) {
159+
ofLogError("glRenderSmokeTest") << "center pixel=" << center << " (expected non-background)";
160+
}
161+
}
162+
163+
ofVboMesh indexedMesh;
164+
ofPath tessellatedPath;
165+
ofFbo fbo;
166+
167+
bool allocated = false;
168+
bool pixelCheckPassed = false;
169+
int frame = 0;
170+
int glErrorCount = 0;
171+
GLenum lastGLError = GL_NO_ERROR;
172+
std::string failureReason;
173+
};
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#include "ofMain.h"
2+
#include "ofApp.h"
3+
4+
int main() {
5+
ofGLWindowSettings settings;
6+
settings.setSize(320, 240);
7+
settings.windowMode = OF_WINDOW;
8+
9+
auto window = ofCreateWindow(settings);
10+
11+
ofRunApp(window, std::make_shared<ofApp>());
12+
return ofRunMainLoop();
13+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#include "ofApp.h"
2+
3+
#include <cstdlib>
4+
#ifdef __EMSCRIPTEN__
5+
#include <emscripten.h>
6+
#endif
7+
8+
namespace {
9+
// std::exit() alone doesn't stop Emscripten's requestAnimationFrame loop
10+
// (the runtime stays alive for pending async work), so draw() keeps getting
11+
// called forever after "finishing" unless we guard against it below, and the
12+
// process itself needs emscripten_force_exit() to actually stop.
13+
void terminateProcess(int code) {
14+
#ifdef __EMSCRIPTEN__
15+
emscripten_force_exit(code);
16+
#else
17+
std::exit(code);
18+
#endif
19+
}
20+
}
21+
22+
void ofApp::setup() {
23+
ofSetLogLevel(OF_LOG_NOTICE);
24+
ofSetFrameRate(60);
25+
ofBackground(0);
26+
ofLogNotice("glRenderSmokeTest") << "renderer=" << glGetString(GL_RENDERER)
27+
<< " version=" << glGetString(GL_VERSION);
28+
core.allocate();
29+
}
30+
31+
void ofApp::draw() {
32+
if (core.finished()) {
33+
return;
34+
}
35+
36+
core.drawFrame();
37+
38+
if (core.finished()) {
39+
ofLogNotice("glRenderSmokeTest") << core.resultLine();
40+
terminateProcess(core.passed() ? 0 : 1);
41+
}
42+
}

0 commit comments

Comments
 (0)