|
| 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 | +}; |
0 commit comments