diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index 24046b14a2e..90b356db721 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -106,6 +106,9 @@ jobs: - name: Run text visual suite run: xvfb-run -a ctest --test-dir cmake_build_dir_rt \ -L visual-text --output-on-failure -VV + - name: Run DrawList raster visual tests + run: xvfb-run -a ctest --test-dir cmake_build_dir_rt \ + -L visual-raster --output-on-failure -VV - name: Upload artifacts uses: actions/upload-artifact@v7 with: diff --git a/CMakeLists.txt b/CMakeLists.txt index 58daf8a3887..e059aa1c3a1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -965,6 +965,8 @@ if(COIN_BUILD_VISUAL_TESTS) "unlit_cube_basic,unlit_transformed_cubes,unlit_vertex_colors,unlit_orthographic_basic,unlit_depth_overlap") set(COIN_MATERIAL_VISUAL_SPECS "cube_basic,camera_persp_basic,camera_ortho_basic,material_emissive,two_lights,point_spot_lights_basic,texture_rgb,texture_luminance_alpha,transparent_overlap,depth_buffer_basic,alpha_test_basic,transparency_depth_basic,transparency_order_basic,specular_shininess_basic") + set(COIN_RASTER_VISUAL_SPECS + "raster_wide_line_basic,raster_line_pattern_basic,raster_points_basic,raster_wireframe_basic,image_basic") set(COIN_TEXT_VISUAL_SPECS "text2_layout_basic") add_test(NAME visual_smoke COMMAND CoinVisualTests snapshot --help) @@ -1022,6 +1024,46 @@ if(COIN_BUILD_VISUAL_TESTS) ) set_tests_properties(visual_gl_drawlist_material_core PROPERTIES LABELS "visual-material") + add_test( + NAME visual_gl_drawlist_text_compat + COMMAND CoinVisualTests run + --renderer drawlist + --gl-profile compat + --only ${COIN_TEXT_VISUAL_SPECS} + --artifacts-dir ${CMAKE_BINARY_DIR}/render_artifacts + ) + set_tests_properties(visual_gl_drawlist_text_compat + PROPERTIES LABELS "visual-text") + add_test( + NAME visual_gl_drawlist_text_core + COMMAND CoinVisualTests run + --renderer drawlist + --gl-profile core + --only ${COIN_TEXT_VISUAL_SPECS} + --artifacts-dir ${CMAKE_BINARY_DIR}/render_artifacts + ) + set_tests_properties(visual_gl_drawlist_text_core + PROPERTIES LABELS "visual-text") + add_test( + NAME visual_gl_drawlist_raster_compat + COMMAND CoinVisualTests run + --renderer drawlist + --gl-profile compat + --only ${COIN_RASTER_VISUAL_SPECS} + --artifacts-dir ${CMAKE_BINARY_DIR}/render_artifacts + ) + set_tests_properties(visual_gl_drawlist_raster_compat + PROPERTIES LABELS "visual-raster") + add_test( + NAME visual_gl_drawlist_raster_core + COMMAND CoinVisualTests run + --renderer drawlist + --gl-profile core + --only ${COIN_RASTER_VISUAL_SPECS} + --artifacts-dir ${CMAKE_BINARY_DIR}/render_artifacts + ) + set_tests_properties(visual_gl_drawlist_raster_core + PROPERTIES LABELS "visual-raster") add_test( NAME visual_gl_invalid_legacy_profile COMMAND CoinVisualTests run diff --git a/data/CMakeLists.txt b/data/CMakeLists.txt index 17315fd9a03..1ae4ebcfb09 100644 --- a/data/CMakeLists.txt +++ b/data/CMakeLists.txt @@ -55,6 +55,16 @@ endmacro() # embedded as independent C++ resources. coin_embed_gl_shader(gl_visual_vertex data/shaders/gl/visual/Vertex.glsl) coin_embed_gl_shader(gl_visual_fragment data/shaders/gl/visual/Fragment.glsl) +coin_embed_gl_shader(gl_wide_line_vertex data/shaders/gl/wide-line/Vertex.glsl) +coin_embed_gl_shader(gl_wide_line_geometry data/shaders/gl/wide-line/Geometry.glsl) +coin_embed_gl_shader(gl_wide_line_triangle_geometry data/shaders/gl/wide-line/TriangleGeometry.glsl) +coin_embed_gl_shader(gl_wide_line_fragment data/shaders/gl/wide-line/Fragment.glsl) +coin_embed_gl_shader(gl_point_vertex data/shaders/gl/point/Vertex.glsl) +coin_embed_gl_shader(gl_point_geometry data/shaders/gl/point/Geometry.glsl) +coin_embed_gl_shader(gl_point_triangle_geometry data/shaders/gl/point/TriangleGeometry.glsl) +coin_embed_gl_shader(gl_point_fragment data/shaders/gl/point/Fragment.glsl) +coin_embed_gl_shader(gl_pixel_vertex data/shaders/gl/pixel/Vertex.glsl) +coin_embed_gl_shader(gl_pixel_fragment data/shaders/gl/pixel/Fragment.glsl) # These are legacy shader snippets exposed through Coin's built-in shader # dictionary. They are ordinary embedded text, not core-profile root diff --git a/data/shaders/gl/pixel/Common.glsl b/data/shaders/gl/pixel/Common.glsl new file mode 100644 index 00000000000..47917df8f11 --- /dev/null +++ b/data/shaders/gl/pixel/Common.glsl @@ -0,0 +1,36 @@ +/* + * Pixel-raster coordinate and source-sampling helpers. + * + * Pixel producers provide final RGBA. The destination raster footprint and + * source texture size are independent, so scaled images map destination + * pixels back to source texels here. Inherited material and scene-texture + * state must not modulate the sampled pixel. + */ + +vec2 coin_pixel_raster_coordinate(vec2 fragCoord, + vec2 viewportOrigin, + vec2 pixelOrigin) +{ + return floor(fragCoord - viewportOrigin - pixelOrigin); +} + +bool coin_pixel_in_raster(vec2 rasterPixel, vec2 rasterSize) +{ + return rasterPixel.x >= 0.0 && rasterPixel.y >= 0.0 && + rasterPixel.x < rasterSize.x && rasterPixel.y < rasterSize.y; +} + +ivec2 coin_pixel_source_coordinate(vec2 rasterPixel, + vec2 sourceSize, + vec2 rasterSize, + ivec2 textureSizePixels) +{ + ivec2 sourcePixel = ivec2(floor(rasterPixel * sourceSize / + max(rasterSize, vec2(1.0)))); + return clamp(sourcePixel, ivec2(0), textureSizePixels - ivec2(1)); +} + +vec4 coin_pixel_fetch(sampler2D textureSampler, ivec2 pixel) +{ + return texelFetch(textureSampler, pixel, 0); +} diff --git a/data/shaders/gl/pixel/Fragment.glsl b/data/shaders/gl/pixel/Fragment.glsl new file mode 100644 index 00000000000..76dc6673fb8 --- /dev/null +++ b/data/shaders/gl/pixel/Fragment.glsl @@ -0,0 +1,33 @@ +#version 330 core + +/* Pixel fragment root. Pixel texels are final producer RGBA; only explicit + * pixel alpha-test state determines whether a covered fragment survives. */ +uniform sampler2D u_texture; +uniform int u_alphaTestFunction; +uniform float u_alphaTestReference; +uniform vec2 u_sourceSize; +uniform vec2 u_rasterSize; +uniform vec2 u_pixelOrigin; +uniform vec2 u_viewportOrigin; + +out vec4 fragColor; + +#include "Common.glsl" +#include "../material/AlphaTest.glsl" + +void main() +{ + vec2 rasterPixel = coin_pixel_raster_coordinate( + gl_FragCoord.xy, u_viewportOrigin, u_pixelOrigin); + if (!coin_pixel_in_raster(rasterPixel, u_rasterSize)) { + discard; + } + ivec2 sourcePixel = coin_pixel_source_coordinate( + rasterPixel, u_sourceSize, u_rasterSize, textureSize(u_texture, 0)); + vec4 texel = coin_pixel_fetch(u_texture, sourcePixel); + // Pixel producers retain final RGBA. Unlike ordinary surface commands, + // image/text pixels must not be modulated by inherited material state. + if (!coin_material_alpha_test_pass(texel.a, u_alphaTestFunction, + u_alphaTestReference)) discard; + fragColor = texel; +} diff --git a/data/shaders/gl/pixel/Vertex.glsl b/data/shaders/gl/pixel/Vertex.glsl new file mode 100644 index 00000000000..b4fb384766f --- /dev/null +++ b/data/shaders/gl/pixel/Vertex.glsl @@ -0,0 +1,25 @@ +#version 330 core + +/* Pixel vertex root. It places a producer-supplied screen-space raster in + * the active viewport; source-texture lookup belongs to pixel/Common.glsl. */ +layout(location = 0) in vec3 a_position; +layout(location = 3) in vec2 a_texcoord; + +uniform mat4 u_proj; +uniform mat4 u_view; +uniform mat4 u_model; +uniform vec3 u_quadCenter; +uniform vec2 u_rasterSize; +uniform vec2 u_vpSize; +uniform vec2 u_pixelOrigin; + +void main() +{ + vec2 pixelPosition = u_pixelOrigin + a_texcoord * u_rasterSize; + // pixelOrigin is retained relative to the active viewport. Convert that + // local position directly to NDC; the viewport origin is supplied to the + // fragment shader only to translate gl_FragCoord back into the same space. + vec2 ndcPosition = 2.0 * pixelPosition / u_vpSize - 1.0; + vec4 centerClip = u_proj * u_view * u_model * vec4(u_quadCenter, 1.0); + gl_Position = vec4(ndcPosition * centerClip.w, centerClip.z, centerClip.w); +} diff --git a/data/shaders/gl/point/Fragment.glsl b/data/shaders/gl/point/Fragment.glsl new file mode 100644 index 00000000000..2effca1d1ea --- /dev/null +++ b/data/shaders/gl/point/Fragment.glsl @@ -0,0 +1,18 @@ +#version 330 core + +/* Point fragment root: generated point coverage shares surface evaluation + * with the ordinary visual pipeline and keeps alpha testing explicit. */ +#include "../material/FragmentEvaluation.glsl" + +in vec4 v_color; +in vec3 v_litColor; +in vec2 v_texcoord; +out vec4 fragColor; + +void main() +{ + vec4 color = coin_surface_fragment_color(v_color, v_litColor, v_texcoord); + if (!coin_material_alpha_test_pass(color.a, u_alphaTestFunction, + u_alphaTestReference)) discard; + fragColor = color; +} diff --git a/data/shaders/gl/point/Geometry.glsl b/data/shaders/gl/point/Geometry.glsl new file mode 100644 index 00000000000..752ebf1edba --- /dev/null +++ b/data/shaders/gl/point/Geometry.glsl @@ -0,0 +1,38 @@ +#version 330 core + +/* Expands points into viewport-sized coverage quads when native point size + * is insufficient. The generated triangles are implementation detail; the + * input point remains the semantic primitive. */ +layout(points) in; +layout(triangle_strip, max_vertices = 4) out; + +uniform vec2 u_vpSize; +uniform float u_pointSize; + +in vec4 vs_color[]; +in vec3 vs_litColor[]; +in vec2 vs_texcoord[]; +out vec4 v_color; +out vec3 v_litColor; +out vec2 v_texcoord; + +void coin_emit_point_corner(vec2 uv) +{ + vec4 center = gl_in[0].gl_Position; + vec2 pixelOffset = (uv - vec2(0.5)) * u_pointSize; + vec2 ndcOffset = 2.0 * pixelOffset / u_vpSize; + gl_Position = center + vec4(ndcOffset * center.w, 0.0, 0.0); + v_color = vs_color[0]; + v_litColor = vs_litColor[0]; + v_texcoord = vs_texcoord[0]; + EmitVertex(); +} + +void main() +{ + coin_emit_point_corner(vec2(0.0, 1.0)); + coin_emit_point_corner(vec2(0.0, 0.0)); + coin_emit_point_corner(vec2(1.0, 1.0)); + coin_emit_point_corner(vec2(1.0, 0.0)); + EndPrimitive(); +} diff --git a/data/shaders/gl/point/TriangleGeometry.glsl b/data/shaders/gl/point/TriangleGeometry.glsl new file mode 100644 index 00000000000..33e51516704 --- /dev/null +++ b/data/shaders/gl/point/TriangleGeometry.glsl @@ -0,0 +1,47 @@ +#version 330 core + +/* Expands triangle point coverage into one quad per source vertex after + * source-facing and strip-parity decisions. */ +layout(triangles) in; +layout(triangle_strip, max_vertices = 12) out; + +uniform vec2 u_vpSize; +uniform float u_pointSize; + +#include "../visual/TriangleFallback.glsl" + +in vec4 vs_color[]; +in vec3 vs_litColor[]; +in vec2 vs_texcoord[]; +out vec4 v_color; +out vec3 v_litColor; +out vec2 v_texcoord; + +void coin_emit_point_corner(int index, vec2 uv) +{ + vec4 center = gl_in[index].gl_Position; + vec2 pixelOffset = (uv - vec2(0.5)) * u_pointSize; + vec2 ndcOffset = 2.0 * pixelOffset / u_vpSize; + gl_Position = center + vec4(ndcOffset * center.w, 0.0, 0.0); + v_color = vs_color[index]; + v_litColor = vs_litColor[index]; + v_texcoord = vs_texcoord[index]; + EmitVertex(); +} + +void coin_emit_point(int index) +{ + coin_emit_point_corner(index, vec2(0.0, 1.0)); + coin_emit_point_corner(index, vec2(0.0, 0.0)); + coin_emit_point_corner(index, vec2(1.0, 1.0)); + coin_emit_point_corner(index, vec2(1.0, 0.0)); + EndPrimitive(); +} + +void main() +{ + if (coin_triangle_is_culled()) return; + coin_emit_point(0); + coin_emit_point(1); + coin_emit_point(2); +} diff --git a/data/shaders/gl/point/Vertex.glsl b/data/shaders/gl/point/Vertex.glsl new file mode 100644 index 00000000000..f1b28d5892a --- /dev/null +++ b/data/shaders/gl/point/Vertex.glsl @@ -0,0 +1,25 @@ +#version 330 core + +#include "../material/VertexEvaluation.glsl" + +layout(location = 0) in vec3 a_position; +layout(location = 1) in vec3 a_normal; +layout(location = 2) in vec4 a_color; +layout(location = 3) in vec2 a_texcoord; + + +out vec4 vs_color; +out vec3 vs_litColor; +out vec2 vs_texcoord; + +void main() +{ + vs_color = coin_surface_vertex_color(a_color); + vs_texcoord = a_texcoord; + vec4 worldPos = u_model * vec4(a_position, 1.0); + vec4 eyePos = u_view * worldPos; + mat3 normalMatrix = transpose(inverse(mat3(u_view * u_model))); + vec3 eyeNormal = normalMatrix * a_normal; + vs_litColor = coin_surface_lit_color(vs_color, eyePos.xyz, eyeNormal); + gl_Position = u_proj * eyePos; +} diff --git a/data/shaders/gl/visual/TriangleFallback.glsl b/data/shaders/gl/visual/TriangleFallback.glsl new file mode 100644 index 00000000000..1c510826ca1 --- /dev/null +++ b/data/shaders/gl/visual/TriangleFallback.glsl @@ -0,0 +1,22 @@ +// Shared state for triangle-to-line/point emulation. The geometry shader +// must apply culling to the source triangle before it emits coverage quads; +// culling those generated quads would not reproduce polygon culling. + +uniform float u_cullBackFaces; +uniform float u_frontFaceCCW; + +bool coin_triangle_front_facing() +{ + vec2 p0 = gl_in[0].gl_Position.xy / gl_in[0].gl_Position.w; + vec2 p1 = gl_in[1].gl_Position.xy / gl_in[1].gl_Position.w; + vec2 p2 = gl_in[2].gl_Position.xy / gl_in[2].gl_Position.w; + vec2 d0 = p1 - p0; + vec2 d1 = p2 - p0; + bool ccw = d0.x * d1.y - d0.y * d1.x > 0.0; + return u_frontFaceCCW > 0.5 ? ccw : !ccw; +} + +bool coin_triangle_is_culled() +{ + return u_cullBackFaces > 0.5 && !coin_triangle_front_facing(); +} diff --git a/data/shaders/gl/wide-line/Fragment.glsl b/data/shaders/gl/wide-line/Fragment.glsl new file mode 100644 index 00000000000..c8cdbe06bf8 --- /dev/null +++ b/data/shaders/gl/wide-line/Fragment.glsl @@ -0,0 +1,26 @@ +#version 330 core + +/* Wide-line fragment root: stipple coverage first, shared surface + * evaluation second, explicit alpha testing last. */ +#include "../material/FragmentEvaluation.glsl" +uniform int u_stipplePattern; +uniform float u_stippleScale; + +in vec4 v_color; +in vec3 v_litColor; +in vec2 v_texcoord; +noperspective in float v_lineDistance; + +out vec4 fragColor; + +void main() +{ + int bit = int(floor(max(v_lineDistance, 0.0) / + max(u_stippleScale, 1.0))) & 15; + if (((u_stipplePattern >> bit) & 1) == 0) discard; + + vec4 color = coin_surface_fragment_color(v_color, v_litColor, v_texcoord); + if (!coin_material_alpha_test_pass(color.a, u_alphaTestFunction, + u_alphaTestReference)) discard; + fragColor = color; +} diff --git a/data/shaders/gl/wide-line/Geometry.glsl b/data/shaders/gl/wide-line/Geometry.glsl new file mode 100644 index 00000000000..34c68b9dfa3 --- /dev/null +++ b/data/shaders/gl/wide-line/Geometry.glsl @@ -0,0 +1,49 @@ +#version 330 core + +/* Expands retained lines into raster triangles when native line width is + * insufficient. Line distance is carried per source occurrence so indexed + * repeated vertices retain independent stipple progression. */ +layout(lines) in; +layout(triangle_strip, max_vertices = 4) out; + +uniform vec2 u_vpSize; +uniform float u_lineWidth; + +in vec4 vs_color[]; +in vec3 vs_litColor[]; +in vec2 vs_texcoord[]; +noperspective in float vs_lineDistance[]; + +out vec4 v_color; +out vec3 v_litColor; +out vec2 v_texcoord; +noperspective out float v_lineDistance; + +void coin_emit_line_vertex(int index, vec2 offset) +{ + vec4 position = gl_in[index].gl_Position; + v_color = vs_color[index]; + v_litColor = vs_litColor[index]; + v_texcoord = vs_texcoord[index]; + v_lineDistance = vs_lineDistance[index]; + gl_Position = position + vec4(offset * position.w, 0.0, 0.0); + EmitVertex(); +} + +void main() +{ + vec4 p0 = gl_in[0].gl_Position; + vec4 p1 = gl_in[1].gl_Position; + vec2 ndc0 = p0.xy / p0.w; + vec2 ndc1 = p1.xy / p1.w; + vec2 delta = ndc1 - ndc0; + vec2 dir = length(delta) > 0.000001 ? normalize(delta) : vec2(1.0, 0.0); + vec2 perp = vec2(-dir.y, dir.x); + vec2 offset = perp * u_lineWidth / u_vpSize; + + coin_emit_line_vertex(0, offset); + coin_emit_line_vertex(0, -offset); + coin_emit_line_vertex(1, offset); + coin_emit_line_vertex(1, -offset); + EndPrimitive(); +} diff --git a/data/shaders/gl/wide-line/TriangleGeometry.glsl b/data/shaders/gl/wide-line/TriangleGeometry.glsl new file mode 100644 index 00000000000..6b486b9eeb4 --- /dev/null +++ b/data/shaders/gl/wide-line/TriangleGeometry.glsl @@ -0,0 +1,57 @@ +#version 330 core + +/* Expands triangle wireframe edges into independent coverage quads. Source + * facing is decided before emission and each edge computes its own pattern + * distance. */ +layout(triangles) in; +layout(triangle_strip, max_vertices = 12) out; + +uniform vec2 u_vpSize; +uniform float u_lineWidth; + +#include "../visual/TriangleFallback.glsl" + +in vec4 vs_color[]; +in vec3 vs_litColor[]; +in vec2 vs_texcoord[]; +noperspective in float vs_lineDistance[]; +out vec4 v_color; +out vec3 v_litColor; +out vec2 v_texcoord; +noperspective out float v_lineDistance; + +void coin_emit_edge_vertex(int index, vec2 offset, float lineDistance) +{ + vec4 position = gl_in[index].gl_Position; + v_color = vs_color[index]; + v_litColor = vs_litColor[index]; + v_texcoord = vs_texcoord[index]; + v_lineDistance = lineDistance; + gl_Position = position + vec4(offset * position.w, 0.0, 0.0); + EmitVertex(); +} + +void coin_emit_edge(int first, int second) +{ + vec4 p0 = gl_in[first].gl_Position; + vec4 p1 = gl_in[second].gl_Position; + vec2 ndc0 = p0.xy / p0.w; + vec2 ndc1 = p1.xy / p1.w; + vec2 delta = ndc1 - ndc0; + float edgeLength = length(delta * (0.5 * u_vpSize)); + vec2 dir = length(delta) > 0.000001 ? normalize(delta) : vec2(1.0, 0.0); + vec2 offset = vec2(-dir.y, dir.x) * u_lineWidth / u_vpSize; + coin_emit_edge_vertex(first, offset, 0.0); + coin_emit_edge_vertex(first, -offset, 0.0); + coin_emit_edge_vertex(second, offset, edgeLength); + coin_emit_edge_vertex(second, -offset, edgeLength); + EndPrimitive(); +} + +void main() +{ + if (coin_triangle_is_culled()) return; + coin_emit_edge(0, 1); + coin_emit_edge(1, 2); + coin_emit_edge(2, 0); +} diff --git a/data/shaders/gl/wide-line/Vertex.glsl b/data/shaders/gl/wide-line/Vertex.glsl new file mode 100644 index 00000000000..d3bf26a2181 --- /dev/null +++ b/data/shaders/gl/wide-line/Vertex.glsl @@ -0,0 +1,29 @@ +#version 330 core + +#include "../material/VertexEvaluation.glsl" + +layout(location = 0) in vec3 a_position; +layout(location = 1) in vec3 a_normal; +layout(location = 2) in vec4 a_color; +layout(location = 3) in vec2 a_texcoord; +layout(location = 4) in float a_lineDistance; + + +out vec4 vs_color; +out vec3 vs_litColor; +out vec2 vs_texcoord; +noperspective out float vs_lineDistance; + +void main() +{ + vs_color = coin_surface_vertex_color(a_color); + vs_texcoord = a_texcoord; + vs_lineDistance = a_lineDistance; + + vec4 worldPos = u_model * vec4(a_position, 1.0); + vec4 eyePos = u_view * worldPos; + mat3 normalMatrix = transpose(inverse(mat3(u_view * u_model))); + vec3 eyeNormal = normalMatrix * a_normal; + vs_litColor = coin_surface_lit_color(vs_color, eyePos.xyz, eyeNormal); + gl_Position = u_proj * eyePos; +} diff --git a/include/Inventor/nodes/SoImage.h b/include/Inventor/nodes/SoImage.h index f540bf68c99..62a67793930 100644 --- a/include/Inventor/nodes/SoImage.h +++ b/include/Inventor/nodes/SoImage.h @@ -43,6 +43,7 @@ class SoSensor; class SoFieldSensor; class SbImage; +class SoIRRenderAction; class COIN_DLL_API SoImage : public SoShape { typedef SoShape inherited; @@ -75,6 +76,7 @@ class COIN_DLL_API SoImage : public SoShape { #if COIN_HAVE_LEGACY_GL_RENDERER void GLRender(SoGLRenderAction * action) override; #endif + void IRRender(SoIRRenderAction * action) override; void rayPick(SoRayPickAction * action) override; void getPrimitiveCount(SoGetPrimitiveCountAction * action) override; diff --git a/include/Inventor/nodes/SoText2.h b/include/Inventor/nodes/SoText2.h index e63f628e745..06c2b06adbf 100644 --- a/include/Inventor/nodes/SoText2.h +++ b/include/Inventor/nodes/SoText2.h @@ -39,6 +39,8 @@ #include #include +class SoIRRenderAction; + class COIN_DLL_API SoText2 : public SoShape { typedef SoShape inherited; @@ -61,6 +63,7 @@ class COIN_DLL_API SoText2 : public SoShape { #if COIN_HAVE_LEGACY_GL_RENDERER void GLRender(SoGLRenderAction * action) override; #endif + void IRRender(SoIRRenderAction * action) override; void rayPick(SoRayPickAction * action) override; void getPrimitiveCount(SoGetPrimitiveCountAction * action) override; diff --git a/include/Inventor/rendering/SoRenderIR.h b/include/Inventor/rendering/SoRenderIR.h index 5a71f29299d..e4f28081c32 100644 --- a/include/Inventor/rendering/SoRenderIR.h +++ b/include/Inventor/rendering/SoRenderIR.h @@ -241,6 +241,17 @@ struct SoTextureData { SbVec4f blendColor = SbVec4f(0.0f, 0.0f, 0.0f, 1.0f); }; +struct SoPixelRasterData { + // Pixel rasterization is a semantic command property, not a producer + // identity. The texture retains the source dimensions; these dimensions + // describe the desired on-screen footprint. + SbBool enabled = FALSE; + float originX = 0.0f; + float originY = 0.0f; + int width = 0; + int height = 0; +}; + /*! \struct SoMaterialData \brief Snapshot of the logical Inventor material state for one draw call. @@ -330,8 +341,10 @@ enum SoRasterFillMode : uint8_t { \brief Rasterizer properties (fill mode, culling, polygon offset). */ struct SoRasterState { + SbBool visible = TRUE; SoRasterFillMode fillMode = SO_RASTER_FILL; - uint8_t cullMode = 0; + SbBool cullBackFaces = FALSE; + SbBool frontFaceCCW = TRUE; SbBool scissorEnabled = FALSE; SbBool viewportEnabled = FALSE; int viewportX = 0; @@ -340,8 +353,13 @@ struct SoRasterState { int viewportHeight = 0; float lineWidth = 1.0f; float pointSize = 1.0f; + uint16_t linePattern = 0xFFFF; + int16_t linePatternScale = 1; float polygonOffsetFactor = 0.0f; float polygonOffsetUnits = 0.0f; + SbBool polygonOffsetFilled = FALSE; + SbBool polygonOffsetLines = FALSE; + SbBool polygonOffsetPoints = FALSE; }; /*! @@ -432,6 +450,7 @@ struct SoRenderCommand { SoLightingHandle lightingHandle = 0; // Stable scene identity. Zero means that the producer did not provide one. uint64_t objectId = 0; + SoPixelRasterData pixelRaster; void * userData = nullptr; //!< Opaque, non-owned producer data. }; diff --git a/include/Inventor/system/gl-fallbacks.h b/include/Inventor/system/gl-fallbacks.h index 046fdb492dd..a762e0df253 100644 --- a/include/Inventor/system/gl-fallbacks.h +++ b/include/Inventor/system/gl-fallbacks.h @@ -250,6 +250,12 @@ #define GL_FLOAT_VEC4_ARB 0x8B52 #endif +/* Geometry shaders are used by the retained raster backend. The macOS + * compatibility headers predate this core enum. */ +#ifndef GL_GEOMETRY_SHADER +#define GL_GEOMETRY_SHADER 0x8DD9 +#endif + #ifndef GL_NUM_EXTENSIONS #define GL_NUM_EXTENSIONS 0x821D #endif diff --git a/src/actions/SoIRRenderAction.cpp b/src/actions/SoIRRenderAction.cpp index cccdafbe4e8..7df5b483dd6 100644 --- a/src/actions/SoIRRenderAction.cpp +++ b/src/actions/SoIRRenderAction.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -109,6 +110,7 @@ SoIRRenderAction::initClass(void) SO_ENABLE(SoIRRenderAction, SoDepthBufferElement); SO_ENABLE(SoIRRenderAction, SoDrawStyleElement); SO_ENABLE(SoIRRenderAction, SoLineWidthElement); + SO_ENABLE(SoIRRenderAction, SoLinePatternElement); SO_ENABLE(SoIRRenderAction, SoPolygonOffsetElement); SO_ENABLE(SoIRRenderAction, SoShapeStyleElement); SO_ENABLE(SoIRRenderAction, SoLightModelElement); diff --git a/src/rendering/SoGLRenderBackend.cpp b/src/rendering/SoGLRenderBackend.cpp index ef6df839336..e64959f8df1 100644 --- a/src/rendering/SoGLRenderBackend.cpp +++ b/src/rendering/SoGLRenderBackend.cpp @@ -13,12 +13,23 @@ #include #include #include +#include #include #include #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace { @@ -28,6 +39,7 @@ static constexpr GLuint POSITION_ATTRIBUTE = 0; static constexpr GLuint NORMAL_ATTRIBUTE = 1; static constexpr GLuint COLOR_ATTRIBUTE = 2; static constexpr GLuint TEXCOORD_ATTRIBUTE = 3; +static constexpr GLuint LINE_DISTANCE_ATTRIBUTE = 4; GLenum textureWrapToGL(const SoTextureWrap wrap) @@ -85,6 +97,9 @@ blendFactorToGL(const SoBlendFactor factor) case SO_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA: return GL_ONE_MINUS_CONSTANT_ALPHA; case SO_BLEND_FACTOR_SRC_ALPHA_SATURATE: return GL_SRC_ALPHA_SATURATE; + // The Visual program has no secondary fragment output. Keep the semantic + // factor in the IR and make the executor's deterministic primary-source + // fallback only at this API boundary. case SO_BLEND_FACTOR_SRC1_COLOR: return GL_SRC_COLOR; case SO_BLEND_FACTOR_ONE_MINUS_SRC1_COLOR: return GL_ONE_MINUS_SRC_COLOR; case SO_BLEND_FACTOR_SRC1_ALPHA: return GL_SRC_ALPHA; @@ -195,24 +210,25 @@ compileShader(const cc_glglue * glue, const GLenum type, const char * source) } GLuint -linkProgram(const cc_glglue * glue, - const char * vertexSource, - const char * fragmentSource) +linkProgram(const cc_glglue * glue, const char * vertexSource, + const char * fragmentSource, const char * geometrySource = nullptr) { const GLuint vertex = compileShader(glue, GL_VERTEX_SHADER, vertexSource); - const GLuint fragment = compileShader(glue, GL_FRAGMENT_SHADER, - fragmentSource); - if (!vertex || !fragment) { + const GLuint fragment = compileShader(glue, GL_FRAGMENT_SHADER, fragmentSource); + const GLuint geometry = geometrySource + ? compileShader(glue, GL_GEOMETRY_SHADER, geometrySource) : 0; + if (!vertex || !fragment || (geometrySource && !geometry)) { if (vertex) cc_glglue_glDeleteShader(glue, vertex); if (fragment) cc_glglue_glDeleteShader(glue, fragment); + if (geometry) cc_glglue_glDeleteShader(glue, geometry); return 0; } const GLuint program = cc_glglue_glCreateProgram(glue); cc_glglue_glAttachShader(glue, program, vertex); cc_glglue_glAttachShader(glue, program, fragment); + if (geometry) cc_glglue_glAttachShader(glue, program, geometry); cc_glglue_glLinkProgram(glue, program); - GLint linked = GL_FALSE; cc_glglue_glGetGLSLProgramiv(glue, program, GL_LINK_STATUS, &linked); if (linked == GL_FALSE) { @@ -221,14 +237,14 @@ linkProgram(const cc_glglue * glue, if (length > 0) { std::string log(static_cast(length), '\0'); cc_glglue_glGetProgramInfoLog(glue, program, length, &length, &log[0]); - SoDebugError::postInfo("SoGLRenderBackend::linkProgram", - "%s", log.c_str()); + SoDebugError::postInfo("SoGLRenderBackend::linkProgram", "%s", + log.c_str()); } cc_glglue_glDeleteProgram(glue, program); } - cc_glglue_glDeleteShader(glue, vertex); cc_glglue_glDeleteShader(glue, fragment); + if (geometry) cc_glglue_glDeleteShader(glue, geometry); return linked == GL_FALSE ? 0 : program; } @@ -326,13 +342,16 @@ SoGLRenderBackend::initialize(const SoRenderBackendInitParams & params) if (!this->glue->glGenVertexArrays || !this->glue->glBindVertexArray || !this->glue->glDeleteVertexArrays || + !this->glue->glGetAttribLocation || !this->glue->glVertexAttribPointer || !this->glue->glEnableVertexAttribArray || !this->glue->glDisableVertexAttribArray || !this->glue->glVertexAttrib4f || !this->glue->glVertexAttrib3f || !this->glue->glVertexAttrib2f || + !this->glue->glVertexAttrib1f || !this->glue->glUniform1f || !this->glue->glUniform1i || + !this->glue->glUniform2f || !this->glue->glUniform3f || !this->glue->glUniform1iv || !this->glue->glUniform2fv || !this->glue->glUniform3fv || !this->glue->glUniform4f || !this->glue->glUniformMatrix4fv || @@ -348,6 +367,12 @@ SoGLRenderBackend::initialize(const SoRenderBackendInitParams & params) return FALSE; } + GLfloat lineRange[2] = { 1.0f, 1.0f }; + GLfloat pointRange[2] = { 1.0f, 1.0f }; + glGetFloatv(GL_LINE_WIDTH_RANGE, lineRange); + glGetFloatv(GL_POINT_SIZE_RANGE, pointRange); + this->rasterPrograms.nativeLineWidthMax = std::max(1.0f, lineRange[1]); + this->rasterPrograms.nativePointSizeMax = std::max(1.0f, pointRange[1]); this->setInitialized(TRUE); return TRUE; } @@ -355,6 +380,7 @@ SoGLRenderBackend::initialize(const SoRenderBackendInitParams & params) void SoGLRenderBackend::destroyCacheEntry(CachedCommand & entry) { + this->destroyLineRasterStream(entry); if (entry.positionBuffer) { cc_glglue_glDeleteBuffers(this->glue, 1, &entry.positionBuffer); } @@ -367,6 +393,9 @@ SoGLRenderBackend::destroyCacheEntry(CachedCommand & entry) if (entry.texcoordBuffer) { cc_glglue_glDeleteBuffers(this->glue, 1, &entry.texcoordBuffer); } + if (entry.lineDistanceBuffer) { + cc_glglue_glDeleteBuffers(this->glue, 1, &entry.lineDistanceBuffer); + } if (entry.texture) { cc_glglue_glDeleteTextures(this->glue, 1, &entry.texture); } @@ -377,6 +406,37 @@ SoGLRenderBackend::destroyCacheEntry(CachedCommand & entry) entry = CachedCommand(); } +void +SoGLRenderBackend::destroyLineRasterStream(CachedCommand & entry) +{ + const GLuint buffers[] = { + entry.lineRasterPositionBuffer, + entry.lineRasterNormalBuffer, + entry.lineRasterColorBuffer, + entry.lineRasterTexcoordBuffer, + entry.lineRasterDistanceBuffer + }; + for (GLuint buffer : buffers) { + if (buffer) cc_glglue_glDeleteBuffers(this->glue, 1, &buffer); + } + if (entry.lineRasterVertexArray) { + this->glue->glDeleteVertexArrays(1, &entry.lineRasterVertexArray); + } + entry.lineRasterVertexArray = 0; + entry.lineRasterPositionBuffer = 0; + entry.lineRasterNormalBuffer = 0; + entry.lineRasterColorBuffer = 0; + entry.lineRasterTexcoordBuffer = 0; + entry.lineRasterDistanceBuffer = 0; + entry.lineRasterVertexCount = 0; + entry.lineRasterIndexCount = 0; + entry.lineRasterPositionsKey = nullptr; + entry.lineRasterNormalsKey = nullptr; + entry.lineRasterColorsKey = nullptr; + entry.lineRasterTexcoordsKey = nullptr; + entry.lineRasterIndicesKey = nullptr; +} + void SoGLRenderBackend::invalidateCache() { @@ -399,7 +459,29 @@ SoGLRenderBackend::shutdown() this->invalidateCache(); if (this->visualProgram.handle) { cc_glglue_glDeleteProgram(this->glue, this->visualProgram.handle); - this->visualProgram = VisualProgram(); + this->visualProgram.handle = 0; + } + if (this->rasterPrograms.line.handle) { + cc_glglue_glDeleteProgram(this->glue, this->rasterPrograms.line.handle); + this->rasterPrograms.line.handle = 0; + } + if (this->rasterPrograms.triangleLine.handle) { + cc_glglue_glDeleteProgram(this->glue, + this->rasterPrograms.triangleLine.handle); + this->rasterPrograms.triangleLine.handle = 0; + } + if (this->rasterPrograms.point.handle) { + cc_glglue_glDeleteProgram(this->glue, this->rasterPrograms.point.handle); + this->rasterPrograms.point.handle = 0; + } + if (this->rasterPrograms.trianglePoint.handle) { + cc_glglue_glDeleteProgram(this->glue, + this->rasterPrograms.trianglePoint.handle); + this->rasterPrograms.trianglePoint.handle = 0; + } + if (this->rasterPrograms.pixel.handle) { + cc_glglue_glDeleteProgram(this->glue, this->rasterPrograms.pixel.handle); + this->rasterPrograms.pixel.handle = 0; } this->glue = nullptr; this->setInitialized(FALSE); @@ -420,37 +502,6 @@ SoGLRenderBackend::getOrCreateCache(const SoRenderCommand * command) return this->gpuCache.back(); } -void -SoGLRenderBackend::uploadGeometry(CachedCommand & entry, - const SoRenderCommand & command) -{ - const SoGeometryDesc & geometry = command.geometry; - const uint32_t vertexStride = geometry.vertexStride - ? geometry.vertexStride : sizeof(float) * 3; - const SoTextureData & texture = command.material.texture; - const bool hasTexture = texture.pixels && texture.width > 0 && - texture.height > 0 && texture.numComponents >= 1 && - texture.numComponents <= 4 && geometry.texcoords && geometry.vertexCount; - - this->uploadVertexBuffers(entry, geometry); - if (hasTexture) this->uploadTexture(entry, geometry, texture); - else { - if (entry.texcoordBuffer) { - cc_glglue_glDeleteBuffers(this->glue, 1, &entry.texcoordBuffer); - entry.texcoordBuffer = 0; - } - if (entry.texture) { - cc_glglue_glDeleteTextures(this->glue, 1, &entry.texture); - entry.texture = 0; - } - } - this->uploadIndices(entry, geometry); - - cc_glglue_glBindBuffer(this->glue, GL_ARRAY_BUFFER, 0); - cc_glglue_glBindBuffer(this->glue, GL_ELEMENT_ARRAY_BUFFER, 0); - this->updateCacheDescription(entry, command, hasTexture, vertexStride); -} - void SoGLRenderBackend::uploadVertexBuffers(CachedCommand & entry, const SoGeometryDesc & geometry) @@ -463,8 +514,7 @@ SoGLRenderBackend::uploadVertexBuffers(CachedCommand & entry, cc_glglue_glBindBuffer(this->glue, GL_ARRAY_BUFFER, entry.positionBuffer); cc_glglue_glBufferData(this->glue, GL_ARRAY_BUFFER, static_cast(geometry.vertexCount) * - vertexStride, - geometry.positions, GL_STATIC_DRAW); + vertexStride, geometry.positions, GL_STATIC_DRAW); if (geometry.normals && geometry.normalCount >= geometry.vertexCount) { if (!entry.normalBuffer) { @@ -473,8 +523,7 @@ SoGLRenderBackend::uploadVertexBuffers(CachedCommand & entry, cc_glglue_glBindBuffer(this->glue, GL_ARRAY_BUFFER, entry.normalBuffer); cc_glglue_glBufferData(this->glue, GL_ARRAY_BUFFER, static_cast(geometry.vertexCount) * - vertexStride, - geometry.normals, GL_STATIC_DRAW); + vertexStride, geometry.normals, GL_STATIC_DRAW); } else if (entry.normalBuffer) { cc_glglue_glDeleteBuffers(this->glue, 1, &entry.normalBuffer); @@ -488,8 +537,7 @@ SoGLRenderBackend::uploadVertexBuffers(CachedCommand & entry, cc_glglue_glBindBuffer(this->glue, GL_ARRAY_BUFFER, entry.colorBuffer); cc_glglue_glBufferData(this->glue, GL_ARRAY_BUFFER, static_cast(geometry.vertexCount) * - sizeof(float) * 4, - geometry.colors, GL_STATIC_DRAW); + sizeof(float) * 4, geometry.colors, GL_STATIC_DRAW); } else if (entry.colorBuffer) { cc_glglue_glDeleteBuffers(this->glue, 1, &entry.colorBuffer); @@ -499,9 +547,25 @@ SoGLRenderBackend::uploadVertexBuffers(CachedCommand & entry, void SoGLRenderBackend::uploadTexture(CachedCommand & entry, - const SoGeometryDesc & geometry, - const SoTextureData & texture) + const SoRenderCommand & command) { + const SoGeometryDesc & geometry = command.geometry; + const SoTextureData & texture = command.material.texture; + const bool hasTexture = texture.pixels && texture.width > 0 && + texture.height > 0 && texture.numComponents >= 1 && + texture.numComponents <= 4 && geometry.texcoords && geometry.vertexCount; + if (!hasTexture) { + if (entry.texcoordBuffer) { + cc_glglue_glDeleteBuffers(this->glue, 1, &entry.texcoordBuffer); + entry.texcoordBuffer = 0; + } + if (entry.texture) { + cc_glglue_glDeleteTextures(this->glue, 1, &entry.texture); + entry.texture = 0; + } + return; + } + if (!entry.texcoordBuffer) { cc_glglue_glGenBuffers(this->glue, 1, &entry.texcoordBuffer); } @@ -517,14 +581,14 @@ SoGLRenderBackend::uploadTexture(CachedCommand & entry, texcoords[static_cast(i) * 2 + 1] = source[1]; } cc_glglue_glBufferData(this->glue, GL_ARRAY_BUFFER, - texcoords.size() * sizeof(float), - texcoords.data(), GL_STATIC_DRAW); + texcoords.size() * sizeof(float), texcoords.data(), + GL_STATIC_DRAW); - if (!entry.texture) cc_glglue_glGenTextures(this->glue, 1, &entry.texture); + if (!entry.texture) { + cc_glglue_glGenTextures(this->glue, 1, &entry.texture); + } cc_glglue_glBindTexture(this->glue, GL_TEXTURE_2D, entry.texture); - - const TextureUploadFormat format = - textureUploadFormat(texture.numComponents); + const TextureUploadFormat format = textureUploadFormat(texture.numComponents); const ScopedPixelUnpackState unpackState(this->glue); glTexImage2D(GL_TEXTURE_2D, 0, format.internalFormat, texture.width, texture.height, 0, format.format, @@ -572,8 +636,7 @@ SoGLRenderBackend::uploadIndices(CachedCommand & entry, entry.indexBuffer); cc_glglue_glBufferData(this->glue, GL_ELEMENT_ARRAY_BUFFER, static_cast(geometry.indexCount) * - sizeof(uint32_t), - geometry.indices, GL_STATIC_DRAW); + sizeof(uint32_t), geometry.indices, GL_STATIC_DRAW); } else if (entry.indexBuffer) { cc_glglue_glDeleteBuffers(this->glue, 1, &entry.indexBuffer); @@ -581,14 +644,73 @@ SoGLRenderBackend::uploadIndices(CachedCommand & entry, } } +void +SoGLRenderBackend::uploadLineDistanceBuffer(CachedCommand & entry, + const SoGeometryDesc & geometry, + const GLsizei vertexStride) +{ + const bool lineGeometry = geometry.topology == SO_TOPOLOGY_LINES || + geometry.topology == SO_TOPOLOGY_LINE_STRIP; + if (!lineGeometry || !geometry.vertexCount) { + if (entry.lineDistanceBuffer) { + cc_glglue_glDeleteBuffers(this->glue, 1, &entry.lineDistanceBuffer); + entry.lineDistanceBuffer = 0; + entry.lineDistanceKey = nullptr; + } + return; + } + if (!entry.lineDistanceBuffer) { + cc_glglue_glGenBuffers(this->glue, 1, &entry.lineDistanceBuffer); + } + std::vector distances(geometry.vertexCount, 0.0f); + const uint32_t strideFloats = static_cast(vertexStride) / + sizeof(float); + const uint32_t count = geometry.indexCount && geometry.indices + ? geometry.indexCount : geometry.vertexCount; + if (geometry.topology == SO_TOPOLOGY_LINE_STRIP) { + for (uint32_t i = 1; i < count; ++i) { + const uint32_t previous = geometry.indices ? geometry.indices[i - 1] : i - 1; + const uint32_t current = geometry.indices ? geometry.indices[i] : i; + const float * p0 = geometry.positions + previous * strideFloats; + const float * p1 = geometry.positions + current * strideFloats; + const float dx = p1[0] - p0[0]; + const float dy = p1[1] - p0[1]; + const float dz = p1[2] - p0[2]; + distances[current] = distances[previous] + + std::sqrt(dx * dx + dy * dy + dz * dz); + } + } + else { + for (uint32_t i = 0; i + 1 < count; i += 2) { + const uint32_t first = geometry.indices ? geometry.indices[i] : i; + const uint32_t second = geometry.indices ? geometry.indices[i + 1] : i + 1; + const float * p0 = geometry.positions + first * strideFloats; + const float * p1 = geometry.positions + second * strideFloats; + const float dx = p1[0] - p0[0]; + const float dy = p1[1] - p0[1]; + const float dz = p1[2] - p0[2]; + distances[first] = 0.0f; + distances[second] = std::sqrt(dx * dx + dy * dy + dz * dz); + } + } + cc_glglue_glBindBuffer(this->glue, GL_ARRAY_BUFFER, + entry.lineDistanceBuffer); + cc_glglue_glBufferData(this->glue, GL_ARRAY_BUFFER, + distances.size() * sizeof(float), distances.data(), + GL_STATIC_DRAW); + entry.lineDistanceKey = geometry.positions; +} + void SoGLRenderBackend::updateCacheDescription(CachedCommand & entry, const SoRenderCommand & command, - const bool hasTexture, - const uint32_t vertexStride) + const GLsizei vertexStride) { const SoGeometryDesc & geometry = command.geometry; const SoTextureData & texture = command.material.texture; + const bool hasTexture = texture.pixels && texture.width > 0 && + texture.height > 0 && texture.numComponents >= 1 && + texture.numComponents <= 4 && geometry.texcoords && geometry.vertexCount; entry.positionsKey = geometry.positions; entry.normalsKey = geometry.normals; entry.colorsKey = geometry.colors; @@ -598,7 +720,7 @@ SoGLRenderBackend::updateCacheDescription(CachedCommand & entry, entry.vertexCount = geometry.vertexCount; entry.normalCount = geometry.normalCount; entry.indexCount = geometry.indexCount; - entry.vertexStride = vertexStride; + entry.vertexStride = static_cast(vertexStride); entry.texcoordStride = geometry.texcoordStride; entry.textureWidth = hasTexture ? texture.width : 0; entry.textureHeight = hasTexture ? texture.height : 0; @@ -614,6 +736,28 @@ SoGLRenderBackend::updateCacheDescription(CachedCommand & entry, entry.textureAnisotropic = hasTexture ? texture.anisotropic : false; } +void +SoGLRenderBackend::uploadGeometry(CachedCommand & entry, + const SoRenderCommand & command) +{ + const SoGeometryDesc & geometry = command.geometry; + const GLsizei vertexStride = static_cast( + geometry.vertexStride ? geometry.vertexStride : sizeof(float) * 3); + + this->uploadVertexBuffers(entry, geometry); + + this->uploadTexture(entry, command); + + this->uploadLineDistanceBuffer(entry, geometry, vertexStride); + + this->uploadIndices(entry, geometry); + + cc_glglue_glBindBuffer(this->glue, GL_ARRAY_BUFFER, 0); + cc_glglue_glBindBuffer(this->glue, GL_ELEMENT_ARRAY_BUFFER, 0); + + this->updateCacheDescription(entry, command, vertexStride); +} + void SoGLRenderBackend::setupVisualVAO(CachedCommand & entry) { @@ -631,7 +775,8 @@ SoGLRenderBackend::setupVisualVAO(CachedCommand & entry) GL_FALSE, entry.vertexStride, nullptr); } if (entry.normalBuffer) { - cc_glglue_glBindBuffer(this->glue, GL_ARRAY_BUFFER, entry.normalBuffer); + cc_glglue_glBindBuffer(this->glue, GL_ARRAY_BUFFER, + entry.normalBuffer); cc_glglue_glEnableVertexAttribArray(this->glue, NORMAL_ATTRIBUTE); cc_glglue_glVertexAttribPointer(this->glue, NORMAL_ATTRIBUTE, 3, GL_FLOAT, GL_FALSE, entry.vertexStride, @@ -663,6 +808,17 @@ SoGLRenderBackend::setupVisualVAO(CachedCommand & entry) cc_glglue_glDisableVertexAttribArray(this->glue, TEXCOORD_ATTRIBUTE); cc_glglue_glVertexAttrib2f(this->glue, TEXCOORD_ATTRIBUTE, 0.0f, 0.0f); } + if (entry.lineDistanceBuffer) { + cc_glglue_glBindBuffer(this->glue, GL_ARRAY_BUFFER, + entry.lineDistanceBuffer); + cc_glglue_glEnableVertexAttribArray(this->glue, LINE_DISTANCE_ATTRIBUTE); + cc_glglue_glVertexAttribPointer(this->glue, LINE_DISTANCE_ATTRIBUTE, 1, + GL_FLOAT, GL_FALSE, 0, nullptr); + } + else { + cc_glglue_glDisableVertexAttribArray(this->glue, LINE_DISTANCE_ATTRIBUTE); + this->glue->glVertexAttrib1f(LINE_DISTANCE_ATTRIBUTE, 0.0f); + } if (entry.indexBuffer) { cc_glglue_glBindBuffer(this->glue, GL_ELEMENT_ARRAY_BUFFER, entry.indexBuffer); @@ -672,6 +828,263 @@ SoGLRenderBackend::setupVisualVAO(CachedCommand & entry) cc_glglue_glBindBuffer(this->glue, GL_ELEMENT_ARRAY_BUFFER, 0); } +void +SoGLRenderBackend::setupLineRasterVAO(CachedCommand & entry) +{ + if (!entry.lineRasterVertexArray) { + this->glue->glGenVertexArrays(1, &entry.lineRasterVertexArray); + } + this->glue->glBindVertexArray(entry.lineRasterVertexArray); + + auto bindAttribute = [this](GLuint buffer, GLuint attribute, GLint size) { + if (buffer) { + cc_glglue_glBindBuffer(this->glue, GL_ARRAY_BUFFER, buffer); + cc_glglue_glEnableVertexAttribArray(this->glue, attribute); + cc_glglue_glVertexAttribPointer(this->glue, attribute, size, GL_FLOAT, + GL_FALSE, 0, nullptr); + } + }; + bindAttribute(entry.lineRasterPositionBuffer, POSITION_ATTRIBUTE, 3); + if (!entry.lineRasterNormalBuffer) { + cc_glglue_glDisableVertexAttribArray(this->glue, NORMAL_ATTRIBUTE); + this->glue->glVertexAttrib3f(NORMAL_ATTRIBUTE, 0.0f, 0.0f, 1.0f); + } + else { + bindAttribute(entry.lineRasterNormalBuffer, NORMAL_ATTRIBUTE, 3); + } + if (!entry.lineRasterColorBuffer) { + cc_glglue_glDisableVertexAttribArray(this->glue, COLOR_ATTRIBUTE); + cc_glglue_glVertexAttrib4f(this->glue, COLOR_ATTRIBUTE, + 1.0f, 1.0f, 1.0f, 1.0f); + } + else { + bindAttribute(entry.lineRasterColorBuffer, COLOR_ATTRIBUTE, 4); + } + if (!entry.lineRasterTexcoordBuffer) { + cc_glglue_glDisableVertexAttribArray(this->glue, TEXCOORD_ATTRIBUTE); + cc_glglue_glVertexAttrib2f(this->glue, TEXCOORD_ATTRIBUTE, 0.0f, 0.0f); + } + else { + bindAttribute(entry.lineRasterTexcoordBuffer, TEXCOORD_ATTRIBUTE, 2); + } + bindAttribute(entry.lineRasterDistanceBuffer, LINE_DISTANCE_ATTRIBUTE, 1); + + this->glue->glBindVertexArray(0); + cc_glglue_glBindBuffer(this->glue, GL_ARRAY_BUFFER, 0); +} + +void +SoGLRenderBackend::updateIndexedLineRasterStream( + CachedCommand & entry, + const SoRenderCommand & command, + const SbMat & viewMat, + const SbMat & projMat, + const SbVec2s & viewportSize) +{ + const SoGeometryDesc & geometry = command.geometry; + const uint32_t occurrenceCount = geometry.indexCount; + if (!geometry.indices || occurrenceCount == 0 || + !geometry.positions || geometry.vertexCount == 0) { + return; + } + + const bool hasNormals = geometry.normals && + geometry.normalCount >= geometry.vertexCount; + const bool hasColors = geometry.colors != nullptr; + const bool hasTexcoords = geometry.texcoords != nullptr; + const bool streamMatches = entry.lineRasterVertexArray != 0 && + entry.lineRasterPositionsKey == geometry.positions && + entry.lineRasterNormalsKey == geometry.normals && + entry.lineRasterColorsKey == geometry.colors && + entry.lineRasterTexcoordsKey == geometry.texcoords && + entry.lineRasterIndicesKey == geometry.indices && + entry.lineRasterIndexCount == occurrenceCount; + + if (!streamMatches) { + this->destroyLineRasterStream(entry); + this->glue->glGenBuffers(1, &entry.lineRasterPositionBuffer); + if (hasNormals) this->glue->glGenBuffers(1, &entry.lineRasterNormalBuffer); + if (hasColors) this->glue->glGenBuffers(1, &entry.lineRasterColorBuffer); + if (hasTexcoords) { + this->glue->glGenBuffers(1, &entry.lineRasterTexcoordBuffer); + } + this->glue->glGenBuffers(1, &entry.lineRasterDistanceBuffer); + + const uint32_t positionStride = geometry.vertexStride + ? geometry.vertexStride / sizeof(float) : 3; + const uint32_t texcoordStride = geometry.texcoordStride + ? geometry.texcoordStride / sizeof(float) : 4; + std::vector positions(static_cast(occurrenceCount) * 3); + std::vector normals; + std::vector colors; + std::vector texcoords; + if (hasNormals) normals.resize(static_cast(occurrenceCount) * 3); + if (hasColors) colors.resize(static_cast(occurrenceCount) * 4); + if (hasTexcoords) texcoords.resize(static_cast(occurrenceCount) * 2); + + for (uint32_t occurrence = 0; occurrence < occurrenceCount; ++occurrence) { + const uint32_t source = geometry.indices[occurrence] < geometry.vertexCount + ? geometry.indices[occurrence] : 0; + const float * position = geometry.positions + + static_cast(source) * positionStride; + std::copy(position, position + 3, positions.begin() + + static_cast(occurrence) * 3); + if (hasNormals) { + const float * normal = geometry.normals + + static_cast(source) * positionStride; + std::copy(normal, normal + 3, normals.begin() + + static_cast(occurrence) * 3); + } + if (hasColors) { + const float * color = geometry.colors + static_cast(source) * 4; + std::copy(color, color + 4, colors.begin() + + static_cast(occurrence) * 4); + } + if (hasTexcoords) { + const float * texcoord = geometry.texcoords + + static_cast(source) * texcoordStride; + texcoords[static_cast(occurrence) * 2] = texcoord[0]; + texcoords[static_cast(occurrence) * 2 + 1] = texcoord[1]; + } + } + + auto upload = [this](GLuint buffer, const std::vector & values) { + if (!buffer) return; + cc_glglue_glBindBuffer(this->glue, GL_ARRAY_BUFFER, buffer); + cc_glglue_glBufferData(this->glue, GL_ARRAY_BUFFER, + values.size() * sizeof(float), values.data(), + GL_STATIC_DRAW); + }; + upload(entry.lineRasterPositionBuffer, positions); + upload(entry.lineRasterNormalBuffer, normals); + upload(entry.lineRasterColorBuffer, colors); + upload(entry.lineRasterTexcoordBuffer, texcoords); + cc_glglue_glBindBuffer(this->glue, GL_ARRAY_BUFFER, 0); + + entry.lineRasterPositionsKey = geometry.positions; + entry.lineRasterNormalsKey = geometry.normals; + entry.lineRasterColorsKey = geometry.colors; + entry.lineRasterTexcoordsKey = geometry.texcoords; + entry.lineRasterIndicesKey = geometry.indices; + entry.lineRasterIndexCount = occurrenceCount; + entry.lineRasterVertexCount = occurrenceCount; + this->setupLineRasterVAO(entry); + } + + const SbMatrix view(viewMat); + const SbMatrix projection(projMat); + const SbMatrix model(command.modelMatrix); + const uint32_t positionStride = geometry.vertexStride + ? geometry.vertexStride / sizeof(float) : 3; + std::vector windowPositions(geometry.vertexCount); + for (uint32_t i = 0; i < geometry.vertexCount; ++i) { + const float * p = geometry.positions + static_cast(i) * positionStride; + SbVec3f point(p[0], p[1], p[2]); + SbVec3f transformed; + model.multVecMatrix(point, transformed); + view.multVecMatrix(transformed, transformed); + projection.multVecMatrix(transformed, transformed); + windowPositions[i].setValue( + (transformed[0] * 0.5f + 0.5f) * viewportSize[0], + (transformed[1] * 0.5f + 0.5f) * viewportSize[1]); + } + + std::vector distances(occurrenceCount, 0.0f); + auto sourceAt = [&geometry](uint32_t occurrence) { + return geometry.indices[occurrence] < geometry.vertexCount + ? geometry.indices[occurrence] : 0; + }; + auto segmentLength = [&windowPositions](uint32_t first, uint32_t second) { + return (windowPositions[second] - windowPositions[first]).length(); + }; + if (geometry.topology == SO_TOPOLOGY_LINE_STRIP) { + for (uint32_t i = 1; i < occurrenceCount; ++i) { + distances[i] = distances[i - 1] + + segmentLength(sourceAt(i - 1), sourceAt(i)); + } + } + else { + for (uint32_t i = 0; i + 1 < occurrenceCount; i += 2) { + distances[i + 1] = segmentLength(sourceAt(i), sourceAt(i + 1)); + } + } + cc_glglue_glBindBuffer(this->glue, GL_ARRAY_BUFFER, + entry.lineRasterDistanceBuffer); + cc_glglue_glBufferData(this->glue, GL_ARRAY_BUFFER, + distances.size() * sizeof(float), distances.data(), + GL_DYNAMIC_DRAW); + cc_glglue_glBindBuffer(this->glue, GL_ARRAY_BUFFER, 0); +} + +void +SoGLRenderBackend::updateLineDistances(CachedCommand & entry, + const SoRenderCommand & command, + const SbMat & viewMat, + const SbMat & projMat, + const SbVec2s & viewportSize) +{ + if (command.geometry.indices && command.geometry.indexCount && + (command.geometry.topology == SO_TOPOLOGY_LINES || + command.geometry.topology == SO_TOPOLOGY_LINE_STRIP)) { + this->updateIndexedLineRasterStream(entry, command, viewMat, projMat, + viewportSize); + return; + } + if (!entry.lineDistanceBuffer || !command.geometry.positions || + command.geometry.vertexCount == 0) return; + + const SbMatrix view(viewMat); + const SbMatrix projection(projMat); + SbMatrix model(command.modelMatrix); + const uint32_t strideFloats = static_cast( + command.geometry.vertexStride ? command.geometry.vertexStride + : sizeof(float) * 3) / sizeof(float); + const uint32_t count = command.geometry.indexCount && command.geometry.indices + ? command.geometry.indexCount : command.geometry.vertexCount; + std::vector windowPositions(command.geometry.vertexCount); + for (uint32_t i = 0; i < command.geometry.vertexCount; ++i) { + const float * p = command.geometry.positions + i * strideFloats; + SbVec3f point(p[0], p[1], p[2]); + SbVec3f transformed; + model.multVecMatrix(point, transformed); + view.multVecMatrix(transformed, transformed); + projection.multVecMatrix(transformed, transformed); + windowPositions[i].setValue( + (transformed[0] * 0.5f + 0.5f) * viewportSize[0], + (transformed[1] * 0.5f + 0.5f) * viewportSize[1]); + } + + std::vector distances(command.geometry.vertexCount, 0.0f); + auto indexAt = [&command](uint32_t i) { + return command.geometry.indices ? command.geometry.indices[i] : i; + }; + auto segmentLength = [&windowPositions](uint32_t first, uint32_t second) { + return (windowPositions[second] - windowPositions[first]).length(); + }; + if (command.geometry.topology == SO_TOPOLOGY_LINE_STRIP) { + for (uint32_t i = 1; i < count; ++i) { + const uint32_t previous = indexAt(i - 1); + const uint32_t current = indexAt(i); + distances[current] = distances[previous] + + segmentLength(previous, current); + } + } + else { + for (uint32_t i = 0; i + 1 < count; i += 2) { + const uint32_t first = indexAt(i); + const uint32_t second = indexAt(i + 1); + distances[first] = 0.0f; + distances[second] = segmentLength(first, second); + } + } + cc_glglue_glBindBuffer(this->glue, GL_ARRAY_BUFFER, + entry.lineDistanceBuffer); + cc_glglue_glBufferData(this->glue, GL_ARRAY_BUFFER, + distances.size() * sizeof(float), distances.data(), + GL_DYNAMIC_DRAW); + cc_glglue_glBindBuffer(this->glue, GL_ARRAY_BUFFER, 0); +} + bool SoGLRenderBackend::textureDescriptionMatches( const CachedCommand & entry, @@ -711,6 +1124,8 @@ SoGLRenderBackend::updateGeometryCache(const SoDrawList & drawlist) CachedCommand & entry = this->getOrCreateCache(&command); const uint32_t vertexStride = geometry.vertexStride ? geometry.vertexStride : sizeof(float) * 3; + const bool lineGeometry = geometry.topology == SO_TOPOLOGY_LINES || + geometry.topology == SO_TOPOLOGY_LINE_STRIP; const bool geometryMatches = entry.positionBuffer != 0 && entry.cacheGeneration == generation && entry.positionsKey == geometry.positions && @@ -723,6 +1138,7 @@ SoGLRenderBackend::updateGeometryCache(const SoDrawList & drawlist) entry.indexCount == geometry.indexCount && entry.vertexStride == vertexStride && entry.texcoordStride == geometry.texcoordStride && + entry.lineDistanceKey == (lineGeometry ? geometry.positions : nullptr) && this->textureDescriptionMatches(entry, command) && ((entry.texturePixelsKey != nullptr) == (command.material.texture.pixels != nullptr)); @@ -736,7 +1152,8 @@ SoGLRenderBackend::updateGeometryCache(const SoDrawList & drawlist) void SoGLRenderBackend::uploadLighting(const SoDrawList & drawlist, - const SoRenderCommand & command) + const SoRenderCommand & command, + const SurfaceUniforms & uniforms) { const SoLightingData * lighting = drawlist.getLighting(command.lightingHandle); static const SoLightingData emptyLighting; @@ -747,13 +1164,13 @@ SoGLRenderBackend::uploadLighting(const SoDrawList & drawlist, std::call_once(invalidHandleWarning, []() { SoDebugError::postWarning( "SoGLRenderBackend::uploadLighting", - "Ignoring an invalid retained lighting handle; no headlight is synthesized."); + "Draw command references missing lighting data; no headlight is " + "synthesized."); }); } } const SbVec3f & ambient = lighting->ambient; - const VisualProgram::Uniforms & uniforms = this->visualProgram.uniforms; this->glue->glUniform3f(uniforms.lighting.ambient, ambient[0], ambient[1], ambient[2]); @@ -770,8 +1187,8 @@ SoGLRenderBackend::uploadLighting(const SoDrawList & drawlist, std::call_once(lightLimitWarning, []() { SoDebugError::postWarning( "SoGLRenderBackend::uploadLighting", - "The retained GL Visual program supports eight lights; additional " - "retained lights are not uploaded."); + "The Visual program supports eight lights; additional retained " + "lights are ignored by this executor."); }); } for (int i = 0; i < count; ++i) { @@ -794,7 +1211,8 @@ SoGLRenderBackend::uploadLighting(const SoDrawList & drawlist, } this->glue->glUniform1i(uniforms.lighting.lightCount, count); this->glue->glUniform1iv(uniforms.lighting.lightType, MAX_SHADER_LIGHTS, types); - this->glue->glUniform3fv(uniforms.lighting.lightColor, MAX_SHADER_LIGHTS, colors); + this->glue->glUniform3fv(uniforms.lighting.lightColor, MAX_SHADER_LIGHTS, + colors); this->glue->glUniform3fv(uniforms.lighting.lightDirection, MAX_SHADER_LIGHTS, directions); this->glue->glUniform3fv(uniforms.lighting.lightPosition, MAX_SHADER_LIGHTS, @@ -806,59 +1224,27 @@ SoGLRenderBackend::uploadLighting(const SoDrawList & drawlist, } void -SoGLRenderBackend::drawCommand(const SoDrawList & drawlist, - const SoRenderCommand & command, - const SbMat & viewMat, - const SbMat & projMat, - const SoRenderParams & params) +SoGLRenderBackend::bindRasterCommon(const SoDrawList & drawlist, + const SoRenderCommand & command, + const SbMat & viewMat, + const SbMat & projMat, + const SbVec4f & color, + const bool useVertexColor, + const bool textured, + const SurfaceUniforms & uniforms) { - if (!command.geometry.positions || command.geometry.vertexCount == 0) return; - const auto found = this->commandToCache.find(&command); - if (found == this->commandToCache.end()) return; - const CachedCommand & entry = this->gpuCache[found->second]; - if (!entry.vertexArray) return; - - this->bindVisualCommand(drawlist, command, entry, viewMat, projMat, params); - this->drawGeometry(command, entry); -} - -void -SoGLRenderBackend::bindTransforms(const SoRenderCommand & command, - const SbMat & viewMat, - const SbMat & projMat) -{ - const VisualProgram::Uniforms & uniforms = this->visualProgram.uniforms; + SbMat model; + command.modelMatrix.getValue(model); this->glue->glUniformMatrix4fv(uniforms.transforms.view, 1, GL_FALSE, &viewMat[0][0]); this->glue->glUniformMatrix4fv(uniforms.transforms.projection, 1, GL_FALSE, &projMat[0][0]); - SbMat model; - command.modelMatrix.getValue(model); this->glue->glUniformMatrix4fv(uniforms.transforms.model, 1, GL_FALSE, &model[0][0]); - -} - -void -SoGLRenderBackend::bindMaterial(const SoRenderCommand & command, - const CachedCommand & entry) -{ - const VisualProgram::Uniforms & uniforms = this->visualProgram.uniforms; - const SbVec4f & color = command.material.diffuse; this->glue->glUniform4f(uniforms.material.color, color[0], color[1], color[2], color[3]); this->glue->glUniform1f(uniforms.material.useVertexColor, - entry.colorBuffer ? 1.0f : 0.0f); - this->glue->glUniform1f( - uniforms.material.vertexColorAlphaIncludesOpacity, - command.material.vertexColorAlphaIncludesOpacity ? 1.0f : 0.0f); - this->glue->glUniform1f( - uniforms.texture.alphaIncludesOpacity, - command.material.textureAlphaIncludesOpacity ? 1.0f : 0.0f); - const bool textureHasAlpha = command.material.texture.numComponents == 2 || - command.material.texture.numComponents == 4; - this->glue->glUniform1f(uniforms.texture.hasAlpha, - textureHasAlpha ? 1.0f : 0.0f); + useVertexColor ? 1.0f : 0.0f); const SoShadingModel shadingModel = command.material.shadingModel; this->glue->glUniform1i(uniforms.material.shadingModel, @@ -876,12 +1262,252 @@ SoGLRenderBackend::bindMaterial(const SoRenderCommand & command, command.material.shininess); this->glue->glUniform1f(uniforms.material.twoSidedLighting, command.material.twoSidedLighting ? 1.0f : 0.0f); + this->glue->glUniform1f(uniforms.material.vertexColorAlphaIncludesOpacity, + command.material.vertexColorAlphaIncludesOpacity + ? 1.0f : 0.0f); + this->glue->glUniform1f(uniforms.texture.alphaIncludesOpacity, + command.material.textureAlphaIncludesOpacity + ? 1.0f : 0.0f); + const bool textureHasAlpha = command.material.texture.numComponents == 2 || + command.material.texture.numComponents == 4; + this->glue->glUniform1f(uniforms.texture.hasAlpha, + textureHasAlpha ? 1.0f : 0.0f); + this->glue->glUniform1f(uniforms.texture.enabled, + textured ? 1.0f : 0.0f); + this->glue->glUniform1i(uniforms.texture.sampler, 0); + this->glue->glUniform1i(uniforms.texture.model, + static_cast(command.material.texture.model)); + const SbVec4f & textureBlend = command.material.texture.blendColor; + this->glue->glUniform4f(uniforms.texture.blendColor, + textureBlend[0], textureBlend[1], + textureBlend[2], textureBlend[3]); + this->glue->glUniform1i( + uniforms.alphaTest.function, + command.state.alphaTest.policy == SO_ALPHA_TEST_POLICY_NONE + ? 0 : static_cast(command.state.alphaTest.function)); + this->glue->glUniform1f(uniforms.alphaTest.reference, + command.state.alphaTest.reference); + this->uploadLighting(drawlist, command, uniforms); } void -SoGLRenderBackend::applyDepthState(const SoRenderCommand & command) +SoGLRenderBackend::bindPointShader(const SoRenderCommand & command, + const SbMat & viewMat, + const SbMat & projMat, + const SbVec4f & color, + const bool useVertexColor, + const float pointSize, + const SbVec2s & viewportSize, + const bool triangleInput, + const SoDrawList & drawlist, + const bool textured) +{ + const GLuint program = triangleInput + ? this->rasterPrograms.trianglePoint.handle + : this->rasterPrograms.point.handle; + const PointProgram & pointProgram = triangleInput + ? this->rasterPrograms.trianglePoint + : this->rasterPrograms.point; + cc_glglue_glUseProgram(this->glue, program); + this->bindRasterCommon(drawlist, command, viewMat, projMat, color, + useVertexColor, textured, pointProgram.surface); + this->glue->glUniform1f(pointProgram.raster.pointSize, pointSize); + this->glue->glUniform2f(pointProgram.raster.viewportSize, + static_cast(viewportSize[0]), + static_cast(viewportSize[1])); + if (triangleInput) { + this->glue->glUniform1f( + pointProgram.raster.cullBackFaces, + command.state.raster.cullBackFaces ? 1.0f : 0.0f); + this->glue->glUniform1f( + pointProgram.raster.frontFaceCCW, + command.state.raster.frontFaceCCW ? 1.0f : 0.0f); + } +} + +void +SoGLRenderBackend::bindLineShader(const SoRenderCommand & command, + const SbMat & viewMat, + const SbMat & projMat, + const SbVec4f & color, + const bool useVertexColor, + const float lineWidth, + const SbVec2s & viewportSize, + const bool triangleInput, + const SoDrawList & drawlist, + const bool textured) +{ + const GLuint program = triangleInput + ? this->rasterPrograms.triangleLine.handle + : this->rasterPrograms.line.handle; + const LineProgram & lineProgram = triangleInput + ? this->rasterPrograms.triangleLine + : this->rasterPrograms.line; + cc_glglue_glUseProgram(this->glue, program); + this->bindRasterCommon(drawlist, command, viewMat, projMat, color, + useVertexColor, textured, lineProgram.surface); + this->glue->glUniform1f(lineProgram.raster.lineWidth, lineWidth); + this->glue->glUniform2f(lineProgram.raster.viewportSize, + static_cast(viewportSize[0]), + static_cast(viewportSize[1])); + if (triangleInput) { + this->glue->glUniform1f( + lineProgram.raster.cullBackFaces, + command.state.raster.cullBackFaces ? 1.0f : 0.0f); + this->glue->glUniform1f( + lineProgram.raster.frontFaceCCW, + command.state.raster.frontFaceCCW ? 1.0f : 0.0f); + } + this->glue->glUniform1i( + lineProgram.raster.stipplePattern, + static_cast(command.state.raster.linePattern)); + this->glue->glUniform1f( + lineProgram.raster.stippleScale, + static_cast(std::max(1, static_cast( + command.state.raster.linePatternScale)))); +} + +void +SoGLRenderBackend::bindPixelShader(const SoRenderCommand & command, + const SbMat & viewMat, + const SbMat & projMat, + const SbVec2s & viewportOrigin, + const SbVec2s & viewportSize) +{ + const PixelProgram & pixel = this->rasterPrograms.pixel; + cc_glglue_glUseProgram(this->glue, pixel.handle); + SbMat model; + command.modelMatrix.getValue(model); + this->glue->glUniformMatrix4fv(pixel.uniforms.view, 1, GL_FALSE, + &viewMat[0][0]); + this->glue->glUniformMatrix4fv(pixel.uniforms.projection, 1, GL_FALSE, + &projMat[0][0]); + this->glue->glUniformMatrix4fv(pixel.uniforms.model, 1, GL_FALSE, + &model[0][0]); + + const GLsizei stride = static_cast( + command.geometry.vertexStride ? command.geometry.vertexStride : sizeof(float) * 3); + const char * raw = reinterpret_cast(command.geometry.positions); + SbVec3f center(0.0f, 0.0f, 0.0f); + for (uint32_t i = 0; i < command.geometry.vertexCount; ++i) { + const float * position = reinterpret_cast(raw + i * stride); + center += SbVec3f(position[0], position[1], position[2]); + } + if (command.geometry.vertexCount) { + center /= static_cast(command.geometry.vertexCount); + } + this->glue->glUniform3f(pixel.uniforms.quadCenter, + center[0], center[1], center[2]); + this->glue->glUniform2f(pixel.uniforms.sourceSize, + static_cast(command.material.texture.width), + static_cast(command.material.texture.height)); + this->glue->glUniform2f(pixel.uniforms.rasterSize, + static_cast(command.pixelRaster.width), + static_cast(command.pixelRaster.height)); + this->glue->glUniform2f(pixel.uniforms.viewportOrigin, + static_cast(viewportOrigin[0]), + static_cast(viewportOrigin[1])); + this->glue->glUniform2f(pixel.uniforms.viewportSize, + static_cast(viewportSize[0]), + static_cast(viewportSize[1])); + this->glue->glUniform2f(pixel.uniforms.pixelOrigin, + static_cast(command.pixelRaster.originX), + static_cast(command.pixelRaster.originY)); + this->glue->glUniform1i(pixel.uniforms.texture, 0); + this->glue->glUniform1i( + pixel.uniforms.alphaTestFunction, + command.state.alphaTest.policy == SO_ALPHA_TEST_POLICY_NONE + ? 0 : static_cast(command.state.alphaTest.function)); + this->glue->glUniform1f(pixel.uniforms.alphaTestReference, + command.state.alphaTest.reference); +} + +void +SoGLRenderBackend::drawCommand(const SoDrawList & drawlist, + const SoRenderCommand & command, + const SbMat & viewMat, + const SbMat & projMat, + const SoRenderParams & params) { + if (!command.state.raster.visible) return; + if (!command.geometry.positions || command.geometry.vertexCount == 0) return; + const auto found = this->commandToCache.find(&command); + if (found == this->commandToCache.end()) return; + CachedCommand & entry = this->gpuCache[found->second]; + if (!entry.vertexArray) return; + + RasterPath path = this->selectRasterPath(entry, command, params); + if (path.useLineShader && + (path.primitive == GL_LINES || path.primitive == GL_LINE_STRIP)) { + this->updateLineDistances(entry, command, viewMat, projMat, + params.viewport.getViewportSizePixels()); + path.expandedLineStream = command.geometry.indices && + command.geometry.indexCount && entry.lineRasterVertexArray != 0; + } + applyViewport(params); + this->applyDepthState(command); + this->applyRasterState(command, path); + this->applyBlendState(command); + GLenum polygonOffsetTarget = GL_POLYGON_OFFSET_FILL; + const bool polygonOffset = this->applyPolygonOffset( + command, path, polygonOffsetTarget); + this->bindCommandProgram(drawlist, command, path, viewMat, projMat, + params, entry); + this->drawGeometry(command, path, entry); + this->restoreRasterState(path, polygonOffsetTarget, polygonOffset); +} + +SoGLRenderBackend::RasterPath +SoGLRenderBackend::selectRasterPath(const CachedCommand & entry, + const SoRenderCommand & command, + const SoRenderParams & params) const +{ + RasterPath path; + path.primitive = topologyToGL(command.geometry.topology); + path.textured = entry.texture != 0 && entry.texcoordBuffer != 0; + path.pixelRaster = path.textured && command.pixelRaster.enabled && + command.pixelRaster.width > 0 && command.pixelRaster.height > 0; + const float dpr = params.devicePixelRatio > 0.0f + ? params.devicePixelRatio : 1.0f; + path.pointSize = std::max(1.0f, command.state.raster.pointSize) * dpr; + path.lineWidth = std::max(1.0f, command.state.raster.lineWidth) * dpr; + const SoRasterFillMode fillMode = command.state.raster.fillMode; + const bool triangleTopology = path.primitive == GL_TRIANGLES || + path.primitive == GL_TRIANGLE_STRIP; + const bool lineTopology = path.primitive == GL_LINES || + path.primitive == GL_LINE_STRIP; + const bool pointTopology = path.primitive == GL_POINTS; + path.linePrimitive = lineTopology || + (fillMode == SO_RASTER_LINES && triangleTopology); + path.pointPrimitive = pointTopology || + (fillMode == SO_RASTER_POINTS && triangleTopology); + path.filledPrimitive = !path.linePrimitive && !path.pointPrimitive; + // Core-profile drivers may report a broad GL_LINE_WIDTH_RANGE while still + // rasterizing ordinary wide lines as one pixel. Use the retained line + // shader for every non-default width so compat and core profiles execute + // the same semantic raster state. + const bool lineEmulationRequired = + path.lineWidth > 1.0f || + command.state.raster.linePattern != 0xFFFF; + path.usePointShader = !path.pixelRaster && path.pointPrimitive && + this->rasterPrograms.point.handle != 0 && + path.pointSize > this->rasterPrograms.nativePointSizeMax; + path.useLineShader = !path.pixelRaster && path.linePrimitive && + this->rasterPrograms.line.handle != 0 && lineEmulationRequired; + path.lineTriangleInput = path.useLineShader && + fillMode == SO_RASTER_LINES && triangleTopology; + path.pointTriangleInput = path.usePointShader && + fillMode == SO_RASTER_POINTS && triangleTopology; + path.expandedLineStream = path.useLineShader && lineTopology && + command.geometry.indices && command.geometry.indexCount && + entry.lineRasterVertexArray != 0; + return path; +} + +void +SoGLRenderBackend::applyDepthState(const SoRenderCommand & command) +{ if (command.state.depth.enabled) { glEnable(GL_DEPTH_TEST); glDepthFunc(depthFunctionToGL(command.state.depth.func)); @@ -891,114 +1517,170 @@ SoGLRenderBackend::applyDepthState(const SoRenderCommand & command) } glDepthMask(command.state.depth.writeEnabled ? GL_TRUE : GL_FALSE); glDepthRange(command.state.depth.range[0], command.state.depth.range[1]); +} +void +SoGLRenderBackend::applyRasterState(const SoRenderCommand & command, + const RasterPath & path) +{ + const bool triangleFallback = path.lineTriangleInput || + path.pointTriangleInput; + if (command.state.raster.cullBackFaces && !triangleFallback) { + glEnable(GL_CULL_FACE); + glCullFace(GL_BACK); + } + else { + glDisable(GL_CULL_FACE); + } + glFrontFace(command.state.raster.frontFaceCCW ? GL_CCW : GL_CW); + const bool triangleTopology = path.primitive == GL_TRIANGLES || + path.primitive == GL_TRIANGLE_STRIP; + if (!path.useLineShader && command.state.raster.fillMode == SO_RASTER_LINES && + !path.pointTriangleInput && triangleTopology) { + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + } + else if (!path.usePointShader && + command.state.raster.fillMode == SO_RASTER_POINTS && + !path.lineTriangleInput && triangleTopology) { + glPolygonMode(GL_FRONT_AND_BACK, GL_POINT); + } + if (!path.usePointShader && path.pointPrimitive) glPointSize(path.pointSize); + if (!path.useLineShader && path.linePrimitive) glLineWidth(path.lineWidth); } void SoGLRenderBackend::applyBlendState(const SoRenderCommand & command) { const bool blending = command.state.blend.enabled; - if (blending) { - glEnable(GL_BLEND); - if (isDualSourceBlendFactor(command.state.blend.srcRGBFactor) || - isDualSourceBlendFactor(command.state.blend.dstRGBFactor) || - isDualSourceBlendFactor(command.state.blend.srcAlphaFactor) || - isDualSourceBlendFactor(command.state.blend.dstAlphaFactor)) { - static std::once_flag dualSourceWarning; - std::call_once(dualSourceWarning, []() { - SoDebugError::postWarning( - "SoGLRenderBackend::bindVisualCommand", - "Dual-source blend factors are not supported by the Visual " - "program; using primary-source factors for execution."); - }); - } - cc_glglue_glBlendFuncSeparate( - this->glue, blendFactorToGL(command.state.blend.srcRGBFactor), - blendFactorToGL(command.state.blend.dstRGBFactor), - blendFactorToGL(command.state.blend.srcAlphaFactor), - blendFactorToGL(command.state.blend.dstAlphaFactor)); - if (cc_glglue_has_blendequation(this->glue) && - command.state.blend.rgbEquation == command.state.blend.alphaEquation) { - cc_glglue_glBlendEquation( - this->glue, blendEquationToGL(command.state.blend.rgbEquation)); - } - } - else { + if (!blending) { glDisable(GL_BLEND); + return; + } + glEnable(GL_BLEND); + if (isDualSourceBlendFactor(command.state.blend.srcRGBFactor) || + isDualSourceBlendFactor(command.state.blend.dstRGBFactor) || + isDualSourceBlendFactor(command.state.blend.srcAlphaFactor) || + isDualSourceBlendFactor(command.state.blend.dstAlphaFactor)) { + static std::once_flag dualSourceWarning; + std::call_once(dualSourceWarning, []() { + SoDebugError::postWarning( + "SoGLRenderBackend::applyBlendState", + "Dual-source blend factors are not supported by the Visual program; " + "using primary-source factors for execution."); + }); + } + cc_glglue_glBlendFuncSeparate( + this->glue, blendFactorToGL(command.state.blend.srcRGBFactor), + blendFactorToGL(command.state.blend.dstRGBFactor), + blendFactorToGL(command.state.blend.srcAlphaFactor), + blendFactorToGL(command.state.blend.dstAlphaFactor)); + if (cc_glglue_has_blendequation(this->glue) && + command.state.blend.rgbEquation == command.state.blend.alphaEquation) { + cc_glglue_glBlendEquation( + this->glue, blendEquationToGL(command.state.blend.rgbEquation)); } } -void -SoGLRenderBackend::bindAlphaTest(const SoRenderCommand & command) +bool +SoGLRenderBackend::applyPolygonOffset(const SoRenderCommand & command, + const RasterPath & path, + GLenum & target) { - const VisualProgram::Uniforms & uniforms = this->visualProgram.uniforms; - this->glue->glUniform1i( - uniforms.alphaTest.function, - command.state.alphaTest.policy == SO_ALPHA_TEST_POLICY_NONE - ? 0 : static_cast(command.state.alphaTest.function)); - this->glue->glUniform1f(uniforms.alphaTest.reference, - command.state.alphaTest.reference); - + const bool applies = (path.filledPrimitive && + command.state.raster.polygonOffsetFilled) || + (path.linePrimitive && command.state.raster.polygonOffsetLines) || + (path.pointPrimitive && command.state.raster.polygonOffsetPoints); + const bool enabled = applies && + (command.state.raster.polygonOffsetFactor != 0.0f || + command.state.raster.polygonOffsetUnits != 0.0f); + if (!enabled) return false; + target = (path.useLineShader || path.usePointShader || path.filledPrimitive) + ? GL_POLYGON_OFFSET_FILL + : (path.linePrimitive ? GL_POLYGON_OFFSET_LINE : GL_POLYGON_OFFSET_POINT); + glEnable(target); + glPolygonOffset(command.state.raster.polygonOffsetFactor, + command.state.raster.polygonOffsetUnits); + return true; } void -SoGLRenderBackend::bindTexture(const SoRenderCommand & command, - const CachedCommand & entry) +SoGLRenderBackend::bindCommandProgram(const SoDrawList & drawlist, + const SoRenderCommand & command, + const RasterPath & path, + const SbMat & viewMat, + const SbMat & projMat, + const SoRenderParams & params, + const CachedCommand & entry) { - const VisualProgram::Uniforms & uniforms = this->visualProgram.uniforms; - const bool textured = entry.texture != 0 && entry.texcoordBuffer != 0; - this->glue->glUniform1f(uniforms.texture.enabled, - textured ? 1.0f : 0.0f); - if (textured) { + if (path.textured) { cc_glglue_glActiveTexture(this->glue, GL_TEXTURE0); cc_glglue_glBindTexture(this->glue, GL_TEXTURE_2D, entry.texture); - this->glue->glUniform1i(uniforms.texture.sampler, 0); } - this->glue->glUniform1i(uniforms.texture.model, - static_cast(command.material.texture.model)); - const SbVec4f & textureBlend = command.material.texture.blendColor; - this->glue->glUniform4f(uniforms.texture.blendColor, - textureBlend[0], textureBlend[1], - textureBlend[2], textureBlend[3]); -} - -void -SoGLRenderBackend::bindVisualCommand(const SoDrawList & drawlist, - const SoRenderCommand & command, - const CachedCommand & entry, - const SbMat & viewMat, - const SbMat & projMat, - const SoRenderParams & params) -{ - applyViewport(params); - this->bindTransforms(command, viewMat, projMat); - this->bindMaterial(command, entry); - this->uploadLighting(drawlist, command); - this->applyDepthState(command); - this->applyBlendState(command); - this->bindAlphaTest(command); - this->bindTexture(command, entry); + const SbVec4f & color = command.material.diffuse; + if (path.pixelRaster) { + this->bindPixelShader(command, viewMat, projMat, + params.viewport.getViewportOriginPixels(), + params.viewport.getViewportSizePixels()); + } + else if (path.usePointShader) { + this->bindPointShader(command, viewMat, projMat, color, + entry.colorBuffer != 0, path.pointSize, + params.viewport.getViewportSizePixels(), + path.pointTriangleInput, drawlist, path.textured); + } + else if (path.useLineShader) { + this->bindLineShader(command, viewMat, projMat, color, + entry.colorBuffer != 0, path.lineWidth, + params.viewport.getViewportSizePixels(), + path.lineTriangleInput, drawlist, path.textured); + } + else { + cc_glglue_glUseProgram(this->glue, this->visualProgram.handle); + this->bindRasterCommon(drawlist, command, viewMat, projMat, color, + entry.colorBuffer != 0, path.textured, + this->visualProgram.surface); + } } void SoGLRenderBackend::drawGeometry(const SoRenderCommand & command, + const RasterPath & path, const CachedCommand & entry) { - const GLenum primitive = topologyToGL(command.geometry.topology); - this->glue->glBindVertexArray(entry.vertexArray); - if (command.geometry.indexCount && command.geometry.indices) { - cc_glglue_glDrawElements(this->glue, primitive, + this->glue->glBindVertexArray(path.expandedLineStream + ? entry.lineRasterVertexArray + : entry.vertexArray); + if (path.expandedLineStream) { + cc_glglue_glDrawArrays(this->glue, path.primitive, 0, + static_cast(entry.lineRasterVertexCount)); + } + else if (command.geometry.indexCount && command.geometry.indices) { + cc_glglue_glDrawElements(this->glue, path.primitive, static_cast(command.geometry.indexCount), GL_UNSIGNED_INT, nullptr); } else { - cc_glglue_glDrawArrays(this->glue, primitive, 0, + cc_glglue_glDrawArrays(this->glue, path.primitive, 0, static_cast(command.geometry.vertexCount)); } this->glue->glBindVertexArray(0); - if (entry.texture && entry.texcoordBuffer) { - cc_glglue_glBindTexture(this->glue, GL_TEXTURE_2D, 0); - } +} + +void +SoGLRenderBackend::restoreRasterState(const RasterPath & path, + const GLenum polygonOffsetTarget, + const bool polygonOffsetEnabled) +{ + if (path.pixelRaster || path.usePointShader || path.useLineShader) { + cc_glglue_glUseProgram(this->glue, this->visualProgram.handle); + } + if (path.textured) cc_glglue_glBindTexture(this->glue, GL_TEXTURE_2D, 0); + if (polygonOffsetEnabled) glDisable(polygonOffsetTarget); + if (!path.filledPrimitive) glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + glDepthRange(0.0, 1.0); + glFrontFace(GL_CCW); + if (!path.usePointShader) glPointSize(1.0f); + if (!path.useLineShader) glLineWidth(1.0f); } void @@ -1013,6 +1695,9 @@ SoGLRenderBackend::beginFrame(const SoRenderParams & params) glDepthMask(GL_TRUE); glDisable(GL_CULL_FACE); glDisable(GL_SCISSOR_TEST); + glDisable(GL_POLYGON_OFFSET_FILL); + glDisable(GL_POLYGON_OFFSET_LINE); + glDisable(GL_POLYGON_OFFSET_POINT); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glPointSize(1.0f); glLineWidth(1.0f); @@ -1036,73 +1721,158 @@ SoGLRenderBackend::beginFrame(const SoRenderParams & params) bool SoGLRenderBackend::createShaders() { - return this->createVisualProgram(); -} - -bool -SoGLRenderBackend::createVisualProgram() -{ - const GLuint program = linkProgram( - this->glue, - coin_gl_visual_vertex_shadersource, + this->visualProgram.handle = linkProgram( + this->glue, coin_gl_visual_vertex_shadersource, coin_gl_visual_fragment_shadersource); - if (!program) return false; - - this->visualProgram.handle = program; - VisualProgram::Uniforms & uniforms = this->visualProgram.uniforms; - uniforms.transforms.view = cc_glglue_glGetUniformLocation(this->glue, program, "u_view"); - uniforms.transforms.projection = cc_glglue_glGetUniformLocation( - this->glue, program, "u_proj"); - uniforms.transforms.model = cc_glglue_glGetUniformLocation(this->glue, program, "u_model"); - uniforms.material.color = cc_glglue_glGetUniformLocation(this->glue, program, "u_color"); - uniforms.material.useVertexColor = cc_glglue_glGetUniformLocation( - this->glue, program, "u_useVertexColor"); - uniforms.material.shadingModel = cc_glglue_glGetUniformLocation( - this->glue, program, "u_shadingModel"); - uniforms.material.emissiveColor = cc_glglue_glGetUniformLocation( - this->glue, program, "u_emissiveColor"); - uniforms.material.ambient = cc_glglue_glGetUniformLocation( - this->glue, program, "u_materialAmbient"); - uniforms.material.specular = cc_glglue_glGetUniformLocation( - this->glue, program, "u_materialSpecular"); - uniforms.material.shininess = cc_glglue_glGetUniformLocation( - this->glue, program, "u_materialShininess"); - uniforms.material.twoSidedLighting = cc_glglue_glGetUniformLocation( - this->glue, program, "u_twoSidedLighting"); - uniforms.material.vertexColorAlphaIncludesOpacity = cc_glglue_glGetUniformLocation( - this->glue, program, "u_vertexColorAlphaIncludesOpacity"); - uniforms.texture.alphaIncludesOpacity = cc_glglue_glGetUniformLocation( - this->glue, program, "u_textureAlphaIncludesOpacity"); - uniforms.texture.hasAlpha = cc_glglue_glGetUniformLocation( - this->glue, program, "u_textureHasAlpha"); - uniforms.lighting.ambient = cc_glglue_glGetUniformLocation( - this->glue, program, "u_ambientLight"); - uniforms.lighting.lightCount = cc_glglue_glGetUniformLocation( - this->glue, program, "u_lightCount"); - uniforms.lighting.lightType = cc_glglue_glGetUniformLocation( - this->glue, program, "u_lightType"); - uniforms.lighting.lightColor = cc_glglue_glGetUniformLocation( - this->glue, program, "u_lightColor"); - uniforms.lighting.lightDirection = cc_glglue_glGetUniformLocation( - this->glue, program, "u_lightDirection"); - uniforms.lighting.lightPosition = cc_glglue_glGetUniformLocation( - this->glue, program, "u_lightPosition"); - uniforms.lighting.lightAttenuation = cc_glglue_glGetUniformLocation( - this->glue, program, "u_lightAttenuation"); - uniforms.lighting.lightSpotParams = cc_glglue_glGetUniformLocation( - this->glue, program, "u_lightSpotParams"); - uniforms.texture.sampler = cc_glglue_glGetUniformLocation( - this->glue, program, "u_texture"); - uniforms.texture.enabled = cc_glglue_glGetUniformLocation( - this->glue, program, "u_textureEnabled"); - uniforms.texture.model = cc_glglue_glGetUniformLocation( - this->glue, program, "u_textureModel"); - uniforms.texture.blendColor = cc_glglue_glGetUniformLocation( - this->glue, program, "u_textureBlendColor"); - uniforms.alphaTest.function = cc_glglue_glGetUniformLocation( - this->glue, program, "u_alphaTestFunction"); - uniforms.alphaTest.reference = cc_glglue_glGetUniformLocation( - this->glue, program, "u_alphaTestReference"); + this->rasterPrograms.line.handle = linkProgram( + this->glue, coin_gl_wide_line_vertex_shadersource, + coin_gl_wide_line_fragment_shadersource, + coin_gl_wide_line_geometry_shadersource); + this->rasterPrograms.triangleLine.handle = linkProgram( + this->glue, coin_gl_wide_line_vertex_shadersource, + coin_gl_wide_line_fragment_shadersource, + coin_gl_wide_line_triangle_geometry_shadersource); + this->rasterPrograms.point.handle = linkProgram( + this->glue, coin_gl_point_vertex_shadersource, + coin_gl_point_fragment_shadersource, + coin_gl_point_geometry_shadersource); + this->rasterPrograms.trianglePoint.handle = linkProgram( + this->glue, coin_gl_point_vertex_shadersource, + coin_gl_point_fragment_shadersource, + coin_gl_point_triangle_geometry_shadersource); + this->rasterPrograms.pixel.handle = linkProgram( + this->glue, coin_gl_pixel_vertex_shadersource, + coin_gl_pixel_fragment_shadersource); + + const GLuint programs[] = { + this->visualProgram.handle, + this->rasterPrograms.line.handle, + this->rasterPrograms.triangleLine.handle, + this->rasterPrograms.point.handle, + this->rasterPrograms.trianglePoint.handle, + this->rasterPrograms.pixel.handle + }; + for (const GLuint program : programs) { + if (!program) { + for (const GLuint created : programs) { + if (created) cc_glglue_glDeleteProgram(this->glue, created); + } + this->visualProgram.handle = 0; + this->rasterPrograms.line.handle = 0; + this->rasterPrograms.triangleLine.handle = 0; + this->rasterPrograms.point.handle = 0; + this->rasterPrograms.trianglePoint.handle = 0; + this->rasterPrograms.pixel.handle = 0; + return false; + } + } + + auto uniform = [this](GLuint program, const char * name) { + return cc_glglue_glGetUniformLocation(this->glue, program, name); + }; + auto cacheSurface = [this, &uniform](SurfaceUniforms & surface, + const GLuint program) { + surface.transforms.view = uniform(program, "u_view"); + surface.transforms.projection = uniform(program, "u_proj"); + surface.transforms.model = uniform(program, "u_model"); + surface.material.color = uniform(program, "u_color"); + surface.material.useVertexColor = uniform(program, "u_useVertexColor"); + surface.material.shadingModel = uniform(program, "u_shadingModel"); + surface.material.emissiveColor = uniform(program, "u_emissiveColor"); + surface.material.ambient = uniform(program, "u_materialAmbient"); + surface.material.specular = uniform(program, "u_materialSpecular"); + surface.material.shininess = uniform(program, "u_materialShininess"); + surface.material.twoSidedLighting = uniform(program, "u_twoSidedLighting"); + surface.material.vertexColorAlphaIncludesOpacity = + uniform(program, "u_vertexColorAlphaIncludesOpacity"); + surface.texture.alphaIncludesOpacity = + uniform(program, "u_textureAlphaIncludesOpacity"); + surface.texture.hasAlpha = uniform(program, "u_textureHasAlpha"); + surface.lighting.ambient = uniform(program, "u_ambientLight"); + surface.lighting.lightCount = uniform(program, "u_lightCount"); + surface.lighting.lightType = uniform(program, "u_lightType"); + surface.lighting.lightColor = uniform(program, "u_lightColor"); + surface.lighting.lightDirection = uniform(program, "u_lightDirection"); + surface.lighting.lightPosition = uniform(program, "u_lightPosition"); + surface.lighting.lightAttenuation = uniform(program, "u_lightAttenuation"); + surface.lighting.lightSpotParams = uniform(program, "u_lightSpotParams"); + surface.texture.sampler = uniform(program, "u_texture"); + surface.texture.enabled = uniform(program, "u_textureEnabled"); + surface.texture.model = uniform(program, "u_textureModel"); + surface.texture.blendColor = uniform(program, "u_textureBlendColor"); + surface.alphaTest.function = uniform(program, "u_alphaTestFunction"); + surface.alphaTest.reference = uniform(program, "u_alphaTestReference"); + }; + cacheSurface(this->visualProgram.surface, this->visualProgram.handle); + cacheSurface(this->rasterPrograms.line.surface, + this->rasterPrograms.line.handle); + cacheSurface(this->rasterPrograms.triangleLine.surface, + this->rasterPrograms.triangleLine.handle); + cacheSurface(this->rasterPrograms.point.surface, + this->rasterPrograms.point.handle); + cacheSurface(this->rasterPrograms.trianglePoint.surface, + this->rasterPrograms.trianglePoint.handle); + + LineProgram::RasterUniforms & line = this->rasterPrograms.line.raster; + line.lineWidth = uniform(this->rasterPrograms.line.handle, "u_lineWidth"); + line.viewportSize = uniform(this->rasterPrograms.line.handle, "u_vpSize"); + line.stipplePattern = uniform(this->rasterPrograms.line.handle, + "u_stipplePattern"); + line.stippleScale = uniform(this->rasterPrograms.line.handle, + "u_stippleScale"); + line.cullBackFaces = uniform(this->rasterPrograms.line.handle, + "u_cullBackFaces"); + line.frontFaceCCW = uniform(this->rasterPrograms.line.handle, + "u_frontFaceCCW"); + this->rasterPrograms.triangleLine.raster = line; + this->rasterPrograms.triangleLine.raster.lineWidth = + uniform(this->rasterPrograms.triangleLine.handle, "u_lineWidth"); + this->rasterPrograms.triangleLine.raster.viewportSize = + uniform(this->rasterPrograms.triangleLine.handle, "u_vpSize"); + this->rasterPrograms.triangleLine.raster.stipplePattern = + uniform(this->rasterPrograms.triangleLine.handle, "u_stipplePattern"); + this->rasterPrograms.triangleLine.raster.stippleScale = + uniform(this->rasterPrograms.triangleLine.handle, "u_stippleScale"); + this->rasterPrograms.triangleLine.raster.cullBackFaces = + uniform(this->rasterPrograms.triangleLine.handle, "u_cullBackFaces"); + this->rasterPrograms.triangleLine.raster.frontFaceCCW = + uniform(this->rasterPrograms.triangleLine.handle, "u_frontFaceCCW"); + + PointProgram::RasterUniforms & point = this->rasterPrograms.point.raster; + point.pointSize = uniform(this->rasterPrograms.point.handle, "u_pointSize"); + point.viewportSize = uniform(this->rasterPrograms.point.handle, "u_vpSize"); + point.cullBackFaces = uniform(this->rasterPrograms.point.handle, + "u_cullBackFaces"); + point.frontFaceCCW = uniform(this->rasterPrograms.point.handle, + "u_frontFaceCCW"); + this->rasterPrograms.trianglePoint.raster = point; + this->rasterPrograms.trianglePoint.raster.pointSize = + uniform(this->rasterPrograms.trianglePoint.handle, "u_pointSize"); + this->rasterPrograms.trianglePoint.raster.viewportSize = + uniform(this->rasterPrograms.trianglePoint.handle, "u_vpSize"); + this->rasterPrograms.trianglePoint.raster.cullBackFaces = + uniform(this->rasterPrograms.trianglePoint.handle, "u_cullBackFaces"); + this->rasterPrograms.trianglePoint.raster.frontFaceCCW = + uniform(this->rasterPrograms.trianglePoint.handle, "u_frontFaceCCW"); + + PixelProgram::Uniforms & pixel = this->rasterPrograms.pixel.uniforms; + pixel.view = uniform(this->rasterPrograms.pixel.handle, "u_view"); + pixel.projection = uniform(this->rasterPrograms.pixel.handle, "u_proj"); + pixel.model = uniform(this->rasterPrograms.pixel.handle, "u_model"); + pixel.quadCenter = uniform(this->rasterPrograms.pixel.handle, "u_quadCenter"); + pixel.sourceSize = uniform(this->rasterPrograms.pixel.handle, + "u_sourceSize"); + pixel.rasterSize = uniform(this->rasterPrograms.pixel.handle, + "u_rasterSize"); + pixel.viewportOrigin = uniform(this->rasterPrograms.pixel.handle, + "u_viewportOrigin"); + pixel.viewportSize = uniform(this->rasterPrograms.pixel.handle, "u_vpSize"); + pixel.pixelOrigin = uniform(this->rasterPrograms.pixel.handle, "u_pixelOrigin"); + pixel.texture = uniform(this->rasterPrograms.pixel.handle, "u_texture"); + pixel.alphaTestFunction = uniform(this->rasterPrograms.pixel.handle, + "u_alphaTestFunction"); + pixel.alphaTestReference = uniform(this->rasterPrograms.pixel.handle, + "u_alphaTestReference"); return true; } diff --git a/src/rendering/SoGLRenderBackend.h b/src/rendering/SoGLRenderBackend.h index b946b407c58..1fb876da74c 100644 --- a/src/rendering/SoGLRenderBackend.h +++ b/src/rendering/SoGLRenderBackend.h @@ -47,6 +47,13 @@ class SoGLRenderBackend : public SoRenderBackend { GLuint normalBuffer = 0; GLuint colorBuffer = 0; GLuint texcoordBuffer = 0; + GLuint lineDistanceBuffer = 0; + GLuint lineRasterVertexArray = 0; + GLuint lineRasterPositionBuffer = 0; + GLuint lineRasterNormalBuffer = 0; + GLuint lineRasterColorBuffer = 0; + GLuint lineRasterTexcoordBuffer = 0; + GLuint lineRasterDistanceBuffer = 0; GLuint texture = 0; GLuint indexBuffer = 0; GLuint vertexArray = 0; @@ -55,11 +62,19 @@ class SoGLRenderBackend : public SoRenderBackend { const float * normalsKey = nullptr; const float * colorsKey = nullptr; const float * texcoordsKey = nullptr; + const float * lineDistanceKey = nullptr; const unsigned char * texturePixelsKey = nullptr; const uint32_t * indicesKey = nullptr; + const float * lineRasterPositionsKey = nullptr; + const float * lineRasterNormalsKey = nullptr; + const float * lineRasterColorsKey = nullptr; + const float * lineRasterTexcoordsKey = nullptr; + const uint32_t * lineRasterIndicesKey = nullptr; uint32_t vertexCount = 0; uint32_t normalCount = 0; uint32_t indexCount = 0; + uint32_t lineRasterVertexCount = 0; + uint32_t lineRasterIndexCount = 0; uint32_t vertexStride = 0; uint32_t texcoordStride = 0; int textureWidth = 0; @@ -73,63 +88,167 @@ class SoGLRenderBackend : public SoRenderBackend { uint32_t cacheGeneration = 0; }; + struct SurfaceUniforms { + struct Transforms { + GLint view = -1; + GLint projection = -1; + GLint model = -1; + } transforms; + struct Material { + GLint color = -1; + GLint useVertexColor = -1; + GLint shadingModel = -1; + GLint emissiveColor = -1; + GLint ambient = -1; + GLint specular = -1; + GLint shininess = -1; + GLint twoSidedLighting = -1; + GLint vertexColorAlphaIncludesOpacity = -1; + } material; + struct Lighting { + GLint ambient = -1; + GLint lightCount = -1; + GLint lightType = -1; + GLint lightColor = -1; + GLint lightDirection = -1; + GLint lightPosition = -1; + GLint lightAttenuation = -1; + GLint lightSpotParams = -1; + } lighting; + struct Texture { + GLint sampler = -1; + GLint enabled = -1; + GLint alphaIncludesOpacity = -1; + GLint hasAlpha = -1; + GLint model = -1; + GLint blendColor = -1; + } texture; + struct AlphaTest { + GLint function = -1; + GLint reference = -1; + } alphaTest; + }; + struct VisualProgram { GLuint handle = 0; + SurfaceUniforms surface; + } visualProgram; + + struct LineProgram { + GLuint handle = 0; + SurfaceUniforms surface; + struct RasterUniforms { + GLint lineWidth = -1; + GLint viewportSize = -1; + GLint stipplePattern = -1; + GLint stippleScale = -1; + GLint cullBackFaces = -1; + GLint frontFaceCCW = -1; + } raster; + }; + + struct PointProgram { + GLuint handle = 0; + SurfaceUniforms surface; + struct RasterUniforms { + GLint pointSize = -1; + GLint viewportSize = -1; + GLint cullBackFaces = -1; + GLint frontFaceCCW = -1; + } raster; + }; + struct PixelProgram { + GLuint handle = 0; struct Uniforms { - struct Transforms { - GLint view = -1; - GLint projection = -1; - GLint model = -1; - } transforms; - struct Material { - GLint color = -1; - GLint useVertexColor = -1; - GLint shadingModel = -1; - GLint emissiveColor = -1; - GLint ambient = -1; - GLint specular = -1; - GLint shininess = -1; - GLint twoSidedLighting = -1; - GLint vertexColorAlphaIncludesOpacity = -1; - } material; - struct Lighting { - GLint ambient = -1; - GLint lightCount = -1; - GLint lightType = -1; - GLint lightColor = -1; - GLint lightDirection = -1; - GLint lightPosition = -1; - GLint lightAttenuation = -1; - GLint lightSpotParams = -1; - } lighting; - struct Texture { - GLint sampler = -1; - GLint enabled = -1; - GLint alphaIncludesOpacity = -1; - GLint hasAlpha = -1; - GLint model = -1; - GLint blendColor = -1; - } texture; - struct AlphaTest { - GLint function = -1; - GLint reference = -1; - } alphaTest; + GLint view = -1; + GLint projection = -1; + GLint model = -1; + GLint quadCenter = -1; + GLint sourceSize = -1; + GLint rasterSize = -1; + GLint viewportOrigin = -1; + GLint viewportSize = -1; + GLint pixelOrigin = -1; + GLint texture = -1; + GLint alphaTestFunction = -1; + GLint alphaTestReference = -1; } uniforms; - } visualProgram; + }; + + struct RasterPrograms { + LineProgram line; + LineProgram triangleLine; + PointProgram point; + PointProgram trianglePoint; + PixelProgram pixel; + float nativeLineWidthMax = 1.0f; + float nativePointSizeMax = 1.0f; + } rasterPrograms; + + struct RasterPath { + GLenum primitive = GL_TRIANGLES; + bool textured = false; + bool pixelRaster = false; + bool usePointShader = false; + bool useLineShader = false; + bool lineTriangleInput = false; + bool pointTriangleInput = false; + bool expandedLineStream = false; + bool linePrimitive = false; + bool pointPrimitive = false; + bool filledPrimitive = true; + float pointSize = 1.0f; + float lineWidth = 1.0f; + }; bool createShaders(); - bool createVisualProgram(); void beginFrame(const SoRenderParams & params); void invalidateCache(); void updateGeometryCache(const SoDrawList & drawlist); + void updateLineDistances(CachedCommand & entry, + const SoRenderCommand & command, + const SbMat & viewMat, + const SbMat & projMat, + const SbVec2s & viewportSize); + void updateIndexedLineRasterStream(CachedCommand & entry, + const SoRenderCommand & command, + const SbMat & viewMat, + const SbMat & projMat, + const SbVec2s & viewportSize); + void setupLineRasterVAO(CachedCommand & entry); + void destroyLineRasterStream(CachedCommand & entry); void drawCommand(const SoDrawList & drawlist, const SoRenderCommand & command, const SbMat & viewMat, const SbMat & projMat, const SoRenderParams & params); + RasterPath selectRasterPath(const CachedCommand & entry, + const SoRenderCommand & command, + const SoRenderParams & params) const; + void applyDepthState(const SoRenderCommand & command); + void applyRasterState(const SoRenderCommand & command, + const RasterPath & path); + void applyBlendState(const SoRenderCommand & command); + bool applyPolygonOffset(const SoRenderCommand & command, + const RasterPath & path, + GLenum & target); + void bindCommandProgram(const SoDrawList & drawlist, + const SoRenderCommand & command, + const RasterPath & path, + const SbMat & viewMat, + const SbMat & projMat, + const SoRenderParams & params, + const CachedCommand & entry); + void drawGeometry(const SoRenderCommand & command, + const RasterPath & path, + const CachedCommand & entry); + void restoreRasterState(const RasterPath & path, + GLenum polygonOffsetTarget, + bool polygonOffsetEnabled); void uploadLighting(const SoDrawList & drawlist, - const SoRenderCommand & command); + const SoRenderCommand & command, + const SurfaceUniforms & uniforms); CachedCommand & getOrCreateCache(const SoRenderCommand * command); void uploadGeometry(CachedCommand & entry, @@ -137,36 +256,52 @@ class SoGLRenderBackend : public SoRenderBackend { void uploadVertexBuffers(CachedCommand & entry, const SoGeometryDesc & geometry); void uploadTexture(CachedCommand & entry, - const SoGeometryDesc & geometry, - const SoTextureData & texture); + const SoRenderCommand & command); + void uploadLineDistanceBuffer(CachedCommand & entry, + const SoGeometryDesc & geometry, + GLsizei vertexStride); void uploadIndices(CachedCommand & entry, const SoGeometryDesc & geometry); void updateCacheDescription(CachedCommand & entry, const SoRenderCommand & command, - bool hasTexture, - uint32_t vertexStride); + GLsizei vertexStride); void setupVisualVAO(CachedCommand & entry); void destroyCacheEntry(CachedCommand & entry); - void bindVisualCommand(const SoDrawList & drawlist, - const SoRenderCommand & command, - const CachedCommand & entry, - const SbMat & viewMat, - const SbMat & projMat, - const SoRenderParams & params); - void bindTransforms(const SoRenderCommand & command, - const SbMat & viewMat, - const SbMat & projMat); - void bindMaterial(const SoRenderCommand & command, - const CachedCommand & entry); - void applyDepthState(const SoRenderCommand & command); - void applyBlendState(const SoRenderCommand & command); - void bindAlphaTest(const SoRenderCommand & command); - void bindTexture(const SoRenderCommand & command, - const CachedCommand & entry); - void drawGeometry(const SoRenderCommand & command, - const CachedCommand & entry); bool textureDescriptionMatches(const CachedCommand & entry, const SoRenderCommand & command) const; + void bindRasterCommon(const SoDrawList & drawlist, + const SoRenderCommand & command, + const SbMat & viewMat, + const SbMat & projMat, + const SbVec4f & color, + bool useVertexColor, + bool textured, + const SurfaceUniforms & uniforms); + void bindLineShader(const SoRenderCommand & command, + const SbMat & viewMat, + const SbMat & projMat, + const SbVec4f & color, + bool useVertexColor, + float lineWidth, + const SbVec2s & viewportSize, + bool triangleInput, + const SoDrawList & drawlist, + bool textured); + void bindPointShader(const SoRenderCommand & command, + const SbMat & viewMat, + const SbMat & projMat, + const SbVec4f & color, + bool useVertexColor, + float pointSize, + const SbVec2s & viewportSize, + bool triangleInput, + const SoDrawList & drawlist, + bool textured); + void bindPixelShader(const SoRenderCommand & command, + const SbMat & viewMat, + const SbMat & projMat, + const SbVec2s & viewportOrigin, + const SbVec2s & viewportSize); const cc_glglue * glue = nullptr; std::vector gpuCache; diff --git a/src/rendering/SoRenderBackend.h b/src/rendering/SoRenderBackend.h index 526a25813c4..39d3dbbd8c6 100644 --- a/src/rendering/SoRenderBackend.h +++ b/src/rendering/SoRenderBackend.h @@ -28,6 +28,7 @@ struct SoRenderParams { SbViewportRegion viewport; SbMatrix viewMatrix; SbMatrix projMatrix; + float devicePixelRatio = 1.0f; SbColor4f clearColor; float clearDepth = 1.0f; uint32_t flags = 0; diff --git a/src/rendering/SoRenderIR.cpp b/src/rendering/SoRenderIR.cpp index 554fdc1104e..2d38bcb54e8 100644 --- a/src/rendering/SoRenderIR.cpp +++ b/src/rendering/SoRenderIR.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -20,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -477,21 +479,29 @@ textureHasTransparency(const SoTextureData & texture) } void -fillCommandStateFromAction(SoIRRenderAction * action, - SoRenderCommand & command, - const int materialIndex) +fillCommandTraversalStateFromAction(SoIRRenderAction * action, + SoRenderCommand & command) { SoState * state = action->getState(); SoDrawList & drawlist = action->getMutableDrawList(); command.modelMatrix = SoModelMatrixElement::get(state); command.viewMatrix = SoViewingMatrixElement::get(state); command.projMatrix = SoProjectionMatrixElement::get(state); - fillMaterialFromState(state, command.material, materialIndex); - fillTextureFromState(state, action, command.material); fillRenderStateFromState(state, command.state); command.lightingHandle = fillLightingFromState(state, drawlist); } +void +fillCommandStateFromAction(SoIRRenderAction * action, + SoRenderCommand & command, + const int materialIndex) +{ + SoState * state = action->getState(); + fillCommandTraversalStateFromAction(action, command); + fillMaterialFromState(state, command.material, materialIndex); + fillTextureFromState(state, action, command.material); +} + void fillMaterialFromState(SoState * state, SoMaterialData & material, int materialIndex) @@ -624,6 +634,9 @@ fillRenderStateFromState(SoState * state, SoRenderState & rs) : SO_ALPHA_TEST_POLICY_EXPLICIT; SoDrawStyleElement::Style style = SoDrawStyleElement::get(mutableState); + const SoShapeStyleElement * shapeStyle = SoShapeStyleElement::get(mutableState); + rs.raster.visible = style != SoDrawStyleElement::INVISIBLE && + (!shapeStyle || !(shapeStyle->getFlags() & SoShapeStyleElement::INVISIBLE)); SoRasterFillMode fillmode = SO_RASTER_FILL; switch (style) { case SoDrawStyleElement::LINES: @@ -640,19 +653,26 @@ fillRenderStateFromState(SoState * state, SoRenderState & rs) // Native GL_POINTS are square unless point smoothing is enabled. Keep the // primitive shape explicit in the IR so backends do not choose independently. - // Backface culling from SoShapeHintsElement: - // vertexOrdering == COUNTERCLOCKWISE + shapeType == SOLID → cull back faces + // Backface culling from SoShapeHintsElement. Vertex ordering selects the + // front-face winding; a solid shape requests back-face culling regardless + // of whether its front faces are clockwise or counter-clockwise. { SoShapeHintsElement::VertexOrdering vo; SoShapeHintsElement::ShapeType st; SoShapeHintsElement::FaceType ft; SoShapeHintsElement::get(mutableState, vo, st, ft); - rs.raster.cullMode = (vo == SoShapeHintsElement::COUNTERCLOCKWISE - && st == SoShapeHintsElement::SOLID) ? 1 : 0; + rs.raster.cullBackFaces = st == SoShapeHintsElement::SOLID && + (vo == SoShapeHintsElement::CLOCKWISE || + vo == SoShapeHintsElement::COUNTERCLOCKWISE); + rs.raster.frontFaceCCW = vo != SoShapeHintsElement::CLOCKWISE; } rs.raster.scissorEnabled = FALSE; rs.raster.lineWidth = SoLineWidthElement::get(mutableState); rs.raster.pointSize = SoPointSizeElement::get(mutableState); + rs.raster.linePattern = static_cast( + SoLinePatternElement::get(mutableState)); + rs.raster.linePatternScale = static_cast(std::max( + 1, SoLinePatternElement::getScaleFactor(mutableState))); const SbViewportRegion & viewport = SoViewportRegionElement::get(mutableState); const SbVec2s & viewportOrigin = viewport.getViewportOriginPixels(); @@ -675,7 +695,12 @@ fillRenderStateFromState(SoState * state, SoRenderState & rs) } rs.raster.polygonOffsetFactor = offsetfactor; rs.raster.polygonOffsetUnits = offsetunits; - + rs.raster.polygonOffsetFilled = offseton && + (offsetstyle & SoPolygonOffsetElement::FILLED); + rs.raster.polygonOffsetLines = offseton && + (offsetstyle & SoPolygonOffsetElement::LINES); + rs.raster.polygonOffsetPoints = offseton && + (offsetstyle & SoPolygonOffsetElement::POINTS); } SoLightingHandle diff --git a/src/rendering/SoRenderIRP.h b/src/rendering/SoRenderIRP.h index 6f0178b55c7..6fd38a7cf6d 100644 --- a/src/rendering/SoRenderIRP.h +++ b/src/rendering/SoRenderIRP.h @@ -57,7 +57,10 @@ SbBool coin_render_ir_trace_enabled(); \brief Helper functions for converting Coin state and caches into render IR. */ namespace SoRenderIR { -//! Capture the ordinary traversal state shared by retained shape producers. +//! Capture matrices, render state, and lighting shared by retained producers. +void fillCommandTraversalStateFromAction(SoIRRenderAction * action, + SoRenderCommand & command); +//! Capture ordinary shape state, including material and inherited texture. void fillCommandStateFromAction(SoIRRenderAction * action, SoRenderCommand & command, int materialIndex = 0); diff --git a/src/shapenodes/SoImage.cpp b/src/shapenodes/SoImage.cpp index 89c16b3529c..916e0fdfb4b 100644 --- a/src/shapenodes/SoImage.cpp +++ b/src/shapenodes/SoImage.cpp @@ -158,9 +158,12 @@ #include #include #include +#include #include #include #include +#include +#include #include #include #include @@ -177,6 +180,9 @@ #include "nodes/SoSubNodeP.h" #include "glue/GLUWrapper.h" #include "glue/simage_wrapper.h" +#include "rendering/SoRenderIRP.h" + +#include /*! @@ -530,7 +536,103 @@ SoImage::GLRender(SoGLRenderAction * action) } #endif - // doc from parent +void +SoImage::IRRender(SoIRRenderAction * action) +{ + if (!action) return; + + const SbVec2s size = this->getSize(); + SbVec2s sourceSize; + int numComponents = 0; + const unsigned char * dataptr = this->image.getValue(sourceSize, + numComponents); + if (!dataptr || sourceSize[0] <= 0 || sourceSize[1] <= 0 || + size[0] <= 0 || size[1] <= 0) return; + + SoState * state = action->getState(); + const SbVec3f nilpoint = SoImage::getNilpoint(state); + int originX = static_cast(nilpoint[0]); + switch (this->horAlignment.getValue()) { + case SoImage::RIGHT: originX -= size[0]; break; + case SoImage::CENTER: originX -= size[0] >> 1; break; + case SoImage::LEFT: break; + } + int originY = static_cast(nilpoint[1]); + switch (this->vertAlignment.getValue()) { + case SoImage::TOP: originY -= size[1]; break; + case SoImage::HALF: originY -= size[1] >> 1; break; + case SoImage::BOTTOM: break; + } + + SbVec3f v0, v1, v2, v3; + this->getQuad(state, v0, v1, v2, v3); + const float positionData[] = { + v0[0], v0[1], v0[2], v1[0], v1[1], v1[2], v2[0], v2[1], v2[2], + v0[0], v0[1], v0[2], v2[0], v2[1], v2[2], v3[0], v3[1], v3[2] + }; + const float normalData[] = { + 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f + }; + const float texcoordData[] = { + 0.0f, 0.0f, 0.0f, 1.0f, + 1.0f, 0.0f, 0.0f, 1.0f, + 1.0f, 1.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 0.0f, 1.0f, + 1.0f, 1.0f, 0.0f, 1.0f, + 0.0f, 1.0f, 0.0f, 1.0f + }; + float * positions = static_cast( + action->allocateGeometryStorage(sizeof(positionData), alignof(float))); + float * normals = static_cast( + action->allocateGeometryStorage(sizeof(normalData), alignof(float))); + float * texcoords = static_cast( + action->allocateGeometryStorage(sizeof(texcoordData), alignof(float))); + std::memcpy(positions, positionData, sizeof(positionData)); + std::memcpy(normals, normalData, sizeof(normalData)); + std::memcpy(texcoords, texcoordData, sizeof(texcoordData)); + + this->testTransparency(); + const size_t byteCount = static_cast(sourceSize[0]) * + static_cast(sourceSize[1]) * + static_cast(numComponents); + unsigned char * pixels = static_cast( + action->allocateGeometryStorage(byteCount, alignof(unsigned char))); + std::memcpy(pixels, dataptr, byteCount); + + SoRenderCommand command = {}; + command.geometry.topology = SO_TOPOLOGY_TRIANGLES; + command.geometry.vertexCount = 6; + command.geometry.normalCount = 6; + command.geometry.vertexStride = sizeof(float) * 3; + command.geometry.texcoordStride = sizeof(float) * 4; + command.geometry.positions = positions; + command.geometry.normals = normals; + command.geometry.texcoords = texcoords; + SoRenderIR::fillCommandTraversalStateFromAction(action, command); + SoRenderIR::fillMaterialFromState(state, command.material); + command.material.diffuse.setValue(1.0f, 1.0f, 1.0f, 1.0f); + command.material.opacity = 1.0f; + command.material.shadingModel = SO_SHADING_UNLIT; + command.material.textureAlphaIncludesOpacity = true; + command.material.texture.pixels = pixels; + command.material.texture.width = sourceSize[0]; + command.material.texture.height = sourceSize[1]; + command.material.texture.numComponents = numComponents; + command.material.texture.wrapS = SO_TEXTURE_WRAP_CLAMP_TO_EDGE; + command.material.texture.wrapT = SO_TEXTURE_WRAP_CLAMP_TO_EDGE; + command.pixelRaster.enabled = TRUE; + command.pixelRaster.originX = originX; + command.pixelRaster.originY = originY; + command.pixelRaster.width = size[0]; + command.pixelRaster.height = size[1]; + SoRenderIR::ensureMaterialBlendState(command.state, command.material); + command.opacityClass = (this->transparency || + SoRenderIR::isMaterialTransparent(command.material)) + ? SO_OPACITY_TRANSPARENT : SO_OPACITY_OPAQUE; + action->addCommand(command); +} + // doc from parent void SoImage::rayPick(SoRayPickAction * action) diff --git a/src/shapenodes/SoText2.cpp b/src/shapenodes/SoText2.cpp index 20ac6e91ccf..9a13a3941ee 100644 --- a/src/shapenodes/SoText2.cpp +++ b/src/shapenodes/SoText2.cpp @@ -116,6 +116,7 @@ #include #include #include +#include #include #include #include @@ -128,6 +129,7 @@ #include #include #include +#include #include #include #include @@ -135,6 +137,7 @@ #include #include #include +#include #if COIN_BUILD_LEGACY_GL_RENDERER #include #endif @@ -146,6 +149,7 @@ #include "nodes/SoSubNodeP.h" #include "caches/SoGlyphCache.h" #include "shapenodes/SoShapeGLRenderP.h" +#include "rendering/SoRenderIRP.h" // The "lean and mean" define is a workaround for a Cygwin bug: when // windows.h is included _after_ one of the X11 or GLX headers above @@ -247,6 +251,35 @@ struct SoTextRasterResult { } }; +static SbBool +so_text2_texture_has_transparency(SoState * state) +{ + if (!state || !SoMultiTextureEnabledElement::get(state, 0)) return FALSE; + + SbVec2s size; + int numComponents = 0; + SoMultiTextureImageElement::Wrap wrapS; + SoMultiTextureImageElement::Wrap wrapT; + SoMultiTextureImageElement::Model model; + SbColor blendColor; + const unsigned char * bytes = SoMultiTextureImageElement::get( + state, 0, size, numComponents, wrapS, wrapT, model, blendColor); + if (!bytes || size[0] <= 0 || size[1] <= 0 || + (numComponents != 2 && numComponents != 4)) { + return FALSE; + } + + const size_t pixelCount = static_cast(size[0]) * + static_cast(size[1]); + const int alphaOffset = numComponents - 1; + for (size_t pixel = 0; pixel < pixelCount; ++pixel) { + if (bytes[pixel * static_cast(numComponents) + alphaOffset] != 255) { + return TRUE; + } + } + return FALSE; +} + class SoText2P { public: SoText2P(SoText2 * textnode) : maxwidth(0), master(textnode) @@ -367,6 +400,169 @@ SoText2::initClass(void) SO_NODE_INTERNAL_INIT_CLASS(SoText2, SO_FROM_INVENTOR_2_1); } +void +SoText2::IRRender(SoIRRenderAction * action) +{ + if (!action) return; + + SoState * state = action->getState(); + if (!state) return; + + const SoShapeStyleElement * shapeStyle = SoShapeStyleElement::get(state); + if (shapeStyle && (shapeStyle->getFlags() & SoShapeStyleElement::INVISIBLE)) { + return; + } + + state->push(); + SoLazyElement::setLightModel(state, SoLazyElement::BASE_COLOR); + + PRIVATE(this)->lock(); + SoTextRasterResult raster; + if (!PRIVATE(this)->getRasterResult(state, raster)) { + PRIVATE(this)->unlock(); + state->pop(); + return; + } + + SbVec3f v0, v1, v2, v3; + if (!PRIVATE(this)->getQuad(state, v0, v1, v2, v3)) { + PRIVATE(this)->unlock(); + state->pop(); + return; + } + + const SbColor & diffuse = SoLazyElement::getDiffuse(state, 0); + const unsigned char red = static_cast(diffuse[0] * 255.0f); + const unsigned char green = static_cast(diffuse[1] * 255.0f); + const unsigned char blue = static_cast(diffuse[2] * 255.0f); + const unsigned int alpha = static_cast( + (1.0f - SoLazyElement::getTransparency(state, 0)) * 256.0f); + + const float positionData[] = { + v0[0], v0[1], v0[2], + v1[0], v1[1], v1[2], + v2[0], v2[1], v2[2], + v0[0], v0[1], v0[2], + v2[0], v2[1], v2[2], + v3[0], v3[1], v3[2] + }; + const float normalData[] = { + 0.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 1.0f + }; + const float texcoordData[] = { + 0.0f, 0.0f, 0.0f, 1.0f, + 1.0f, 0.0f, 0.0f, 1.0f, + 1.0f, 1.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 0.0f, 1.0f, + 1.0f, 1.0f, 0.0f, 1.0f, + 0.0f, 1.0f, 0.0f, 1.0f + }; + + float * positions = static_cast( + action->allocateGeometryStorage(sizeof(positionData), alignof(float))); + float * normals = static_cast( + action->allocateGeometryStorage(sizeof(normalData), alignof(float))); + float * texcoords = static_cast( + action->allocateGeometryStorage(sizeof(texcoordData), alignof(float))); + std::memcpy(positions, positionData, sizeof(positionData)); + std::memcpy(normals, normalData, sizeof(normalData)); + std::memcpy(texcoords, texcoordData, sizeof(texcoordData)); + + SoRenderCommand baseCommand = {}; + baseCommand.geometry.topology = SO_TOPOLOGY_TRIANGLES; + baseCommand.geometry.vertexCount = 6; + baseCommand.geometry.normalCount = 6; + baseCommand.geometry.vertexStride = sizeof(float) * 3; + baseCommand.geometry.texcoordStride = sizeof(float) * 4; + baseCommand.geometry.positions = positions; + baseCommand.geometry.normals = normals; + baseCommand.geometry.texcoords = texcoords; + SoRenderIR::fillCommandTraversalStateFromAction(action, baseCommand); + SoRenderIR::fillMaterialFromState(state, baseCommand.material); + + const unsigned int shapeFlags = shapeStyle ? shapeStyle->getFlags() : 0; + const bool transparent = + (shapeFlags & (SoShapeStyleElement::TRANSP_TEXTURE | + SoShapeStyleElement::TRANSP_MATERIAL)) != 0 || + so_text2_texture_has_transparency(state); + SoRenderIR::ensureMaterialBlendState(baseCommand.state, + baseCommand.material); + if (transparent && !baseCommand.state.blend.enabled) { + baseCommand.state.blend.enabled = TRUE; + baseCommand.state.blend.srcRGBFactor = SO_BLEND_FACTOR_SRC_ALPHA; + baseCommand.state.blend.dstRGBFactor = SO_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + baseCommand.state.blend.srcAlphaFactor = SO_BLEND_FACTOR_SRC_ALPHA; + baseCommand.state.blend.dstAlphaFactor = SO_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + baseCommand.state.blend.rgbEquation = SO_BLEND_EQUATION_ADD; + baseCommand.state.blend.alphaEquation = SO_BLEND_EQUATION_ADD; + } + + const auto emitRasterCommand = [&](SbBool binary) { + SoRenderCommand command = baseCommand; + const size_t pixelBytes = static_cast(raster.antialiasedSize[0]) * + static_cast(raster.antialiasedSize[1]) * 4; + unsigned char * pixels = static_cast( + action->allocateGeometryStorage(pixelBytes, alignof(unsigned char))); + if (binary) { + PRIVATE(this)->fillBinaryPixelBuffer(raster, red, green, blue, alpha, + pixels); + } + else { + PRIVATE(this)->fillAntialiasedPixelBuffer( + raster, red, green, blue, alpha, pixels); + } + + // getQuad() already converts the text bounds into camera-facing geometry. + // Keep the exact generated quad instead of asking the backend to recenter + // it through the generic billboard path. + command.material.diffuse[3] = 1.0f; + command.material.opacity = 1.0f; + command.material.shadingModel = SO_SHADING_UNLIT; + command.material.texture.pixels = pixels; + command.material.texture.width = raster.antialiasedSize[0]; + command.material.texture.height = raster.antialiasedSize[1]; + command.material.texture.numComponents = 4; + command.material.texture.wrapS = SO_TEXTURE_WRAP_CLAMP_TO_EDGE; + command.material.texture.wrapT = SO_TEXTURE_WRAP_CLAMP_TO_EDGE; + command.material.textureAlphaIncludesOpacity = true; + command.pixelRaster.enabled = TRUE; + const SbVec2f & origin = binary + ? raster.binaryOrigin : raster.antialiasedOrigin; + command.pixelRaster.originX = origin[0]; + command.pixelRaster.originY = origin[1]; + command.pixelRaster.width = raster.antialiasedSize[0]; + command.pixelRaster.height = raster.antialiasedSize[1]; + + // The alpha test removes zero-coverage texels from the binary command; + // the antialiased command keeps the historical SoText2 threshold. + command.state.alphaTest.policy = SO_ALPHA_TEST_POLICY_EXPLICIT; + command.state.alphaTest.function = SO_ALPHA_TEST_GREATER; + command.state.alphaTest.reference = binary ? 0.0f : 0.3f; + if (!binary) { + command.state.blend.enabled = TRUE; + command.state.blend.srcRGBFactor = SO_BLEND_FACTOR_SRC_ALPHA; + command.state.blend.dstRGBFactor = SO_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + command.state.blend.srcAlphaFactor = SO_BLEND_FACTOR_SRC_ALPHA; + command.state.blend.dstAlphaFactor = SO_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + } + + command.opacityClass = transparent + ? SO_OPACITY_TRANSPARENT : SO_OPACITY_OPAQUE; + action->addCommand(command); + }; + + if (!raster.binaryGlyphs.empty()) emitRasterCommand(TRUE); + if (raster.hasAntialiasedGlyphs) emitRasterCommand(FALSE); + + PRIVATE(this)->unlock(); + state->pop(); +} + // ************************************************************************** // doc in super diff --git a/testsuite/CMakeLists.txt b/testsuite/CMakeLists.txt index 7774a9d5ab0..edd0f9f025c 100644 --- a/testsuite/CMakeLists.txt +++ b/testsuite/CMakeLists.txt @@ -232,6 +232,16 @@ target_include_directories(RetainedMaterialLightingTest PRIVATE add_test(NAME RetainedMixedTopologyTest COMMAND RetainedMixedTopologyTest) add_test(NAME RetainedMaterialLightingTest COMMAND RetainedMaterialLightingTest) + +add_executable(RetainedRasterTextTest RetainedRasterTextTest.cpp) +target_link_libraries(RetainedRasterTextTest Coin ${COIN_TARGET_LINK_LIBRARIES}) +target_include_directories(RetainedRasterTextTest PRIVATE + ${PROJECT_SOURCE_DIR}/include + ${PROJECT_BINARY_DIR}/include + ${COIN_TARGET_INCLUDE_DIRECTORIES} +) +add_test(NAME RetainedRasterTextTest COMMAND RetainedRasterTextTest) + if(HAVE_EGL) add_executable(EGLBindingTest EGLBindingTest.cpp) target_link_libraries(EGLBindingTest Coin ${COIN_TARGET_LINK_LIBRARIES}) @@ -249,6 +259,7 @@ endif() if(COIN_BUILD_GL_TESTS_EFFECTIVE) coin_add_gl_test(RetainedMaterialLightingGLTest RetainedMaterialLightingGLTest.cpp) + coin_add_gl_test(RetainedRasterGLTest RetainedRasterGLTest.cpp) coin_add_gl_test(DrawListGLTest DrawListGLTest.cpp) coin_add_gl_test(GLSLRuntimeTest GLSLRuntimeTest.cpp) coin_add_gl_test(GLSLDiagnosticsTest GLSLDiagnosticsTest.cpp) diff --git a/testsuite/RetainedRasterGLTest.cpp b/testsuite/RetainedRasterGLTest.cpp new file mode 100644 index 00000000000..023ecbf8b41 --- /dev/null +++ b/testsuite/RetainedRasterGLTest.cpp @@ -0,0 +1,698 @@ +#include "rendering/SoGLRenderBackend.h" +#include "rendering/SoRenderPlan.h" +#include "support/GLTestContext.h" + +#include +#include +#include +#include +#include + +#include +#include + +namespace { + +int skip(const char * reason) +{ + std::cout << "SKIP: " << reason << std::endl; + return 77; +} + +struct Fixture { + GLTestContext context; + SoGLRenderBackend backend; + + int initialize() + { + GLTestContextConfig config; + config.profile = GLTestProfile::Core; + config.major = 3; + config.minor = 3; + config.width = 64; + config.height = 64; + if (!context.initialize(config)) return 77; + SoRenderBackendInitParams init = {}; + if (backend.initialize(init)) return 0; + context.shutdown(); + return 1; + } + + std::vector render(const SoDrawList & drawlist, + const SbVec4f & clearColor, + float dpr = 1.0f, + const SbVec2s & viewportOrigin = SbVec2s(0, 0), + const SbVec2s & viewportSize = SbVec2s(64, 64)) + { + SoRenderParams params = {}; + params.viewport = SbViewportRegion(64, 64); + params.viewport.setViewportPixels(viewportOrigin, viewportSize); + params.viewMatrix.makeIdentity(); + params.projMatrix.makeIdentity(); + params.devicePixelRatio = dpr; + params.clearColor = clearColor; + params.clearDepth = 1.0f; + params.flags = SO_PARAM_CLEAR_WINDOW | SO_PARAM_CLEAR_DEPTH; + SoRenderPlanner planner; + SoRenderPlan plan; + planner.build(drawlist, plan); + backend.render(drawlist, plan, params); + glFinish(); + return context.readPixels(); + } + + void shutdown() + { + backend.shutdown(); + context.shutdown(); + } +}; + +std::vector renderNode(Fixture & fixture, SoNode * root, + const SbVec4f & clearColor) +{ + SoIRRenderAction action(SbViewportRegion(64, 64)); + action.apply(root); + return fixture.render(action.getDrawList(), clearColor); +} + +const uint8_t * pixelAt(const std::vector & pixels, int x, int y) +{ + return &pixels[static_cast(y * 64 + x) * 4]; +} + +bool check(bool condition, const char * message) +{ + if (!condition) std::cerr << "FAIL: " << message << std::endl; + return condition; +} + +SoRenderCommand coloredCommand(SoPrimitiveTopology topology, + const float * positions, + uint32_t vertexCount, + const SbVec4f & color) +{ + SoRenderCommand command; + command.modelMatrix.makeIdentity(); + command.geometry.topology = topology; + command.geometry.positions = positions; + command.geometry.vertexCount = vertexCount; + command.geometry.vertexStride = sizeof(float) * 3; + command.material.diffuse = color; + command.material.shadingModel = SO_SHADING_UNLIT; + return command; +} + +bool testWideLine(Fixture & fixture) +{ + const float positions[] = { -0.8f, 0.0f, 0.0f, 0.8f, 0.0f, 0.0f }; + SoDrawList drawlist; + SoRenderCommand command = coloredCommand(SO_TOPOLOGY_LINES, positions, 2, + SbVec4f(1, 0, 0, 1)); + command.state.raster.lineWidth = 4.0f; + drawlist.addCommand(command); + const std::vector pixels = fixture.render(drawlist, + SbVec4f(0, 0, 0, 1), 2.0f); + const uint8_t * center = pixelAt(pixels, 32, 32); + const uint8_t * edge = pixelAt(pixels, 32, 35); + return check(center[0] > 200 && center[1] < 50 && + edge[0] > 150 && edge[1] < 80, + "wide-line geometry shader did not apply DPR-scaled width"); +} + +bool testPointSize(Fixture & fixture) +{ + const float position[] = { 0.0f, 0.0f, 0.0f }; + SoDrawList drawlist; + SoRenderCommand command = coloredCommand(SO_TOPOLOGY_POINTS, position, 1, + SbVec4f(0, 1, 0, 1)); + command.state.raster.pointSize = 12.0f; + drawlist.addCommand(command); + const std::vector pixels = fixture.render(drawlist, + SbVec4f(0, 0, 0, 1)); + const uint8_t * center = pixelAt(pixels, 32, 32); + return check(center[1] > 200 && center[0] < 50, + "point-size pipeline did not render the point"); +} + +bool testFullLinePattern(Fixture & fixture) +{ + const float positions[] = { -0.8f, 0.0f, 0.0f, 0.8f, 0.0f, 0.0f }; + SoDrawList drawlist; + SoRenderCommand command = coloredCommand(SO_TOPOLOGY_LINES, positions, 2, + SbVec4f(1, 0, 0, 1)); + command.state.raster.lineWidth = 2.0f; + command.state.raster.linePattern = 0x0001; + command.state.raster.linePatternScale = 4; + drawlist.addCommand(command); + const std::vector pixels = fixture.render(drawlist, + SbVec4f(0, 0, 0, 1)); + int redPixels = 0; + for (int x = 8; x < 56; ++x) { + const uint8_t * pixel = pixelAt(pixels, x, 32); + if (pixel[0] > 180 && pixel[1] < 60) ++redPixels; + } + return check(redPixels > 0 && redPixels < 24, + "wide-line shader did not apply the complete 16-bit pattern"); +} + +bool testLineStripPatternContinuity(Fixture & fixture) +{ + const float stripPositions[] = { + -0.75f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, + 0.0f, 0.75f, 0.0f + }; + const float separatePositions[] = { + -0.75f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, + 0.0f, 0.75f, 0.0f + }; + auto render = [&](SoPrimitiveTopology topology, const float * positions, + uint32_t vertexCount) { + SoDrawList drawlist; + SoRenderCommand command = coloredCommand( + topology, positions, vertexCount, SbVec4f(1, 0, 0, 1)); + command.state.raster.lineWidth = 2.0f; + command.state.raster.linePattern = 0x0001; + command.state.raster.linePatternScale = 4; + drawlist.addCommand(command); + return fixture.render(drawlist, SbVec4f(0, 0, 0, 1)); + }; + + const std::vector strip = render( + SO_TOPOLOGY_LINE_STRIP, stripPositions, 3); + const std::vector separate = render( + SO_TOPOLOGY_LINES, separatePositions, 4); + int stripNearCorner = 0; + int separateNearCorner = 0; + for (int y = 33; y <= 37; ++y) { + for (int x = 30; x <= 34; ++x) { + if (pixelAt(strip, x, y)[0] > 150) ++stripNearCorner; + if (pixelAt(separate, x, y)[0] > 150) ++separateNearCorner; + } + } + return check(separateNearCorner > stripNearCorner + 2, + "line-strip stipple restarted at a segment corner"); +} + +bool testIndexedLinePatternOccurrences(Fixture & fixture) +{ + const float sourcePositions[] = { + -0.8f, -0.25f, 0.0f, + 0.0f, 0.25f, 0.0f, + 0.8f, -0.25f, 0.0f + }; + const uint32_t lineIndices[] = { 0, 1, 1, 2 }; + const float expandedLinePositions[] = { + -0.8f, -0.25f, 0.0f, + 0.0f, 0.25f, 0.0f, + 0.0f, 0.25f, 0.0f, + 0.8f, -0.25f, 0.0f + }; + const float stripPositions[] = { + -0.8f, -0.25f, 0.0f, + 0.0f, 0.25f, 0.0f, + -0.8f, -0.25f, 0.0f + }; + + auto render = [&](SoPrimitiveTopology topology, const float * positions, + uint32_t vertexCount, const uint32_t * indices, + uint32_t indexCount) { + SoDrawList drawlist; + SoRenderCommand command = coloredCommand( + topology, positions, vertexCount, SbVec4f(1, 0, 0, 1)); + command.geometry.indices = indices; + command.geometry.indexCount = indexCount; + command.state.raster.lineWidth = 3.0f; + command.state.raster.linePattern = 0x0003; + command.state.raster.linePatternScale = 5; + drawlist.addCommand(command); + return fixture.render(drawlist, SbVec4f(0, 0, 0, 1)); + }; + + const std::vector indexedLines = render( + SO_TOPOLOGY_LINES, sourcePositions, 3, lineIndices, 4); + const std::vector expandedLines = render( + SO_TOPOLOGY_LINES, expandedLinePositions, 4, nullptr, 0); + if (!check(indexedLines == expandedLines, + "indexed line stipple did not preserve per-occurrence distances")) { + return false; + } + + const uint32_t stripIndices[] = { 0, 1, 0 }; + const std::vector indexedStrip = render( + SO_TOPOLOGY_LINE_STRIP, sourcePositions, 3, stripIndices, 3); + const std::vector expandedStrip = render( + SO_TOPOLOGY_LINE_STRIP, stripPositions, 3, nullptr, 0); + return check(indexedStrip == expandedStrip, + "indexed line-strip stipple did not preserve repeated vertices"); +} + +bool testEmptyLinePattern(Fixture & fixture) +{ + const float positions[] = { -0.8f, 0.0f, 0.0f, 0.8f, 0.0f, 0.0f }; + SoDrawList drawlist; + SoRenderCommand command = coloredCommand(SO_TOPOLOGY_LINES, positions, 2, + SbVec4f(1, 0, 0, 1)); + command.state.raster.lineWidth = 4.0f; + command.state.raster.linePattern = 0x0000; + drawlist.addCommand(command); + const std::vector pixels = fixture.render(drawlist, + SbVec4f(0, 0, 0, 1)); + const uint8_t * center = pixelAt(pixels, 32, 32); + return check(center[0] < 20 && center[1] < 20 && center[2] < 20, + "zero line pattern did not discard the complete line"); +} + +bool testTriangleFallbacks(Fixture & fixture) +{ + const float positions[] = { + 0.0f, 0.65f, 0.0f, + -0.65f, -0.65f, 0.0f, + 0.65f, -0.65f, 0.0f + }; + SoDrawList drawlist; + SoRenderCommand line = coloredCommand(SO_TOPOLOGY_TRIANGLES, positions, 3, + SbVec4f(1, 0, 0, 1)); + line.state.raster.fillMode = SO_RASTER_LINES; + line.state.raster.lineWidth = 4.0f; + drawlist.addCommand(line); + SoRenderCommand point = line; + point.state.raster.fillMode = SO_RASTER_POINTS; + point.state.raster.pointSize = 12.0f; + drawlist.addCommand(point); + const std::vector pixels = fixture.render(drawlist, + SbVec4f(0, 0, 0, 1)); + const uint8_t * top = pixelAt(pixels, 32, 52); + const uint8_t * center = pixelAt(pixels, 32, 11); + return check((top[0] > 150 && top[1] < 80) && + (center[0] > 150 && center[1] < 80), + "triangle line/point fallbacks did not emit raster geometry"); +} + +int countRedPixels(const std::vector & pixels) +{ + int count = 0; + for (int y = 0; y < 64; ++y) { + for (int x = 0; x < 64; ++x) { + const uint8_t * pixel = pixelAt(pixels, x, y); + if (pixel[0] > 150 && pixel[1] < 80 && pixel[2] < 80) ++count; + } + } + return count; +} + +int countGreenPixels(const std::vector & pixels) +{ + int count = 0; + for (int y = 0; y < 64; ++y) { + for (int x = 0; x < 64; ++x) { + const uint8_t * pixel = pixelAt(pixels, x, y); + if (pixel[1] > 150 && pixel[0] < 80 && pixel[2] < 80) ++count; + } + } + return count; +} + +bool testPatternedTriangleFallback(Fixture & fixture) +{ + const float positions[] = { + 0.0f, 0.65f, 0.0f, + -0.65f, -0.65f, 0.0f, + 0.65f, -0.65f, 0.0f + }; + auto renderPattern = [&](uint16_t pattern) { + SoDrawList drawlist; + SoRenderCommand command = coloredCommand( + SO_TOPOLOGY_TRIANGLES, positions, 3, SbVec4f(1, 0, 0, 1)); + command.state.raster.fillMode = SO_RASTER_LINES; + command.state.raster.lineWidth = 4.0f; + command.state.raster.linePattern = pattern; + command.state.raster.linePatternScale = 4; + drawlist.addCommand(command); + return fixture.render(drawlist, SbVec4f(0, 0, 0, 1)); + }; + + const int patterned = countRedPixels(renderPattern(0x0001)); + const int solid = countRedPixels(renderPattern(0xFFFF)); + return check(patterned > 0 && patterned < solid / 2, + "triangle wireframe fallback did not vary stipple along edges"); +} + +bool testTriangleFallbackCulling(Fixture & fixture) +{ + const float frontPositions[] = { + -0.85f, -0.55f, 0.0f, + -0.25f, -0.55f, 0.0f, + -0.55f, 0.55f, 0.0f + }; + const float backPositions[] = { + 0.85f, -0.55f, 0.0f, + 0.25f, -0.55f, 0.0f, + 0.55f, 0.55f, 0.0f + }; + + auto render = [&](bool frontFaceCCW, bool points) { + SoDrawList drawlist; + SoRenderCommand front = coloredCommand( + SO_TOPOLOGY_TRIANGLES, frontPositions, 3, SbVec4f(1, 0, 0, 1)); + SoRenderCommand back = coloredCommand( + SO_TOPOLOGY_TRIANGLES, backPositions, 3, SbVec4f(0, 1, 0, 1)); + for (SoRenderCommand * command : {&front, &back}) { + command->state.raster.cullBackFaces = TRUE; + command->state.raster.frontFaceCCW = frontFaceCCW ? TRUE : FALSE; + if (points) { + command->state.raster.fillMode = SO_RASTER_POINTS; + command->state.raster.pointSize = 512.0f; + } + else { + command->state.raster.fillMode = SO_RASTER_LINES; + command->state.raster.lineWidth = 4.0f; + // A nearly solid pattern forces the triangle line fallback without + // requiring a test-specific assumption about the native line range. + command->state.raster.linePattern = 0xFFFE; + } + } + drawlist.addCommand(front); + drawlist.addCommand(back); + return fixture.render(drawlist, SbVec4f(0, 0, 0, 1)); + }; + + const std::vector ccwLines = render(true, false); + if (!check(countRedPixels(ccwLines) > 0 && countGreenPixels(ccwLines) == 0, + "triangle line fallback did not cull a back-facing source triangle")) { + return false; + } + const std::vector cwLines = render(false, false); + if (!check(countRedPixels(cwLines) == 0 && countGreenPixels(cwLines) > 0, + "triangle line fallback did not honor clockwise front faces")) { + return false; + } + + const std::vector ccwPoints = render(true, true); + if (!check(countRedPixels(ccwPoints) > 0 && countGreenPixels(ccwPoints) == 0, + "triangle point fallback did not cull a back-facing source triangle")) { + return false; + } + const std::vector cwPoints = render(false, true); + return check(countRedPixels(cwPoints) == 0 && countGreenPixels(cwPoints) > 0, + "triangle point fallback did not honor clockwise front faces"); +} + +bool testTriangleStripFallbackCulling(Fixture & fixture) +{ + const float positions[] = { + -0.7f, -0.7f, 0.0f, + 0.7f, -0.7f, 0.0f, + -0.7f, 0.7f, 0.0f, + 0.7f, 0.7f, 0.0f + }; + SoRenderCommand command = coloredCommand( + SO_TOPOLOGY_TRIANGLE_STRIP, positions, 4, SbVec4f(1, 0, 0, 1)); + command.state.raster.cullBackFaces = TRUE; + command.state.raster.frontFaceCCW = TRUE; + command.state.raster.fillMode = SO_RASTER_LINES; + command.state.raster.lineWidth = 4.0f; + command.state.raster.linePattern = 0xFFFD; + SoDrawList drawlist; + drawlist.addCommand(command); + const std::vector pixels = fixture.render( + drawlist, SbVec4f(0, 0, 0, 1)); + const uint8_t * bottom = pixelAt(pixels, 32, 10); + const uint8_t * top = pixelAt(pixels, 32, 54); + return check(bottom[0] > 150 && top[0] > 150, + "triangle-strip fallback did not preserve source-face parity"); +} + +bool testPolygonOffsetTargets(Fixture & fixture) +{ + const float positions[] = { + -0.6f, -0.6f, 0.0f, + 0.6f, -0.6f, 0.0f, + 0.6f, 0.6f, 0.0f, + -0.6f, -0.6f, 0.0f, + 0.6f, 0.6f, 0.0f, + -0.6f, 0.6f, 0.0f + }; + + auto render = [&](SoRasterFillMode mode) { + SoDrawList drawlist; + SoRenderCommand base = coloredCommand( + SO_TOPOLOGY_TRIANGLES, positions, 6, SbVec4f(0, 0, 1, 1)); + base.state.depth.func = SO_DEPTH_LESS; + SoRenderCommand overlay = coloredCommand( + SO_TOPOLOGY_TRIANGLES, positions, 6, SbVec4f(1, 0, 0, 1)); + overlay.state.depth.func = SO_DEPTH_LESS; + overlay.state.raster.fillMode = mode; + overlay.state.raster.polygonOffsetFactor = -1.0f; + overlay.state.raster.polygonOffsetUnits = -1.0f; + if (mode == SO_RASTER_FILL) { + overlay.state.raster.polygonOffsetFilled = TRUE; + } + else if (mode == SO_RASTER_LINES) { + overlay.state.raster.polygonOffsetLines = TRUE; + overlay.state.raster.lineWidth = 8.0f; + overlay.state.raster.linePattern = 0xFFFE; + } + else { + overlay.state.raster.polygonOffsetPoints = TRUE; + overlay.state.raster.pointSize = 32.0f; + } + drawlist.addCommand(base); + drawlist.addCommand(overlay); + glEnable(GL_POLYGON_OFFSET_FILL); + glEnable(GL_POLYGON_OFFSET_LINE); + glEnable(GL_POLYGON_OFFSET_POINT); + glPolygonOffset(100.0f, 100.0f); + return fixture.render(drawlist, SbVec4f(0, 0, 0, 1)); + }; + + const std::vector filled = render(SO_RASTER_FILL); + if (!check(countRedPixels(filled) > 0, + "filled polygon offset did not move the overlay forward")) { + return false; + } + const std::vector lines = render(SO_RASTER_LINES); + if (!check(countRedPixels(lines) > 0, + "line polygon offset did not cover emulated line geometry")) { + return false; + } + const std::vector points = render(SO_RASTER_POINTS); + return check(countRedPixels(points) > 0, + "point polygon offset did not cover emulated point geometry"); +} + +bool testSemanticFallback(Fixture & fixture) +{ + const unsigned char texturePixels[] = { 255, 0, 0, 255 }; + const float linePositions[] = { + -0.75f, 0.0f, 0.0f, 0.75f, 0.0f, 0.0f + }; + const float lineTexcoords[] = { + 0.5f, 0.5f, 0.0f, 0.0f, 0.5f, 0.5f, 0.0f, 0.0f + }; + const float pointPosition[] = { 0.0f, 0.0f, 0.0f }; + const float pointTexcoord[] = { 0.5f, 0.5f, 0.0f, 0.0f }; + + auto render = [&](SoPrimitiveTopology topology, const float * positions, + uint32_t vertexCount, const float * texcoords) { + SoDrawList drawlist; + SoRenderCommand command = coloredCommand( + topology, positions, vertexCount, SbVec4f(1, 1, 1, 0.5f)); + command.geometry.texcoords = texcoords; + command.geometry.texcoordStride = sizeof(float) * 4; + command.material.texture.pixels = texturePixels; + command.material.texture.width = 1; + command.material.texture.height = 1; + command.material.texture.numComponents = 4; + command.material.texture.minFilter = SO_TEXTURE_FILTER_NEAREST; + command.material.texture.magFilter = SO_TEXTURE_FILTER_NEAREST; + command.material.texture.model = SO_TEXTURE_MODEL_MODULATE; + command.material.textureAlphaIncludesOpacity = false; + command.state.blend.enabled = TRUE; + command.state.blend.srcRGBFactor = SO_BLEND_FACTOR_SRC_ALPHA; + command.state.blend.dstRGBFactor = SO_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + command.state.blend.srcAlphaFactor = SO_BLEND_FACTOR_SRC_ALPHA; + command.state.blend.dstAlphaFactor = SO_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + if (topology == SO_TOPOLOGY_LINES) { + command.state.raster.lineWidth = 4.0f; + command.state.raster.linePattern = 0xFFFD; + } + else { + command.state.raster.pointSize = 512.0f; + } + drawlist.addCommand(command); + return fixture.render(drawlist, SbVec4f(0, 0, 1, 1)); + }; + + const std::vector line = render( + SO_TOPOLOGY_LINES, linePositions, 2, lineTexcoords); + const std::vector point = render( + SO_TOPOLOGY_POINTS, pointPosition, 1, pointTexcoord); + const uint8_t * linePixel = pixelAt(line, 32, 32); + const uint8_t * pointPixel = pixelAt(point, 32, 32); + const auto hasBlendedRed = [](const uint8_t * pixel) { + return pixel[0] > 60 && pixel[0] < 200 && + pixel[2] > 60 && pixel[2] < 200 && pixel[1] < 40; + }; + return check(hasBlendedRed(linePixel) && hasBlendedRed(pointPixel), + "line/point fallback did not preserve texture and alpha semantics"); +} + +bool testImageNode(Fixture & fixture) +{ + const unsigned char pixels[] = { + 255, 0, 0, 255, 0, 255, 0, 255, + 0, 0, 255, 255, 255, 255, 0, 255 + }; + SoSeparator * root = new SoSeparator; + root->ref(); + SoImage * image = new SoImage; + image->image.setValue(SbVec2s(2, 2), 4, pixels); + image->width = 4; + image->height = 4; + root->addChild(image); + SoIRRenderAction action(SbViewportRegion(64, 64)); + action.apply(root); + bool retainedSourceAndFootprint = false; + for (int i = 0; i < action.getDrawList().getNumCommands(); ++i) { + const SoRenderCommand & command = action.getDrawList().getCommand(i); + if (command.pixelRaster.enabled) { + retainedSourceAndFootprint = + command.material.texture.width == 2 && + command.material.texture.height == 2 && + command.pixelRaster.width == 4 && + command.pixelRaster.height == 4; + break; + } + } + const std::vector rendered = fixture.render( + action.getDrawList(), SbVec4f(0, 0, 1, 1)); + root->unref(); + int red = 0; + int green = 0; + int blue = 0; + int yellow = 0; + for (int y = 30; y < 38; ++y) { + for (int x = 30; x < 38; ++x) { + const uint8_t * pixel = pixelAt(rendered, x, y); + if (pixel[0] > 220 && pixel[1] < 30 && pixel[2] < 30) ++red; + if (pixel[0] < 30 && pixel[1] > 220 && pixel[2] < 30) ++green; + if (pixel[0] < 30 && pixel[1] < 30 && pixel[2] > 220) ++blue; + if (pixel[0] > 220 && pixel[1] > 220 && pixel[2] < 30) ++yellow; + } + } + return check(retainedSourceAndFootprint && red > 0 && green > 0 && + blue > 0 && yellow > 0, + "SoImage did not preserve source texels while scaling its footprint"); +} + +bool testTextNode(Fixture & fixture) +{ + SoSeparator * root = new SoSeparator; + root->ref(); + SoText2 * text = new SoText2; + text->string = "Coin"; + root->addChild(text); + const std::vector rendered = renderNode( + fixture, root, SbVec4f(0, 0, 0, 1)); + root->unref(); + int nonBlack = 0; + for (int y = 0; y < 64; ++y) { + for (int x = 0; x < 64; ++x) { + const uint8_t * pixel = pixelAt(rendered, x, y); + if (pixel[0] > 30 || pixel[1] > 30 || pixel[2] > 30) ++nonBlack; + } + } + return check(nonBlack > 0, + "SoText2 did not render through the retained pixel path"); +} + +bool testPixelDraw(Fixture & fixture) +{ + const float positions[] = { + -0.1f, -0.1f, 0.0f, 0.1f, -0.1f, 0.0f, + 0.1f, 0.1f, 0.0f, -0.1f, 0.1f, 0.0f + }; + const float texcoords[] = { + 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, + 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f + }; + const uint32_t indices[] = { 0, 1, 2, 0, 2, 3 }; + const unsigned char image[] = { + 255, 0, 0, 255, 255, 0, 0, 255, + 255, 0, 0, 255, 255, 0, 0, 255 + }; + SoRenderCommand command; + command.modelMatrix.makeIdentity(); + command.geometry.topology = SO_TOPOLOGY_TRIANGLES; + command.geometry.vertexCount = 4; + command.geometry.indexCount = 6; + command.geometry.positions = positions; + command.geometry.indices = indices; + command.geometry.vertexStride = sizeof(float) * 3; + command.geometry.texcoords = texcoords; + command.geometry.texcoordStride = sizeof(float) * 4; + command.material.texture.pixels = image; + command.material.texture.width = 2; + command.material.texture.height = 2; + command.material.texture.numComponents = 4; + command.pixelRaster.enabled = TRUE; + command.pixelRaster.originX = 20; + command.pixelRaster.originY = 20; + command.pixelRaster.width = 2; + command.pixelRaster.height = 2; + command.material.shadingModel = SO_SHADING_UNLIT; + SoDrawList drawlist; + drawlist.addCommand(command); + const std::vector pixels = fixture.render( + drawlist, SbVec4f(0, 0, 1, 1), 1.0f, SbVec2s(8, 8), SbVec2s(48, 48)); + const uint8_t * pixel = pixelAt(pixels, 28, 28); + return check(pixel[0] > 200 && pixel[1] < 50 && pixel[2] < 50, + "pixel pipeline did not sample the retained image at its origin"); +} + +} // namespace + +static int runTest() +{ + SoDB::init(); + Fixture fixture; + const int initializationResult = fixture.initialize(); + if (initializationResult != 0) { + if (initializationResult == 77) { + return skip("core GLFW raster context is unavailable"); + } + std::cerr << "FAIL: retained raster backend did not initialize on the " + << "verified OpenGL 3.3/GLSL 330 context" << std::endl; + return 1; + } + + int result = 0; + if (!testWideLine(fixture)) result = 1; + if (!testPointSize(fixture)) result = 1; + if (!testFullLinePattern(fixture)) result = 1; + if (!testLineStripPatternContinuity(fixture)) result = 1; + if (!testIndexedLinePatternOccurrences(fixture)) result = 1; + if (!testEmptyLinePattern(fixture)) result = 1; + if (!testTriangleFallbacks(fixture)) result = 1; + if (!testPatternedTriangleFallback(fixture)) result = 1; + if (!testTriangleFallbackCulling(fixture)) result = 1; + if (!testTriangleStripFallbackCulling(fixture)) result = 1; + if (!testPolygonOffsetTargets(fixture)) result = 1; + if (!testSemanticFallback(fixture)) result = 1; + if (!testPixelDraw(fixture)) result = 1; + if (!testImageNode(fixture)) result = 1; + if (!testTextNode(fixture)) result = 1; + fixture.shutdown(); + return result; +} + +int main() +{ + const int result = runTest(); + SoDB::finish(); + return result; +} diff --git a/testsuite/RetainedRasterTextTest.cpp b/testsuite/RetainedRasterTextTest.cpp new file mode 100644 index 00000000000..6804225ea4f --- /dev/null +++ b/testsuite/RetainedRasterTextTest.cpp @@ -0,0 +1,167 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +static int +runTest() +{ + SoDB::init(); + + int result = 0; + + SoSeparator * root = new SoSeparator; + root->ref(); + SoText2 * text = new SoText2; + text->string = "Coin"; + SoMaterial * material = new SoMaterial; + material->transparency = 0.5f; + root->addChild(material); + root->addChild(text); + + SoDrawStyle * drawStyle = new SoDrawStyle; + drawStyle->style = SoDrawStyle::LINES; + drawStyle->linePattern = 0x0f0f; + drawStyle->linePatternScaleFactor = 3; + root->addChild(drawStyle); + root->addChild(new SoCube); + + SoIRRenderAction action(SbViewportRegion(128, 64)); + action.apply(root); + + bool foundPixelText = false; + bool invalidAntialiasedText = false; + for (int i = 0; i < action.getDrawList().getNumCommands(); ++i) { + const SoRenderCommand & command = action.getDrawList().getCommand(i); + if (!command.pixelRaster.enabled) continue; + const bool validCommand = command.geometry.vertexCount == 6 && + command.material.texture.pixels != NULL && + command.material.texture.width > 0 && command.material.texture.height > 0 && + command.opacityClass == SO_OPACITY_TRANSPARENT && + command.state.depth.writeEnabled; + foundPixelText = foundPixelText || validCommand; + if (command.state.alphaTest.reference == 0.3f) { + invalidAntialiasedText = + invalidAntialiasedText || + !command.state.blend.enabled || + command.state.alphaTest.policy != SO_ALPHA_TEST_POLICY_EXPLICIT; + } + } + if (!foundPixelText) { + std::cerr << "FAIL: SoText2 did not retain direct-raster depth semantics" << std::endl; + result = 1; + } + if (invalidAntialiasedText) { + std::cerr << "FAIL: SoText2 did not retain antialiased coverage semantics" << std::endl; + result = 1; + } + + SoSeparator * opaqueRoot = new SoSeparator; + opaqueRoot->ref(); + SoText2 * opaqueText = new SoText2; + opaqueText->string = "Coin"; + opaqueRoot->addChild(opaqueText); + SoIRRenderAction opaqueAction(SbViewportRegion(128, 64)); + opaqueAction.apply(opaqueRoot); + bool foundOpaqueText = false; + for (int i = 0; i < opaqueAction.getDrawList().getNumCommands(); ++i) { + const SoRenderCommand & command = opaqueAction.getDrawList().getCommand(i); + if (command.pixelRaster.enabled && + command.opacityClass == SO_OPACITY_OPAQUE) { + foundOpaqueText = true; + break; + } + } + if (!foundOpaqueText) { + std::cerr << "FAIL: opaque SoText2 was classified as transparent" << std::endl; + result = 1; + } + opaqueRoot->unref(); + + const unsigned char opaqueTexturePixel[] = { 255, 255, 255, 255 }; + SoSeparator * opaqueTexturedRoot = new SoSeparator; + opaqueTexturedRoot->ref(); + SoTexture2 * opaqueTexture = new SoTexture2; + opaqueTexture->image.setValue(SbVec2s(1, 1), 4, opaqueTexturePixel); + opaqueTexturedRoot->addChild(opaqueTexture); + SoText2 * opaqueTexturedText = new SoText2; + opaqueTexturedText->string = "Coin"; + opaqueTexturedRoot->addChild(opaqueTexturedText); + SoIRRenderAction opaqueTexturedAction(SbViewportRegion(128, 64)); + opaqueTexturedAction.apply(opaqueTexturedRoot); + bool foundOpaqueTexturedText = false; + for (int i = 0; i < opaqueTexturedAction.getDrawList().getNumCommands(); ++i) { + const SoRenderCommand & command = opaqueTexturedAction.getDrawList().getCommand(i); + if (command.pixelRaster.enabled && + command.opacityClass == SO_OPACITY_OPAQUE) { + foundOpaqueTexturedText = true; + break; + } + } + if (!foundOpaqueTexturedText) { + std::cerr << "FAIL: fully opaque RGBA texture changed SoText2 scheduling" + << std::endl; + result = 1; + } + opaqueTexturedRoot->unref(); + + const unsigned char texturePixel[] = { 255, 255, 255, 128 }; + SoSeparator * texturedRoot = new SoSeparator; + texturedRoot->ref(); + SoTexture2 * texture = new SoTexture2; + texture->image.setValue(SbVec2s(1, 1), 4, texturePixel); + texturedRoot->addChild(texture); + SoText2 * texturedText = new SoText2; + texturedText->string = "Coin"; + texturedRoot->addChild(texturedText); + SoIRRenderAction texturedAction(SbViewportRegion(128, 64)); + texturedAction.apply(texturedRoot); + bool foundTextureTransparentText = false; + for (int i = 0; i < texturedAction.getDrawList().getNumCommands(); ++i) { + const SoRenderCommand & command = texturedAction.getDrawList().getCommand(i); + if (command.pixelRaster.enabled && + command.opacityClass == SO_OPACITY_TRANSPARENT) { + foundTextureTransparentText = true; + break; + } + } + if (!foundTextureTransparentText) { + std::cerr << "FAIL: texture transparency did not schedule SoText2 as transparent" + << std::endl; + result = 1; + } + texturedRoot->unref(); + + bool foundLinePattern = false; + for (int i = 0; i < action.getDrawList().getNumCommands(); ++i) { + const SoRenderCommand & command = action.getDrawList().getCommand(i); + if (command.state.raster.fillMode == SO_RASTER_LINES && + command.state.raster.linePattern == 0x0f0f && + command.state.raster.linePatternScale == 3) { + foundLinePattern = true; + break; + } + } + if (!foundLinePattern) { + std::cerr << "FAIL: retained line-pattern state was not captured" << std::endl; + result = 1; + } + + root->unref(); + return result; +} + +int +main() +{ + const int result = runTest(); + SoDB::finish(); + return result; +} diff --git a/testsuite/render/baselines/gl/image_basic.png b/testsuite/render/baselines/gl/image_basic.png new file mode 100644 index 00000000000..082cbad9ad8 Binary files /dev/null and b/testsuite/render/baselines/gl/image_basic.png differ diff --git a/testsuite/render/baselines/gl/raster_line_pattern_basic.png b/testsuite/render/baselines/gl/raster_line_pattern_basic.png new file mode 100644 index 00000000000..8f9ffb34a82 Binary files /dev/null and b/testsuite/render/baselines/gl/raster_line_pattern_basic.png differ diff --git a/testsuite/render/baselines/gl/raster_points_basic.png b/testsuite/render/baselines/gl/raster_points_basic.png new file mode 100644 index 00000000000..9733d758c9e Binary files /dev/null and b/testsuite/render/baselines/gl/raster_points_basic.png differ diff --git a/testsuite/render/baselines/gl/raster_wide_line_basic.png b/testsuite/render/baselines/gl/raster_wide_line_basic.png new file mode 100644 index 00000000000..fc02b6490de Binary files /dev/null and b/testsuite/render/baselines/gl/raster_wide_line_basic.png differ diff --git a/testsuite/render/baselines/gl/raster_wireframe_basic.png b/testsuite/render/baselines/gl/raster_wireframe_basic.png new file mode 100644 index 00000000000..a9604a26de3 Binary files /dev/null and b/testsuite/render/baselines/gl/raster_wireframe_basic.png differ diff --git a/testsuite/render/scenes/image_basic.iv b/testsuite/render/scenes/image_basic.iv new file mode 100644 index 00000000000..45ba210e447 --- /dev/null +++ b/testsuite/render/scenes/image_basic.iv @@ -0,0 +1,21 @@ +#Inventor V2.1 ascii + +Separator { + OrthographicCamera { + position 0 0 5 + height 6 + nearDistance 0.1 + farDistance 20 + } + Image { + image 4 4 3 + 0xe63946 0xe63946 0x457b9d 0x457b9d + 0xe63946 0xe63946 0x457b9d 0x457b9d + 0xf1fa8c 0xf1fa8c 0x2a9d8f 0x2a9d8f + 0xf1fa8c 0xf1fa8c 0x2a9d8f 0x2a9d8f + width 192 + height 192 + horAlignment CENTER + vertAlignment HALF + } +} diff --git a/testsuite/render/scenes/raster_line_pattern_basic.iv b/testsuite/render/scenes/raster_line_pattern_basic.iv new file mode 100644 index 00000000000..0b1a3679ec4 --- /dev/null +++ b/testsuite/render/scenes/raster_line_pattern_basic.iv @@ -0,0 +1,24 @@ +#Inventor V2.1 ascii + +Separator { + PerspectiveCamera { + position 0 0 6 + heightAngle 0.785398 + nearDistance 0.1 + farDistance 20 + } + LightModel { model BASE_COLOR } + Material { diffuseColor 0.95 0.85 0.1 } + DrawStyle { + style LINES + lineWidth 4 + linePattern 0xf0f0 + linePatternScaleFactor 2 + } + Coordinate3 { + point [ -2.2 -1.2 0, 2.2 -1.2 0, + -2.2 0 0, 2.2 0 0, + -2.2 1.2 0, 2.2 1.2 0 ] + } + LineSet { numVertices [ 2, 2, 2 ] } +} diff --git a/testsuite/render/scenes/raster_points_basic.iv b/testsuite/render/scenes/raster_points_basic.iv new file mode 100644 index 00000000000..aa2701b864a --- /dev/null +++ b/testsuite/render/scenes/raster_points_basic.iv @@ -0,0 +1,17 @@ +#Inventor V2.1 ascii + +Separator { + PerspectiveCamera { + position 0 0 6 + heightAngle 0.785398 + nearDistance 0.1 + farDistance 20 + } + LightModel { model BASE_COLOR } + Material { diffuseColor 0.1 0.65 0.95 } + DrawStyle { style POINTS pointSize 12 } + Coordinate3 { + point [ -1.8 0 0, -0.6 0 0, 0.6 0 0, 1.8 0 0 ] + } + PointSet { numPoints 4 } +} diff --git a/testsuite/render/scenes/raster_wide_line_basic.iv b/testsuite/render/scenes/raster_wide_line_basic.iv new file mode 100644 index 00000000000..a68e35f0a16 --- /dev/null +++ b/testsuite/render/scenes/raster_wide_line_basic.iv @@ -0,0 +1,23 @@ +#Inventor V2.1 ascii + +Separator { + PerspectiveCamera { + position 0 0 6 + heightAngle 0.785398 + nearDistance 0.1 + farDistance 20 + } + LightModel { model BASE_COLOR } + DrawStyle { style LINES lineWidth 6 } + Coordinate3 { + point [ -2.2 -1.2 0, 2.2 -1.2 0, + -2.2 0 0, 2.2 0 0, + -2.2 1.2 0, 2.2 1.2 0 ] + } + Material { diffuseColor 0.95 0.15 0.1 } + LineSet { numVertices [ 2 ] } + Material { diffuseColor 0.1 0.8 0.25 } + LineSet { numVertices [ 2 ] } + Material { diffuseColor 0.15 0.35 0.95 } + LineSet { numVertices [ 2 ] } +} diff --git a/testsuite/render/scenes/raster_wireframe_basic.iv b/testsuite/render/scenes/raster_wireframe_basic.iv new file mode 100644 index 00000000000..621480db4d0 --- /dev/null +++ b/testsuite/render/scenes/raster_wireframe_basic.iv @@ -0,0 +1,18 @@ +#Inventor V2.1 ascii + +Separator { + PerspectiveCamera { + position 0 0 6 + heightAngle 0.785398 + nearDistance 0.1 + farDistance 20 + } + LightModel { model BASE_COLOR } + Material { diffuseColor 0.1 0.75 0.95 } + DrawStyle { style LINES lineWidth 1 } + Coordinate3 { + point [ -1.8 -1.2 0, 1.8 -1.2 0, + -1.8 1.2 0, 1.8 1.2 0 ] + } + TriangleStripSet { numVertices [ 4 ] } +} diff --git a/testsuite/render/specs/image_basic.yml b/testsuite/render/specs/image_basic.yml new file mode 100644 index 00000000000..9681e6bf454 --- /dev/null +++ b/testsuite/render/specs/image_basic.yml @@ -0,0 +1,3 @@ +id: image_basic +scene: ../scenes/image_basic.iv +baseline: ../baselines/gl/image_basic.png diff --git a/testsuite/render/specs/raster_line_pattern_basic.yml b/testsuite/render/specs/raster_line_pattern_basic.yml new file mode 100644 index 00000000000..01c742040d8 --- /dev/null +++ b/testsuite/render/specs/raster_line_pattern_basic.yml @@ -0,0 +1,3 @@ +id: raster_line_pattern_basic +scene: ../scenes/raster_line_pattern_basic.iv +baseline: ../baselines/gl/raster_line_pattern_basic.png diff --git a/testsuite/render/specs/raster_points_basic.yml b/testsuite/render/specs/raster_points_basic.yml new file mode 100644 index 00000000000..f158fa6d4c5 --- /dev/null +++ b/testsuite/render/specs/raster_points_basic.yml @@ -0,0 +1,3 @@ +id: raster_points_basic +scene: ../scenes/raster_points_basic.iv +baseline: ../baselines/gl/raster_points_basic.png diff --git a/testsuite/render/specs/raster_wide_line_basic.yml b/testsuite/render/specs/raster_wide_line_basic.yml new file mode 100644 index 00000000000..3748980a57b --- /dev/null +++ b/testsuite/render/specs/raster_wide_line_basic.yml @@ -0,0 +1,3 @@ +id: raster_wide_line_basic +scene: ../scenes/raster_wide_line_basic.iv +baseline: ../baselines/gl/raster_wide_line_basic.png diff --git a/testsuite/render/specs/raster_wireframe_basic.yml b/testsuite/render/specs/raster_wireframe_basic.yml new file mode 100644 index 00000000000..c0702ef4157 --- /dev/null +++ b/testsuite/render/specs/raster_wireframe_basic.yml @@ -0,0 +1,4 @@ +id: raster_wireframe_basic +scene: ../scenes/raster_wireframe_basic.iv +baseline: ../baselines/gl/raster_wireframe_basic.png +compare: relaxed