diff --git a/CMakeLists.txt b/CMakeLists.txt index 009e94ce3f..5d75c5d14c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -970,6 +970,8 @@ if(COIN_BUILD_VISUAL_TESTS) add_subdirectory(tools) set(COIN_DRAWLIST_BASE_VISUAL_SPECS "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") add_test(NAME visual_smoke COMMAND CoinVisualTests snapshot --help) set_tests_properties(visual_smoke PROPERTIES LABELS "visual-contract") if(COIN_BUILD_LEGACY_GL_RENDERER) @@ -1001,6 +1003,26 @@ if(COIN_BUILD_VISUAL_TESTS) ) set_tests_properties(visual_gl_drawlist_core PROPERTIES LABELS "visual-render;visual-drawlist") + add_test( + NAME visual_gl_drawlist_material_compat + COMMAND CoinVisualTests run + --renderer drawlist + --gl-profile compat + --only ${COIN_MATERIAL_VISUAL_SPECS} + --artifacts-dir ${CMAKE_BINARY_DIR}/render_artifacts + ) + set_tests_properties(visual_gl_drawlist_material_compat + PROPERTIES LABELS "visual-render;visual-material") + add_test( + NAME visual_gl_drawlist_material_core + COMMAND CoinVisualTests run + --renderer drawlist + --gl-profile core + --only ${COIN_MATERIAL_VISUAL_SPECS} + --artifacts-dir ${CMAKE_BINARY_DIR}/render_artifacts + ) + set_tests_properties(visual_gl_drawlist_material_core + PROPERTIES LABELS "visual-render;visual-material") add_test( NAME visual_gl_invalid_legacy_profile COMMAND CoinVisualTests run diff --git a/data/shaders/gl/common/VertexColor.glsl b/data/shaders/gl/common/VertexColor.glsl index 3e3ea5eee4..6d655b5c6d 100644 --- a/data/shaders/gl/common/VertexColor.glsl +++ b/data/shaders/gl/common/VertexColor.glsl @@ -5,12 +5,3 @@ vec4 coin_visual_color(vec4 vertexColor, vec4 uniformColor, { return useVertexColor > 0.5 ? vertexColor : uniformColor; } - -vec4 coin_visual_texture(vec4 color, sampler2D textureSampler, - vec2 texcoord, float textureEnabled, - vec4 textureModulation) -{ - return textureEnabled > 0.5 - ? color * texture(textureSampler, texcoord) * textureModulation - : color; -} diff --git a/data/shaders/gl/material/AlphaTest.glsl b/data/shaders/gl/material/AlphaTest.glsl new file mode 100644 index 0000000000..2db26bd2fe --- /dev/null +++ b/data/shaders/gl/material/AlphaTest.glsl @@ -0,0 +1,31 @@ +/* + * Retained alpha-test comparison helper. + * + * The caller supplies the alpha whose coverage semantics apply to the + * producer. This module performs only the comparison; it does not decide + * pass ordering, blending, or which producer alpha should be tested. + */ + +const int COIN_ALPHA_TEST_NONE = 0; +const int COIN_ALPHA_TEST_NEVER = 1; +const int COIN_ALPHA_TEST_ALWAYS = 2; +const int COIN_ALPHA_TEST_LESS = 3; +const int COIN_ALPHA_TEST_LEQUAL = 4; +const int COIN_ALPHA_TEST_EQUAL = 5; +const int COIN_ALPHA_TEST_GEQUAL = 6; +const int COIN_ALPHA_TEST_GREATER = 7; +const int COIN_ALPHA_TEST_NOTEQUAL = 8; + +bool coin_material_alpha_test_pass(float alpha, int function, + float reference) +{ + if (function == COIN_ALPHA_TEST_NEVER) return false; + if (function == COIN_ALPHA_TEST_ALWAYS) return true; + if (function == COIN_ALPHA_TEST_LESS) return alpha < reference; + if (function == COIN_ALPHA_TEST_LEQUAL) return alpha <= reference; + if (function == COIN_ALPHA_TEST_EQUAL) return alpha == reference; + if (function == COIN_ALPHA_TEST_GEQUAL) return alpha >= reference; + if (function == COIN_ALPHA_TEST_GREATER) return alpha > reference; + if (function == COIN_ALPHA_TEST_NOTEQUAL) return alpha != reference; + return true; +} diff --git a/data/shaders/gl/material/FragmentEvaluation.glsl b/data/shaders/gl/material/FragmentEvaluation.glsl new file mode 100644 index 0000000000..35ca4c9bae --- /dev/null +++ b/data/shaders/gl/material/FragmentEvaluation.glsl @@ -0,0 +1,39 @@ +/* + * Shared material and texture evaluation for surface fragments. + * + * The returned RGBA is the surface coverage used by visual, line, and point + * roots. Alpha testing remains a caller decision so alternate output roots + * can share coverage without sharing framebuffer or selection policy. + */ + +uniform sampler2D u_texture; +uniform float u_textureEnabled; +uniform int u_textureModel; +uniform vec4 u_textureBlendColor; +uniform vec4 u_color; +uniform float u_useVertexColor; +uniform float u_vertexColorAlphaIncludesOpacity; +uniform float u_textureAlphaIncludesOpacity; +uniform float u_textureHasAlpha; +uniform int u_alphaTestFunction; +uniform float u_alphaTestReference; + +#include "Texture.glsl" +#include "AlphaTest.glsl" + +vec4 +coin_surface_fragment_color(vec4 vertexColor, vec3 litColor, vec2 texcoord) +{ + float materialAlpha = u_color.a; + if (u_useVertexColor > 0.5 && + u_vertexColorAlphaIncludesOpacity > 0.5) { + materialAlpha = 1.0; + } + if (u_textureEnabled > 0.5) { + return coin_material_textured_color( + u_texture, texcoord, vec4(litColor, vertexColor.a), materialAlpha, + u_textureAlphaIncludesOpacity, u_textureHasAlpha, u_textureModel, + u_textureBlendColor); + } + return vec4(litColor, vertexColor.a * materialAlpha); +} diff --git a/data/shaders/gl/material/Lighting.glsl b/data/shaders/gl/material/Lighting.glsl new file mode 100644 index 0000000000..44dfe6f130 --- /dev/null +++ b/data/shaders/gl/material/Lighting.glsl @@ -0,0 +1,66 @@ +/* + * Retained Gouraud lighting evaluation. + * + * Inputs are the captured view-space light/material uniforms and the base + * surface color. This module computes lighting only; framebuffer, pass, + * texture and alpha-test policy belong to the calling shader roots. + */ + +const int COIN_MAX_LIGHTS = 8; +const int COIN_LIGHT_DIRECTIONAL = 0; +const int COIN_LIGHT_POINT = 1; +const int COIN_LIGHT_SPOT = 2; + +vec3 coin_material_compute_gouraud_color(vec3 eyePos, vec3 eyeNormal, + vec3 baseColor) +{ + vec3 N = normalize(eyeNormal); + vec3 V = normalize(-eyePos); + // Coin's LegacyGL path leaves GL_LIGHT_MODEL_LOCAL_VIEWER at its default + // false value, so the fixed-function half-vector uses a viewer direction + // of (0, 0, 1) in eye space rather than a per-vertex eye position. + vec3 specularViewer = vec3(0.0, 0.0, 1.0); + if (u_twoSidedLighting > 0.5 && dot(N, V) < 0.0) { + N = -N; + } + vec3 litColor = u_ambientLight * u_materialAmbient; + + for (int i = 0; i < COIN_MAX_LIGHTS; ++i) { + if (i >= u_lightCount) break; + + vec3 L = u_lightDirection[i]; + float attenuation = 1.0; + float spotFactor = 1.0; + if (u_lightType[i] != COIN_LIGHT_DIRECTIONAL) { + vec3 lightVector = u_lightPosition[i] - eyePos; + float distanceToLight = length(lightVector); + if (distanceToLight <= 0.0001) continue; + L = lightVector / distanceToLight; + vec3 att = u_lightAttenuation[i]; + attenuation = 1.0 / max(att.z + att.y * distanceToLight + + att.x * distanceToLight * distanceToLight, + 0.0001); + if (u_lightType[i] == COIN_LIGHT_SPOT) { + vec3 coneDir = normalize(u_lightDirection[i]); + vec3 fromLight = normalize(eyePos - u_lightPosition[i]); + float spotCos = dot(coneDir, fromLight); + if (spotCos < u_lightSpotParams[i].x) continue; + spotFactor = pow(max(spotCos, 0.0), u_lightSpotParams[i].y); + } + } + + vec3 Ln = normalize(L); + float NdotL = max(dot(N, Ln), 0.0); + if (NdotL <= 0.0) continue; + vec3 H = normalize(Ln + specularViewer); + float NdotH = max(dot(N, H), 0.0); + float shininess = max(u_materialShininess * 128.0, 0.0); + float specularFactor = shininess > 0.0 + ? pow(NdotH, shininess) : 0.0; + vec3 diffuse = baseColor * NdotL; + vec3 specular = u_materialSpecular * specularFactor; + litColor += u_lightColor[i] * attenuation * spotFactor * + (diffuse + specular); + } + return clamp(litColor + u_emissiveColor, 0.0, 1.0); +} diff --git a/data/shaders/gl/material/Texture.glsl b/data/shaders/gl/material/Texture.glsl new file mode 100644 index 0000000000..2c0bfb9032 --- /dev/null +++ b/data/shaders/gl/material/Texture.glsl @@ -0,0 +1,47 @@ +/* + * Retained material texture evaluation. + * + * Applies Coin's texture model and alpha semantics to a supplied surface + * color. It does not choose a render pass or perform alpha testing; callers + * retain those decisions explicitly so visual, picking, and selection roots + * can share coverage without sharing output policy. + */ + +const int COIN_TEXTURE_MODEL_MODULATE = 0; +const int COIN_TEXTURE_MODEL_DECAL = 1; +const int COIN_TEXTURE_MODEL_BLEND = 2; +const int COIN_TEXTURE_MODEL_REPLACE = 3; + +vec4 coin_material_textured_color(sampler2D textureSampler, vec2 texcoord, + vec4 visualColor, float materialAlpha, + float textureAlphaIncludesOpacity, + float textureHasAlpha, + int textureModel, vec4 blendColor) +{ + vec4 texel = texture(textureSampler, texcoord); + float primaryAlpha = visualColor.a * + (textureAlphaIncludesOpacity > 0.5 ? 1.0 : materialAlpha); + float textureAlpha = textureHasAlpha > 0.5 ? texel.a : 1.0; + vec3 rgb = visualColor.rgb; + float alpha = primaryAlpha; + + switch (textureModel) { + case COIN_TEXTURE_MODEL_DECAL: + rgb = mix(visualColor.rgb, texel.rgb, textureAlpha); + break; + case COIN_TEXTURE_MODEL_BLEND: + rgb = mix(visualColor.rgb, blendColor.rgb, texel.rgb); + alpha = primaryAlpha * textureAlpha; + break; + case COIN_TEXTURE_MODEL_REPLACE: + rgb = texel.rgb; + alpha = textureHasAlpha > 0.5 ? texel.a : primaryAlpha; + break; + case COIN_TEXTURE_MODEL_MODULATE: + default: + rgb = visualColor.rgb * texel.rgb; + alpha = primaryAlpha * textureAlpha; + break; + } + return vec4(rgb, alpha); +} diff --git a/data/shaders/gl/material/VertexEvaluation.glsl b/data/shaders/gl/material/VertexEvaluation.glsl new file mode 100644 index 0000000000..3a1844ace4 --- /dev/null +++ b/data/shaders/gl/material/VertexEvaluation.glsl @@ -0,0 +1,47 @@ +/* + * Shared surface evaluation for visual, wide-line, and point vertex stages. + * + * This module selects vertex color and computes optional view-space lighting. + * It owns no framebuffer output, raster-path selection, pass ordering, or + * alpha-test decision. + */ + +#include "../common/VertexColor.glsl" + +uniform mat4 u_proj; +uniform mat4 u_view; +uniform mat4 u_model; +uniform vec4 u_color; +uniform float u_useVertexColor; +uniform int u_shadingModel; +uniform vec3 u_emissiveColor; +uniform vec3 u_ambientLight; +uniform vec3 u_materialAmbient; +uniform vec3 u_materialSpecular; +uniform float u_materialShininess; +uniform float u_twoSidedLighting; +uniform int u_lightCount; +uniform int u_lightType[8]; +uniform vec3 u_lightColor[8]; +uniform vec3 u_lightDirection[8]; +uniform vec3 u_lightPosition[8]; +uniform vec3 u_lightAttenuation[8]; +uniform vec2 u_lightSpotParams[8]; + +#include "Lighting.glsl" + +vec4 +coin_surface_vertex_color(vec4 vertexColor) +{ + return coin_visual_color(vertexColor, vec4(u_color.rgb, 1.0), + u_useVertexColor); +} + +vec3 +coin_surface_lit_color(vec4 vertexColor, vec3 eyePosition, vec3 eyeNormal) +{ + return u_shadingModel == 0 + ? vertexColor.rgb + : coin_material_compute_gouraud_color(eyePosition, eyeNormal, + vertexColor.rgb); +} diff --git a/data/shaders/gl/visual/Fragment.glsl b/data/shaders/gl/visual/Fragment.glsl index b0f08c69d6..178d502e91 100644 --- a/data/shaders/gl/visual/Fragment.glsl +++ b/data/shaders/gl/visual/Fragment.glsl @@ -1,20 +1,19 @@ #version 330 core -/* Base retained visual fragment root: evaluate the bound surface and emit - * framebuffer color. Coverage policy remains explicit at this root. */ -#include "../common/VertexColor.glsl" - -uniform sampler2D u_texture; -uniform float u_textureEnabled; -uniform vec4 u_texModColor; +/* Visual surface fragment root: shared surface evaluation, explicit alpha + * test, then framebuffer color. */ +#include "../material/FragmentEvaluation.glsl" in vec4 v_color; +in vec3 v_litColor; in vec2 v_texcoord; out vec4 fragColor; void main() { - fragColor = coin_visual_texture(v_color, u_texture, v_texcoord, - u_textureEnabled, u_texModColor); + 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/visual/Vertex.glsl b/data/shaders/gl/visual/Vertex.glsl index 18599dc954..f6f3bb634d 100644 --- a/data/shaders/gl/visual/Vertex.glsl +++ b/data/shaders/gl/visual/Vertex.glsl @@ -1,28 +1,27 @@ #version 330 core -/* - * Base retained visual vertex root. The OpenGL backend requires 3.3 or newer; - * executable roots therefore use GLSL 330 core. Included files are semantic - * modules and intentionally provide no version directive or main(). - */ -#include "../common/VertexColor.glsl" - +/* Visual surface vertex root. The backend requires OpenGL 3.3 or newer, so + * executable roots use GLSL 330 core; this root wires shared vertex color and + * lighting evaluation into the ordinary triangle pipeline. */ +#include "../material/VertexEvaluation.glsl" layout(location = 0) in vec3 a_position; -layout(location = 1) in vec4 a_color; -layout(location = 2) in vec2 a_texcoord; - -uniform mat4 u_proj; -uniform mat4 u_view; -uniform mat4 u_model; -uniform vec4 u_color; -uniform float u_useVertexColor; +layout(location = 1) in vec3 a_normal; +layout(location = 2) in vec4 a_color; +layout(location = 3) in vec2 a_texcoord; out vec4 v_color; +out vec3 v_litColor; out vec2 v_texcoord; void main() { - v_color = (u_useVertexColor > 0.5) ? a_color : u_color; + v_color = coin_surface_vertex_color(a_color); v_texcoord = a_texcoord; - gl_Position = u_proj * u_view * u_model * vec4(a_position, 1.0); + + 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; + v_litColor = coin_surface_lit_color(v_color, eyePos.xyz, eyeNormal); + gl_Position = u_proj * eyePos; } diff --git a/include/Inventor/actions/SoIRRenderAction.h b/include/Inventor/actions/SoIRRenderAction.h index 68e1cc36ae..f49c3acc91 100644 --- a/include/Inventor/actions/SoIRRenderAction.h +++ b/include/Inventor/actions/SoIRRenderAction.h @@ -105,6 +105,14 @@ class COIN_DLL_API SoIRRenderAction : public SoAction { */ void * allocateGeometryStorage(size_t bytes, size_t alignment = alignof(float)); + //! Copy one texture payload into the current frame, reusing an existing copy. + const unsigned char * allocateTextureStorage(const unsigned char * source, + size_t bytes, + int width, + int height, + int numComponents, + bool & hasTransparency); + //! Push a primitive collector for subsequent fallback primitive generation. void pushPrimitiveCollector(PrimitiveCollector * collector); //! Pop the current primitive collector. The caller must pop in stack order. diff --git a/include/Inventor/elements/SoLazyElement.h b/include/Inventor/elements/SoLazyElement.h index 9607d566d8..94e047c4de 100644 --- a/include/Inventor/elements/SoLazyElement.h +++ b/include/Inventor/elements/SoLazyElement.h @@ -279,7 +279,8 @@ class COIN_DLL_API SoLazyElement : public SoElement { virtual void setAlphaTestElt(int func, float value); private: - SoLazyElementP * pimpl; // for future use + friend class SoLazyElementP; + SoLazyElementP * pimpl = nullptr; }; diff --git a/include/Inventor/nodes/SoAlphaTest.h b/include/Inventor/nodes/SoAlphaTest.h index f43de64ff0..c285ff90da 100644 --- a/include/Inventor/nodes/SoAlphaTest.h +++ b/include/Inventor/nodes/SoAlphaTest.h @@ -61,6 +61,8 @@ class COIN_DLL_API SoAlphaTest : public SoNode { SoSFEnum function; SoSFFloat value; + void doAction(SoAction * action) override; + #if COIN_HAVE_LEGACY_GL_RENDERER void GLRender(SoGLRenderAction * action) override; #endif diff --git a/include/Inventor/nodes/SoDepthBuffer.h b/include/Inventor/nodes/SoDepthBuffer.h index 8ac4efa396..bfe994249d 100644 --- a/include/Inventor/nodes/SoDepthBuffer.h +++ b/include/Inventor/nodes/SoDepthBuffer.h @@ -64,6 +64,8 @@ class COIN_DLL_API SoDepthBuffer : public SoNode { SoSFEnum function; SoSFVec2f range; + void doAction(SoAction * action) override; + #if COIN_HAVE_LEGACY_GL_RENDERER void GLRender(SoGLRenderAction * action) override; #endif diff --git a/include/Inventor/nodes/SoLight.h b/include/Inventor/nodes/SoLight.h index 8c7a383ff7..bfe0b360be 100644 --- a/include/Inventor/nodes/SoLight.h +++ b/include/Inventor/nodes/SoLight.h @@ -50,6 +50,7 @@ class COIN_DLL_API SoLight : public SoNode { SoSFFloat intensity; SoSFColor color; + void doAction(SoAction * action) override; void callback(SoCallbackAction * action) override; protected: diff --git a/include/Inventor/rendering/SoRenderIR.h b/include/Inventor/rendering/SoRenderIR.h index 2dab38bf44..5b5a3e3d4b 100644 --- a/include/Inventor/rendering/SoRenderIR.h +++ b/include/Inventor/rendering/SoRenderIR.h @@ -146,6 +146,15 @@ enum SoTextureColorSpace : uint8_t { SO_TEXTURE_COLORSPACE_SRGB }; +// Texture environment models retained from SoMultiTextureImageElement. These +// are semantic values; a backend maps them to its own texture-combine API. +enum SoTextureModel : uint8_t { + SO_TEXTURE_MODEL_MODULATE = 0, + SO_TEXTURE_MODEL_DECAL, + SO_TEXTURE_MODEL_BLEND, + SO_TEXTURE_MODEL_REPLACE +}; + // --- Depth state --------------------------------------------------------- // Semantic comparison functions. These deliberately do not use GL enum @@ -179,7 +188,11 @@ enum SoBlendFactor : uint8_t { SO_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR, SO_BLEND_FACTOR_CONSTANT_ALPHA, SO_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA, - SO_BLEND_FACTOR_SRC_ALPHA_SATURATE + SO_BLEND_FACTOR_SRC_ALPHA_SATURATE, + SO_BLEND_FACTOR_SRC1_COLOR, + SO_BLEND_FACTOR_ONE_MINUS_SRC1_COLOR, + SO_BLEND_FACTOR_SRC1_ALPHA, + SO_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA }; enum SoBlendEquation : uint8_t { @@ -228,18 +241,25 @@ struct SoTextureData { int width = 0; int height = 0; int numComponents = 0; // 1=L, 2=LA, 3=RGB, 4=RGBA + // True when at least one texel can contribute alpha below one. This is a + // semantic classification captured once with the frame payload. + bool hasTransparency = false; SoTextureFilter minFilter = SO_TEXTURE_FILTER_NEAREST; SoTextureFilter magFilter = SO_TEXTURE_FILTER_NEAREST; SoTextureWrap wrapS = SO_TEXTURE_WRAP_CLAMP_TO_EDGE; SoTextureWrap wrapT = SO_TEXTURE_WRAP_CLAMP_TO_EDGE; SoTextureColorSpace colorSpace = SO_TEXTURE_COLORSPACE_LEGACY; - // A nonzero key permits a backend to retain the texture resource across // frame lifetimes. revision changes require the resource contents to be // refreshed; zero remains transient. uint64_t cacheKey = 0; uint64_t revision = 0; + // Request anisotropic filtering when Coin's texture-quality policy enables + // it. The executor selects the driver's supported level. + bool anisotropic = false; + SoTextureModel model = SO_TEXTURE_MODEL_MODULATE; + SbVec4f blendColor = SbVec4f(0.0f, 0.0f, 0.0f, 1.0f); }; /*! @@ -259,7 +279,10 @@ struct SoMaterialData { SbVec4f ambient = {0.2f, 0.2f, 0.2f, 1.0f}; SbVec4f specular = {0.0f, 0.0f, 0.0f, 1.0f}; SbVec4f emissive = {0.0f, 0.0f, 0.0f, 1.0f}; - SoShadingModel shadingModel = SO_SHADING_LEGACY_GOURAUD; + // A command with no retained lighting setup is explicitly unlit. Scene + // traversal fills this with the effective Gouraud model when lighting is + // present, so the executor never needs to invent a headlight. + SoShadingModel shadingModel = SO_SHADING_UNLIT; float shininess = 0.2f; float opacity = 1.0f; @@ -356,6 +379,20 @@ struct SoRenderState { SoBlendState blend; SoAlphaTestState alphaTest; SoRasterState raster; + //! Use view/projection matrices captured with this command. + SbBool useCommandMatrices = FALSE; +}; + +/*! + \enum SoOpacityClass + \brief Semantic surface opacity classification used by the planner. + + This describes whether a command contributes translucent surface semantics. + It is not an execution pass and does not encode ordering. +*/ +enum SoOpacityClass : uint8_t { + SO_OPACITY_OPAQUE = 0, + SO_OPACITY_TRANSPARENT }; /*! @@ -419,6 +456,7 @@ struct SoRenderCommand { SbMatrix viewMatrix; SbMatrix projMatrix; + SoOpacityClass opacityClass = SO_OPACITY_OPAQUE; SoLightingHandle lightingHandle = 0; SoNodeId nodeId = 0; //!< Identity of the underlying scene node. SoInstanceId instanceId = 0; //!< Identity of this rendered occurrence. diff --git a/src/actions/SoIRRenderAction.cpp b/src/actions/SoIRRenderAction.cpp index 94b4250ef2..0593038f70 100644 --- a/src/actions/SoIRRenderAction.cpp +++ b/src/actions/SoIRRenderAction.cpp @@ -61,14 +61,27 @@ #include #include #include +#include +#include SO_ACTION_SOURCE(SoIRRenderAction); class SoIRRenderActionP { public: + struct TextureStorage { + const unsigned char * source = nullptr; + size_t bytes = 0; + int width = 0; + int height = 0; + int numComponents = 0; + const unsigned char * copy = nullptr; + bool hasTransparency = false; + }; + SoIRRenderActionP() = default; SoIRBuffer geometryPool; + std::vector textureStorage; SbList collectorStack; std::vector instancePathNodes; std::vector instancePathIndices; @@ -290,10 +303,60 @@ SoIRRenderAction::allocateGeometryStorage(size_t bytes, size_t alignment) return PRIVATE(this)->geometryPool.allocate(bytes, alignment); } +const unsigned char * +SoIRRenderAction::allocateTextureStorage(const unsigned char * source, + size_t bytes, + int width, + int height, + int numComponents, + bool & hasTransparency) +{ + assert(source != NULL); + for (std::vector::const_iterator it = + PRIVATE(this)->textureStorage.begin(); + it != PRIVATE(this)->textureStorage.end(); ++it) { + if (it->source == source && it->bytes == bytes && + it->width == width && it->height == height && + it->numComponents == numComponents) { + hasTransparency = it->hasTransparency; + return it->copy; + } + } + + const bool carriesAlpha = numComponents == 2 || numComponents == 4; + bool transparent = false; + if (carriesAlpha) { + const size_t pixelCount = bytes / static_cast(numComponents); + for (size_t pixel = 0; pixel < pixelCount; ++pixel) { + if (source[pixel * static_cast(numComponents) + + static_cast(numComponents - 1)] != 0xffu) { + transparent = true; + break; + } + } + } + + unsigned char * copy = static_cast( + PRIVATE(this)->geometryPool.allocate(bytes, alignof(unsigned char))); + std::memcpy(copy, source, bytes); + SoIRRenderActionP::TextureStorage storage; + storage.source = source; + storage.bytes = bytes; + storage.width = width; + storage.height = height; + storage.numComponents = numComponents; + storage.copy = copy; + storage.hasTransparency = transparent; + PRIVATE(this)->textureStorage.push_back(storage); + hasTransparency = transparent; + return copy; +} + void SoIRRenderAction::resetFrameResources() { PRIVATE(this)->geometryPool.clear(); + PRIVATE(this)->textureStorage.clear(); PRIVATE(this)->collectorStack.truncate(0); PRIVATE(this)->instancePathNodes.clear(); PRIVATE(this)->instancePathIndices.clear(); diff --git a/src/elements/SoLazyElement.cpp b/src/elements/SoLazyElement.cpp index ab4e70ad97..56f34fbdda 100644 --- a/src/elements/SoLazyElement.cpp +++ b/src/elements/SoLazyElement.cpp @@ -72,8 +72,11 @@ #include +#include "elements/SoLazyElementP.h" + #include #include +#include #include #if COIN_BUILD_LEGACY_GL_RENDERER @@ -156,6 +159,8 @@ SoLazyElement::initClass() SoLazyElement::~SoLazyElement() { + delete this->pimpl; + this->pimpl = NULL; } // ! FIXME: write doc @@ -163,6 +168,9 @@ SoLazyElement::~SoLazyElement() void SoLazyElement::init(SoState * COIN_UNUSED_ARG(state)) { + if (!this->pimpl) this->pimpl = new SoLazyElementP; + this->pimpl->packedColor = SoLazyElementP::PackedColorState(); + this->pimpl->semanticAlphaTest = SoLazyElementP::SemanticAlphaTestState(); this->coinstate.ambient = this->getDefaultAmbient(); this->coinstate.specular = this->getDefaultSpecular(); this->coinstate.emissive = this->getDefaultEmissive(); @@ -204,6 +212,9 @@ SoLazyElement::push(SoState *state) inherited::push(state); const SoLazyElement * prev = coin_assert_cast(this->getNextInStack()); this->coinstate = prev->coinstate; + if (!this->pimpl) this->pimpl = new SoLazyElementP; + if (prev->pimpl) *this->pimpl = *prev->pimpl; + else *this->pimpl = SoLazyElementP(); } @@ -253,6 +264,7 @@ SoLazyElement::setDiffuse(SoState * state, SoNode * node, int32_t numcolors, if (numcolors && (elem->coinstate.diffusenodeid != get_diffuse_node_id(node, numcolors, colors))) { elem = getWInstance(state); + SoLazyElementP::clearPackedVertexColorState(state); elem->setDiffuseElt(node, numcolors, colors, packer); if (state->isCacheOpen()) elem->lazyDidSet(DIFFUSE_MASK); } @@ -276,6 +288,7 @@ SoLazyElement::setTransparency(SoState *state, SoNode *node, int32_t numvalues, if (numvalues && (elem->coinstate.transpnodeid != get_transp_node_id(node, numvalues, transparency))) { elem = getWInstance(state); + SoLazyElementP::clearPackedVertexColorState(state); elem->setTranspElt(node, numvalues, transparency, packer); if (state->isCacheOpen()) elem->lazyDidSet(TRANSPARENCY_MASK); } @@ -300,6 +313,7 @@ SoLazyElement::setPacked(SoState * state, SoNode * node, SoLazyElement * elem = SoLazyElement::getInstance(state); if (numcolors && elem->coinstate.diffusenodeid != node->getNodeId()) { elem = getWInstance(state); + SoLazyElementP::clearPackedVertexColorState(state); elem->setPackedElt(node, numcolors, colors, packedtransparency); if (state->isCacheOpen()) elem->lazyDidSet(TRANSPARENCY_MASK|DIFFUSE_MASK); } @@ -309,6 +323,86 @@ SoLazyElement::setPacked(SoState * state, SoNode * node, SoShapeStyleElement::setTransparentMaterial(state, elem->coinstate.istransparent); } +void +SoLazyElementP::setPackedVertexColors( + SoState * state, SoNode * node, int32_t numcolors, + const uint32_t * colors, const SbBool packedtransparency) +{ + SbList inheritedOpacities; + SoLazyElementP::capturePackedVertexColorOpacities( + state, inheritedOpacities); + SoLazyElement::setPacked(state, node, numcolors, colors, + packedtransparency); + SoLazyElementP::setPackedVertexColorState(state, inheritedOpacities); +} + +void +SoLazyElementP::capturePackedVertexColorOpacities( + SoState * state, SbList & opacities) +{ + opacities.truncate(0); + SoLazyElement * lazy = SoLazyElement::getInstance(state); + if (lazy->pimpl && lazy->pimpl->packedColor.fromVertexProperty) { + opacities = lazy->pimpl->packedColor.inheritedOpacities; + return; + } + + // A packed color written by another node is vertex data, not a material + // opacity source. Start a new composition from neutral opacity. + if (lazy->isPacked()) { + opacities.append(1.0f); + return; + } + + const int count = std::max(1, lazy->getNumTransparencies()); + for (int i = 0; i < count; ++i) { + opacities.append(1.0f - SoLazyElement::getTransparency(state, i)); + } +} + +void +SoLazyElementP::setPackedVertexColorState( + SoState * state, const SbList & opacities) +{ + SoLazyElement * lazy = SoLazyElement::getWInstance(state); + if (!lazy->pimpl) lazy->pimpl = new SoLazyElementP; + lazy->pimpl->packedColor.fromVertexProperty = opacities.getLength() > 0; + lazy->pimpl->packedColor.inheritedOpacities = opacities; +} + +void +SoLazyElementP::clearPackedVertexColorState(SoState * state) +{ + SoLazyElement * lazy = SoLazyElement::getWInstance(state); + if (!lazy->pimpl) lazy->pimpl = new SoLazyElementP; + lazy->pimpl->packedColor.fromVertexProperty = FALSE; + lazy->pimpl->packedColor.inheritedOpacities.truncate(0); +} + +SbBool +SoLazyElementP::hasPackedVertexColorState(SoState * state) +{ + const SoLazyElement * lazy = SoLazyElement::getInstance(state); + return lazy->pimpl && lazy->pimpl->packedColor.fromVertexProperty && + lazy->isPacked(); +} + +float +SoLazyElementP::getPackedVertexColorOpacity(SoState * state, + const int materialIndex) +{ + const SoLazyElement * lazy = SoLazyElement::getInstance(state); + if (!lazy->pimpl || !lazy->pimpl->packedColor.fromVertexProperty || + !lazy->isPacked() || + lazy->pimpl->packedColor.inheritedOpacities.getLength() == 0) { + return 1.0f; + } + const int index = std::max( + 0, std::min(materialIndex, + lazy->pimpl->packedColor.inheritedOpacities.getLength() - 1)); + return lazy->pimpl->packedColor.inheritedOpacities[index]; +} + // ! FIXME: write doc void @@ -318,6 +412,7 @@ SoLazyElement::setColorIndices(SoState *state, SoNode *node, SoLazyElement * elem = SoLazyElement::getInstance(state); if (numindices && elem->coinstate.diffusenodeid != node->getNodeId()) { elem = getWInstance(state); + SoLazyElementP::clearPackedVertexColorState(state); elem->setColorIndexElt(node, numindices, indices); if (state->isCacheOpen()) elem->lazyDidSet(DIFFUSE_MASK); } @@ -615,6 +710,18 @@ SoLazyElement::getAlphaTest(SoState * state, float & value) return elem->coinstate.alphatestfunc; } +int +SoLazyElementP::getAlphaTestSemantic(SoState * state, float & value) +{ + const SoLazyElement * elem = SoLazyElement::getInstance(state); + if (!elem->pimpl) { + value = 0.5f; + return 0; + } + value = elem->pimpl->semanticAlphaTest.value; + return elem->pimpl->semanticAlphaTest.function; +} + // ! FIXME: write doc int32_t @@ -809,6 +916,9 @@ SoLazyElement::setMaterials(SoState * state, SoNode *node, uint32_t bitmask, if (eltbitmask) { welem = getWInstance(state); + if (eltbitmask & (DIFFUSE_MASK | TRANSPARENCY_MASK)) { + SoLazyElementP::clearPackedVertexColorState(state); + } welem->setMaterialElt(node, eltbitmask, packer, diffuse, numdiffuse, transp, numtransp, ambient, emissive, specular, shininess, @@ -890,10 +1000,26 @@ SoLazyElement::setAlphaTest(SoState * state, int func, float value) elem->coinstate.alphatestvalue != value) { elem = getWInstance(state); elem->setAlphaTestElt(func, value); - if (state->isCacheOpen()) elem->lazyDidSet(ALPHATEST_MASK); + if (state->isCacheOpen()) elem->lazyDidSet(SoLazyElement::ALPHATEST_MASK); } else if (state->isCacheOpen()) { - elem->lazyDidntSet(ALPHATEST_MASK); + elem->lazyDidntSet(SoLazyElement::ALPHATEST_MASK); + } +} + +void +SoLazyElementP::setAlphaTestSemantic(SoState * state, int function, float value) +{ + SoLazyElement * elem = SoLazyElement::getInstance(state); + if (!elem->pimpl || elem->pimpl->semanticAlphaTest.function != function || + elem->pimpl->semanticAlphaTest.value != value) { + elem = SoLazyElement::getWInstance(state); + elem->pimpl->semanticAlphaTest.function = function; + elem->pimpl->semanticAlphaTest.value = value; + if (state->isCacheOpen()) elem->lazyDidSet(SoLazyElement::ALPHATEST_MASK); + } + else if (state->isCacheOpen()) { + elem->lazyDidntSet(SoLazyElement::ALPHATEST_MASK); } } @@ -1149,7 +1275,6 @@ SoLazyElement::setAlphaTestElt(int func, float value) this->coinstate.alphatestvalue = value; } - // SoColorPacker class. FIXME: move to separate file and document, pederb, 2002-09-09 static uint32_t colorpacker_default = 0xccccccff; diff --git a/src/elements/SoLazyElementP.h b/src/elements/SoLazyElementP.h new file mode 100644 index 0000000000..bdd4293ba1 --- /dev/null +++ b/src/elements/SoLazyElementP.h @@ -0,0 +1,48 @@ +#ifndef COIN_SOLAZYELEMENTP_H +#define COIN_SOLAZYELEMENTP_H + +#include +#include + +class SoLazyElement; +class SoNode; +class SoState; + +// Source-private access to metadata describing SoLazyElement's packed color +// representation. The state remains owned by SoLazyElement's pimpl; these +// helpers are not part of Coin's public element API. +class SoLazyElementP { +public: + SoLazyElementP() = default; + + static void setPackedVertexColors( + SoState * state, SoNode * node, int32_t numcolors, + const uint32_t * colors, SbBool packedtransparency); + static SbBool hasPackedVertexColorState(SoState * state); + static float getPackedVertexColorOpacity(SoState * state, + int materialIndex); + static void setAlphaTestSemantic(SoState * state, int function, + float value); + static int getAlphaTestSemantic(SoState * state, float & value); + +private: + friend class SoLazyElement; + + static void capturePackedVertexColorOpacities( + SoState * state, SbList & opacities); + static void setPackedVertexColorState( + SoState * state, const SbList & opacities); + static void clearPackedVertexColorState(SoState * state); + + struct PackedColorState { + SbBool fromVertexProperty = FALSE; + SbList inheritedOpacities; + } packedColor; + + struct SemanticAlphaTestState { + int function = 0; + float value = 0.5f; + } semanticAlphaTest; +}; + +#endif // COIN_SOLAZYELEMENTP_H diff --git a/src/nodes/SoAlphaTest.cpp b/src/nodes/SoAlphaTest.cpp index ca49c37838..aa026969ce 100644 --- a/src/nodes/SoAlphaTest.cpp +++ b/src/nodes/SoAlphaTest.cpp @@ -69,6 +69,7 @@ #include #include +#include "elements/SoLazyElementP.h" #include "nodes/SoSubNodeP.h" /*! @@ -173,6 +174,16 @@ SoAlphaTest::~SoAlphaTest() { } +void +SoAlphaTest::doAction(SoAction * action) +{ + // Generic traversal retains the semantic Inventor function. The LegacyGL + // override below is the only path that translates it to a GL enum. + SoLazyElementP::setAlphaTestSemantic(action->getState(), + this->function.getValue(), + this->value.getValue()); +} + // Doc from parent #if COIN_BUILD_LEGACY_GL_RENDERER void diff --git a/src/nodes/SoDepthBuffer.cpp b/src/nodes/SoDepthBuffer.cpp index 2903907995..bc181064d0 100644 --- a/src/nodes/SoDepthBuffer.cpp +++ b/src/nodes/SoDepthBuffer.cpp @@ -189,6 +189,33 @@ SoDepthBuffer::~SoDepthBuffer() { } +void +SoDepthBuffer::doAction(SoAction * action) +{ + SoState * state = action->getState(); + SbBool testenable = this->test.getValue(); + SbBool writeenable = this->write.getValue(); + SoDepthBufferElement::DepthWriteFunction function = + static_cast(this->function.getValue()); + SbVec2f depthrange = this->range.getValue(); + + if (this->test.isIgnored()) { + testenable = SoDepthBufferElement::getTestEnable(state); + } + if (this->write.isIgnored()) { + writeenable = SoDepthBufferElement::getWriteEnable(state); + } + if (this->function.isIgnored()) { + function = SoDepthBufferElement::getFunction(state); + } + if (this->range.isIgnored()) { + depthrange = SoDepthBufferElement::getRange(state); + } + + SoDepthBufferElement::set(state, testenable, writeenable, + function, depthrange); +} + #if COIN_BUILD_LEGACY_GL_RENDERER // Doc from parent void diff --git a/src/nodes/SoLight.cpp b/src/nodes/SoLight.cpp index 1809c524a3..d59f1a612a 100644 --- a/src/nodes/SoLight.cpp +++ b/src/nodes/SoLight.cpp @@ -72,6 +72,7 @@ #include #include +#include #include #if COIN_BUILD_LEGACY_GL_RENDERER #include @@ -80,6 +81,7 @@ #include #include #include +#include #include "nodes/SoSubNodeP.h" @@ -140,6 +142,15 @@ SoLight::initClass(void) SO_ENABLE(SoCallbackAction, SoLightElement); } +// Doc from superclass. +void +SoLight::doAction(SoAction * action) +{ + SoState * state = action->getState(); + SoLightElement::add(state, this, SoModelMatrixElement::get(state) * + SoViewingMatrixElement::get(state)); +} + // Doc from superclass. void SoLight::callback(SoCallbackAction * action) diff --git a/src/nodes/SoTexture2.cpp b/src/nodes/SoTexture2.cpp index 416a128945..7e81c60bad 100644 --- a/src/nodes/SoTexture2.cpp +++ b/src/nodes/SoTexture2.cpp @@ -663,8 +663,8 @@ SoTexture2::doAction(SoAction * action) if (size != SbVec2s(0,0)) { SoMultiTextureImageElement::set(state, this, unit, size, nc, bytes, - (SoMultiTextureImageElement::Wrap)this->wrapT.getValue(), (SoMultiTextureImageElement::Wrap)this->wrapS.getValue(), + (SoMultiTextureImageElement::Wrap)this->wrapT.getValue(), (SoMultiTextureImageElement::Model) model.getValue(), this->blendColor.getValue()); SoMultiTextureEnabledElement::set(state, this, unit, TRUE); diff --git a/src/nodes/SoVertexProperty.cpp b/src/nodes/SoVertexProperty.cpp index aabf6096a7..da4546d8df 100644 --- a/src/nodes/SoVertexProperty.cpp +++ b/src/nodes/SoVertexProperty.cpp @@ -99,7 +99,6 @@ class SoVBO; #include #include #include - #include #include #include @@ -126,6 +125,7 @@ class SoVBO; #include #include "nodes/SoSubNodeP.h" +#include "elements/SoLazyElementP.h" #if COIN_BUILD_LEGACY_GL_RENDERER #include "rendering/SoVBO.h" #endif @@ -621,10 +621,9 @@ SoVertexProperty::updateMaterial(SoState * state, uint32_t overrideflags, SbBool int num = this->orderedRGBA.getNum(); if (num > 0 && !TEST_OVERRIDE(DIFFUSE_COLOR, overrideflags)) { - - SoLazyElement::setPacked(state, this, num, - this->orderedRGBA.getValues(0), - PRIVATE(this)->transparent); + SoLazyElementP::setPackedVertexColors( + state, this, num, this->orderedRGBA.getValues(0), + PRIVATE(this)->transparent); if (this->isOverride()) { SoOverrideElement::setDiffuseColorOverride(state, this, TRUE); } diff --git a/src/rendering/CMakeLists.txt b/src/rendering/CMakeLists.txt index 1eb44ace05..5fd167a36f 100644 --- a/src/rendering/CMakeLists.txt +++ b/src/rendering/CMakeLists.txt @@ -55,6 +55,7 @@ set(COIN_RENDERING_INTERNAL_FILES CoinGLReadback.cpp SoRenderIRP.h SoRenderPlan.h + SoTextureQualityPolicy.h SoRenderBackend.h SoGLRenderBackend.h ) diff --git a/src/rendering/SoGLImage.cpp b/src/rendering/SoGLImage.cpp index 64094a68fb..d8a6771cf4 100644 --- a/src/rendering/SoGLImage.cpp +++ b/src/rendering/SoGLImage.cpp @@ -219,6 +219,7 @@ #include "tidbitsp.h" #include "rendering/SoGL.h" +#include "rendering/SoTextureQualityPolicy.h" #include "elements/SoTextureScaleQualityElement.h" #include "glue/GLUWrapper.h" #include "glue/glp.h" @@ -233,11 +234,7 @@ // ************************************************************************* -static float DEFAULT_LINEAR_LIMIT = 0.2f; -static float DEFAULT_MIPMAP_LIMIT = 0.5f; -static float DEFAULT_LINEAR_MIPMAP_LIMIT = 0.8f; static float DEFAULT_SCALEUP_LIMIT = 0.7f; -static float DEFAULT_ANISOTROPIC_LIMIT = 0.85f; static float COIN_TEX2_LINEAR_LIMIT = -1.0f; static float COIN_TEX2_MIPMAP_LIMIT = -1.0f; @@ -741,26 +738,16 @@ SoGLImage::SoGLImage(void) PRIVATE(this)->owner = this; // check environment variables + const CoinTextureQualityLimits qualityLimits = + coin_get_texture_quality_limits(); if (COIN_TEX2_LINEAR_LIMIT < 0.0f) { - const char *env = coin_getenv("COIN_TEX2_LINEAR_LIMIT"); - if (env) COIN_TEX2_LINEAR_LIMIT = (float) atof(env); - if (COIN_TEX2_LINEAR_LIMIT < 0.0f || COIN_TEX2_LINEAR_LIMIT > 1.0f) { - COIN_TEX2_LINEAR_LIMIT = DEFAULT_LINEAR_LIMIT; - } + COIN_TEX2_LINEAR_LIMIT = qualityLimits.linear; } if (COIN_TEX2_MIPMAP_LIMIT < 0.0f) { - const char *env = coin_getenv("COIN_TEX2_MIPMAP_LIMIT"); - if (env) COIN_TEX2_MIPMAP_LIMIT = (float) atof(env); - if (COIN_TEX2_MIPMAP_LIMIT < 0.0f || COIN_TEX2_MIPMAP_LIMIT > 1.0f) { - COIN_TEX2_MIPMAP_LIMIT = DEFAULT_MIPMAP_LIMIT; - } + COIN_TEX2_MIPMAP_LIMIT = qualityLimits.mipmap; } if (COIN_TEX2_LINEAR_MIPMAP_LIMIT < 0.0f) { - const char *env = coin_getenv("COIN_TEX2_LINEAR_MIPMAP_LIMIT"); - if (env) COIN_TEX2_LINEAR_MIPMAP_LIMIT = (float) atof(env); - if (COIN_TEX2_LINEAR_MIPMAP_LIMIT < 0.0f || COIN_TEX2_LINEAR_MIPMAP_LIMIT > 1.0f) { - COIN_TEX2_LINEAR_MIPMAP_LIMIT = DEFAULT_LINEAR_MIPMAP_LIMIT; - } + COIN_TEX2_LINEAR_MIPMAP_LIMIT = qualityLimits.linearMipmap; } if (COIN_TEX2_SCALEUP_LIMIT < 0.0f) { @@ -794,9 +781,7 @@ SoGLImage::SoGLImage(void) else COIN_ENABLE_CONFORMANT_GL_CLAMP = 0; } if (COIN_TEX2_ANISOTROPIC_LIMIT < 0.0f) { - const char *env = coin_getenv("COIN_TEX2_ANISOTROPIC_LIMIT"); - if (env) COIN_TEX2_ANISOTROPIC_LIMIT = (float) atof(env); - else COIN_TEX2_ANISOTROPIC_LIMIT = DEFAULT_ANISOTROPIC_LIMIT; + COIN_TEX2_ANISOTROPIC_LIMIT = qualityLimits.anisotropic; } } diff --git a/src/rendering/SoGLRenderBackend.cpp b/src/rendering/SoGLRenderBackend.cpp index c88bdf4500..378bbb644b 100644 --- a/src/rendering/SoGLRenderBackend.cpp +++ b/src/rendering/SoGLRenderBackend.cpp @@ -2,13 +2,18 @@ #include "rendering/SoGLRenderBackend.h" +#include +#include #include +#include #include "glue/glp.h" #include "glue/glslp.h" #include #include +#include +#include #include #include #include @@ -19,9 +24,113 @@ namespace { static constexpr int MAX_VERTEX_COUNT = 10000000; +static constexpr int MAX_SHADER_LIGHTS = 8; static constexpr GLuint POSITION_ATTRIBUTE = 0; -static constexpr GLuint COLOR_ATTRIBUTE = 1; -static constexpr GLuint TEXCOORD_ATTRIBUTE = 2; +static constexpr GLuint NORMAL_ATTRIBUTE = 1; +static constexpr GLuint COLOR_ATTRIBUTE = 2; +static constexpr GLuint TEXCOORD_ATTRIBUTE = 3; + +GLenum +textureWrapToGL(const SoTextureWrap wrap) +{ + switch (wrap) { + case SO_TEXTURE_WRAP_REPEAT: return GL_REPEAT; + case SO_TEXTURE_WRAP_CLAMP_TO_BORDER: return GL_CLAMP_TO_BORDER; + case SO_TEXTURE_WRAP_CLAMP_TO_EDGE: + default: return GL_CLAMP_TO_EDGE; + } +} + +GLenum +textureMinFilterToGL(const SoTextureFilter filter) +{ + switch (filter) { + case SO_TEXTURE_FILTER_LINEAR: return GL_LINEAR; + case SO_TEXTURE_FILTER_NEAREST_MIPMAP_NEAREST: + return GL_NEAREST_MIPMAP_NEAREST; + case SO_TEXTURE_FILTER_LINEAR_MIPMAP_NEAREST: + return GL_LINEAR_MIPMAP_NEAREST; + case SO_TEXTURE_FILTER_NEAREST_MIPMAP_LINEAR: + return GL_NEAREST_MIPMAP_LINEAR; + case SO_TEXTURE_FILTER_LINEAR_MIPMAP_LINEAR: + return GL_LINEAR_MIPMAP_LINEAR; + case SO_TEXTURE_FILTER_NEAREST: + default: return GL_NEAREST; + } +} + +GLenum +textureMagFilterToGL(const SoTextureFilter filter) +{ + return filter == SO_TEXTURE_FILTER_NEAREST ? GL_NEAREST : GL_LINEAR; +} + +GLenum +blendFactorToGL(const SoBlendFactor factor) +{ + switch (factor) { + case SO_BLEND_FACTOR_ZERO: return GL_ZERO; + case SO_BLEND_FACTOR_ONE: return GL_ONE; + case SO_BLEND_FACTOR_SRC_COLOR: return GL_SRC_COLOR; + case SO_BLEND_FACTOR_ONE_MINUS_SRC_COLOR: return GL_ONE_MINUS_SRC_COLOR; + case SO_BLEND_FACTOR_DST_COLOR: return GL_DST_COLOR; + case SO_BLEND_FACTOR_ONE_MINUS_DST_COLOR: return GL_ONE_MINUS_DST_COLOR; + case SO_BLEND_FACTOR_SRC_ALPHA: return GL_SRC_ALPHA; + case SO_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA: return GL_ONE_MINUS_SRC_ALPHA; + case SO_BLEND_FACTOR_DST_ALPHA: return GL_DST_ALPHA; + case SO_BLEND_FACTOR_ONE_MINUS_DST_ALPHA: return GL_ONE_MINUS_DST_ALPHA; + case SO_BLEND_FACTOR_CONSTANT_COLOR: return GL_CONSTANT_COLOR; + case SO_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR: + return GL_ONE_MINUS_CONSTANT_COLOR; + case SO_BLEND_FACTOR_CONSTANT_ALPHA: return GL_CONSTANT_ALPHA; + 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; + 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; + case SO_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA: return GL_ONE_MINUS_SRC_ALPHA; + default: return GL_ONE; + } +} + +bool +isDualSourceBlendFactor(const SoBlendFactor factor) +{ + return factor == SO_BLEND_FACTOR_SRC1_COLOR || + factor == SO_BLEND_FACTOR_ONE_MINUS_SRC1_COLOR || + factor == SO_BLEND_FACTOR_SRC1_ALPHA || + factor == SO_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA; +} + +GLenum +blendEquationToGL(const SoBlendEquation equation) +{ + switch (equation) { + case SO_BLEND_EQUATION_SUBTRACT: return GL_FUNC_SUBTRACT; + case SO_BLEND_EQUATION_REVERSE_SUBTRACT: return GL_FUNC_REVERSE_SUBTRACT; + case SO_BLEND_EQUATION_MIN: return GL_MIN; + case SO_BLEND_EQUATION_MAX: return GL_MAX; + case SO_BLEND_EQUATION_ADD: + default: return GL_FUNC_ADD; + } +} + +GLenum +depthFunctionToGL(const SoDepthFunction function) +{ + switch (function) { + case SO_DEPTH_NEVER: return GL_NEVER; + case SO_DEPTH_ALWAYS: return GL_ALWAYS; + case SO_DEPTH_LESS: return GL_LESS; + case SO_DEPTH_LEQUAL: return GL_LEQUAL; + case SO_DEPTH_EQUAL: return GL_EQUAL; + case SO_DEPTH_GEQUAL: return GL_GEQUAL; + case SO_DEPTH_GREATER: return GL_GREATER; + case SO_DEPTH_NOTEQUAL: return GL_NOTEQUAL; + default: return GL_LEQUAL; + } +} GLenum topologyToGL(const SoPrimitiveTopology topology) @@ -224,9 +333,13 @@ SoGLRenderBackend::initialize(const SoRenderBackendInitParams & params) !this->glue->glEnableVertexAttribArray || !this->glue->glDisableVertexAttribArray || !this->glue->glVertexAttrib4f || + !this->glue->glVertexAttrib3f || !this->glue->glVertexAttrib2f || !this->glue->glUniform1f || !this->glue->glUniform1i || - !this->glue->glUniform4f || !this->glue->glUniformMatrix4fv) { + !this->glue->glUniform3f || !this->glue->glUniform1iv || + !this->glue->glUniform2fv || !this->glue->glUniform3fv || + !this->glue->glUniform4f || !this->glue->glUniformMatrix4fv || + !this->glue->glBlendFuncSeparate) { this->emitError("active context does not provide retained-renderer GL dispatch"); this->glue = nullptr; return FALSE; @@ -248,6 +361,9 @@ SoGLRenderBackend::destroyCacheEntry(CachedCommand & entry) if (entry.positionBuffer) { cc_glglue_glDeleteBuffers(this->glue, 1, &entry.positionBuffer); } + if (entry.normalBuffer) { + cc_glglue_glDeleteBuffers(this->glue, 1, &entry.normalBuffer); + } if (entry.colorBuffer) { cc_glglue_glDeleteBuffers(this->glue, 1, &entry.colorBuffer); } @@ -378,6 +494,21 @@ SoGLRenderBackend::uploadVertexBuffers(CachedCommand & entry, vertexStride, geometry.positions, GL_STATIC_DRAW); + if (geometry.normals && geometry.normalCount >= geometry.vertexCount) { + if (!entry.normalBuffer) { + cc_glglue_glGenBuffers(this->glue, 1, &entry.normalBuffer); + } + 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); + } + else if (entry.normalBuffer) { + cc_glglue_glDeleteBuffers(this->glue, 1, &entry.normalBuffer); + entry.normalBuffer = 0; + } + if (geometry.colors && geometry.vertexCount) { if (!entry.colorBuffer) { cc_glglue_glGenBuffers(this->glue, 1, &entry.colorBuffer); @@ -430,10 +561,30 @@ SoGLRenderBackend::uploadTexture(CachedCommand & entry, glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G, format.swizzle[1]); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, format.swizzle[2]); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, format.swizzle[3]); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, + textureMinFilterToGL(texture.minFilter)); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, + textureMagFilterToGL(texture.magFilter)); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, + textureWrapToGL(texture.wrapS)); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, + textureWrapToGL(texture.wrapT)); + const bool mipmapped = + texture.minFilter == SO_TEXTURE_FILTER_NEAREST_MIPMAP_NEAREST || + texture.minFilter == SO_TEXTURE_FILTER_LINEAR_MIPMAP_NEAREST || + texture.minFilter == SO_TEXTURE_FILTER_NEAREST_MIPMAP_LINEAR || + texture.minFilter == SO_TEXTURE_FILTER_LINEAR_MIPMAP_LINEAR; + if (mipmapped && this->glue->glGenerateMipmap) { + this->glue->glGenerateMipmap(GL_TEXTURE_2D); + } + if (SoGLDriverDatabase::isSupported( + this->glue, SbName(SO_GL_ANISOTROPIC_FILTERING))) { + const float supported = cc_glglue_get_max_anisotropy(this->glue); + if (supported > 1.0f) { + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, + texture.anisotropic ? supported : 1.0f); + } + } cc_glglue_glBindTexture(this->glue, GL_TEXTURE_2D, 0); } @@ -467,11 +618,13 @@ SoGLRenderBackend::updateCacheDescription(CachedCommand & entry, const SoGeometryDesc & geometry = command.geometry; const SoTextureData & texture = command.material.texture; entry.positionsKey = geometry.positions; + entry.normalsKey = geometry.normals; entry.colorsKey = geometry.colors; entry.texcoordsKey = geometry.texcoords; entry.texturePixelsKey = hasTexture ? texture.pixels : nullptr; entry.indicesKey = geometry.indices; entry.vertexCount = geometry.vertexCount; + entry.normalCount = geometry.normalCount; entry.indexCount = geometry.indexCount; entry.vertexStride = vertexStride; entry.texcoordStride = geometry.texcoordStride; @@ -484,6 +637,15 @@ SoGLRenderBackend::updateCacheDescription(CachedCommand & entry, entry.geometryRevision = geometry.revision; entry.textureCacheKey = hasTexture ? texture.cacheKey : 0; entry.textureRevision = hasTexture ? texture.revision : 0; + entry.textureMinFilter = hasTexture ? texture.minFilter + : SO_TEXTURE_FILTER_NEAREST; + entry.textureMagFilter = hasTexture ? texture.magFilter + : SO_TEXTURE_FILTER_NEAREST; + entry.textureWrapS = hasTexture ? texture.wrapS + : SO_TEXTURE_WRAP_CLAMP_TO_EDGE; + entry.textureWrapT = hasTexture ? texture.wrapT + : SO_TEXTURE_WRAP_CLAMP_TO_EDGE; + entry.textureAnisotropic = hasTexture ? texture.anisotropic : false; } void @@ -502,6 +664,17 @@ SoGLRenderBackend::setupVisualVAO(CachedCommand & entry) GL_FLOAT, GL_FALSE, entry.vertexStride, nullptr); } + if (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, + nullptr); + } + else { + cc_glglue_glDisableVertexAttribArray(this->glue, NORMAL_ATTRIBUTE); + this->glue->glVertexAttrib3f(NORMAL_ATTRIBUTE, 0.0f, 0.0f, 1.0f); + } if (entry.colorBuffer) { cc_glglue_glBindBuffer(this->glue, GL_ARRAY_BUFFER, entry.colorBuffer); cc_glglue_glEnableVertexAttribArray(this->glue, COLOR_ATTRIBUTE); @@ -547,7 +720,12 @@ SoGLRenderBackend::textureDescriptionMatches( entry.textureWidth == texture.width && entry.textureHeight == texture.height && entry.textureComponents == texture.numComponents && - entry.textureColorSpace == texture.colorSpace; + entry.textureColorSpace == texture.colorSpace && + entry.textureMinFilter == texture.minFilter && + entry.textureMagFilter == texture.magFilter && + entry.textureWrapS == texture.wrapS && + entry.textureWrapT == texture.wrapT && + entry.textureAnisotropic == texture.anisotropic; } void @@ -586,13 +764,15 @@ SoGLRenderBackend::updateGeometryCache(const SoDrawList & drawlist) const bool identityMatches = geometry.cacheKey != 0 ? entry.geometryCacheKey == geometry.cacheKey && entry.geometryRevision == geometry.revision - : entry.positionsKey == geometry.positions; + : entry.positionsKey == geometry.positions && + entry.normalsKey == geometry.normals; const bool geometryMatches = entry.positionBuffer != 0 && identityMatches && entry.colorsKey == geometry.colors && entry.texcoordsKey == geometry.texcoords && entry.indicesKey == geometry.indices && entry.vertexCount == geometry.vertexCount && + entry.normalCount == geometry.normalCount && entry.indexCount == geometry.indexCount && entry.vertexStride == vertexStride && entry.texcoordStride == geometry.texcoordStride && @@ -608,7 +788,79 @@ SoGLRenderBackend::updateGeometryCache(const SoDrawList & drawlist) } void -SoGLRenderBackend::drawCommand(const SoRenderCommand & command, +SoGLRenderBackend::uploadLighting(const SoDrawList & drawlist, + const SoRenderCommand & command) +{ + const SoLightingData * lighting = drawlist.getLighting(command.lightingHandle); + static const SoLightingData emptyLighting; + if (!lighting) { + lighting = &emptyLighting; + if (command.lightingHandle != 0) { + static std::once_flag invalidHandleWarning; + std::call_once(invalidHandleWarning, []() { + SoDebugError::postWarning( + "SoGLRenderBackend::uploadLighting", + "Ignoring an invalid retained lighting handle; 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]); + + GLint types[MAX_SHADER_LIGHTS] = {}; + GLfloat colors[MAX_SHADER_LIGHTS * 3] = {}; + GLfloat directions[MAX_SHADER_LIGHTS * 3] = {}; + GLfloat positions[MAX_SHADER_LIGHTS * 3] = {}; + GLfloat attenuations[MAX_SHADER_LIGHTS * 3] = {}; + GLfloat spotParams[MAX_SHADER_LIGHTS * 2] = {}; + const int count = std::min(static_cast(lighting->lights.size()), + MAX_SHADER_LIGHTS); + if (static_cast(lighting->lights.size()) > MAX_SHADER_LIGHTS) { + static std::once_flag lightLimitWarning; + std::call_once(lightLimitWarning, []() { + SoDebugError::postWarning( + "SoGLRenderBackend::uploadLighting", + "The retained GL Visual program supports eight lights; additional " + "retained lights are not uploaded."); + }); + } + for (int i = 0; i < count; ++i) { + const SoLightData & light = lighting->lights[static_cast(i)]; + types[i] = static_cast(light.type); + colors[i * 3 + 0] = light.color[0]; + colors[i * 3 + 1] = light.color[1]; + colors[i * 3 + 2] = light.color[2]; + directions[i * 3 + 0] = light.direction[0]; + directions[i * 3 + 1] = light.direction[1]; + directions[i * 3 + 2] = light.direction[2]; + positions[i * 3 + 0] = light.position[0]; + positions[i * 3 + 1] = light.position[1]; + positions[i * 3 + 2] = light.position[2]; + attenuations[i * 3 + 0] = light.attenuation[0]; + attenuations[i * 3 + 1] = light.attenuation[1]; + attenuations[i * 3 + 2] = light.attenuation[2]; + spotParams[i * 2 + 0] = light.spotCutoffCos; + spotParams[i * 2 + 1] = light.spotExponent; + } + 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.lightDirection, MAX_SHADER_LIGHTS, + directions); + this->glue->glUniform3fv(uniforms.lighting.lightPosition, MAX_SHADER_LIGHTS, + positions); + this->glue->glUniform3fv(uniforms.lighting.lightAttenuation, MAX_SHADER_LIGHTS, + attenuations); + this->glue->glUniform2fv(uniforms.lighting.lightSpotParams, MAX_SHADER_LIGHTS, + spotParams); +} + +void +SoGLRenderBackend::drawCommand(const SoDrawList & drawlist, + const SoRenderCommand & command, const SbMat & viewMat, const SbMat & projMat, const SoRenderParams & params) @@ -620,44 +872,168 @@ SoGLRenderBackend::drawCommand(const SoRenderCommand & command, const CachedCommand & entry = this->gpuCache[found->second]; if (!entry.vertexArray) return; - this->bindVisualCommand(command, entry, viewMat, projMat, params); + this->bindVisualCommand(drawlist, command, entry, viewMat, projMat, params); this->drawGeometry(command, entry); } void -SoGLRenderBackend::bindVisualCommand(const SoRenderCommand & command, - const CachedCommand & entry, - const SbMat & viewMat, - const SbMat & projMat, - const SoRenderParams & params) +SoGLRenderBackend::bindTransforms(const SoRenderCommand & command, + const SbMat & viewMat, + const SbMat & projMat) { - applyViewport(params); const VisualProgram::Uniforms & uniforms = this->visualProgram.uniforms; - this->glue->glUniformMatrix4fv(uniforms.view, 1, GL_FALSE, + this->glue->glUniformMatrix4fv(uniforms.transforms.view, 1, GL_FALSE, &viewMat[0][0]); - this->glue->glUniformMatrix4fv(uniforms.projection, 1, GL_FALSE, + this->glue->glUniformMatrix4fv(uniforms.transforms.projection, 1, GL_FALSE, &projMat[0][0]); SbMat model; command.modelMatrix.getValue(model); - this->glue->glUniformMatrix4fv(uniforms.model, 1, GL_FALSE, + 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.color, + this->glue->glUniform4f(uniforms.material.color, color[0], color[1], color[2], color[3]); - this->glue->glUniform1f(uniforms.useVertexColor, + 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); + + const SoShadingModel shadingModel = command.material.shadingModel; + this->glue->glUniform1i(uniforms.material.shadingModel, + static_cast(shadingModel)); + const SbVec4f & emissive = command.material.emissive; + const SbVec4f & ambient = command.material.ambient; + const SbVec4f & specular = command.material.specular; + this->glue->glUniform3f(uniforms.material.emissiveColor, + emissive[0], emissive[1], emissive[2]); + this->glue->glUniform3f(uniforms.material.ambient, + ambient[0], ambient[1], ambient[2]); + this->glue->glUniform3f(uniforms.material.specular, + specular[0], specular[1], specular[2]); + this->glue->glUniform1f(uniforms.material.shininess, + command.material.shininess); + this->glue->glUniform1f(uniforms.material.twoSidedLighting, + command.material.twoSidedLighting ? 1.0f : 0.0f); +} +void +SoGLRenderBackend::applyDepthState(const SoRenderCommand & command) +{ + + if (command.state.depth.enabled) { + glEnable(GL_DEPTH_TEST); + glDepthFunc(depthFunctionToGL(command.state.depth.func)); + } + else { + glDisable(GL_DEPTH_TEST); + } + glDepthMask(command.state.depth.writeEnabled ? GL_TRUE : GL_FALSE); + glDepthRange(command.state.depth.range[0], command.state.depth.range[1]); + +} + +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 { + glDisable(GL_BLEND); + } +} + +void +SoGLRenderBackend::bindAlphaTest(const SoRenderCommand & command) +{ + 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); + +} + +void +SoGLRenderBackend::bindTexture(const SoRenderCommand & command, + const CachedCommand & entry) +{ + const VisualProgram::Uniforms & uniforms = this->visualProgram.uniforms; const bool textured = entry.texture != 0 && entry.texcoordBuffer != 0; - this->glue->glUniform1f(uniforms.textureEnabled, + this->glue->glUniform1f(uniforms.texture.enabled, textured ? 1.0f : 0.0f); if (textured) { cc_glglue_glActiveTexture(this->glue, GL_TEXTURE0); cc_glglue_glBindTexture(this->glue, GL_TEXTURE_2D, entry.texture); - this->glue->glUniform1i(uniforms.texture, 0); - this->glue->glUniform4f(uniforms.textureModulation, - color[0], color[1], color[2], color[3]); + 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) +{ + const VisualProgram & program = this->selectSurfaceProgram(command); + cc_glglue_glUseProgram(this->glue, program.handle); + 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); } void @@ -719,6 +1095,20 @@ SoGLRenderBackend::createShaders() return this->createVisualProgram(); } +const SoGLRenderBackend::VisualProgram & +SoGLRenderBackend::selectSurfaceProgram(const SoRenderCommand & command) const +{ + // Centralize the mapping from retained material semantics to an executor + // implementation. Both current models use the retained Inventor lighting + // evaluation program, with u_shadingModel selecting its defined behavior. + switch (command.material.shadingModel) { + case SO_SHADING_UNLIT: + case SO_SHADING_LEGACY_GOURAUD: + default: + return this->visualProgram; + } +} + bool SoGLRenderBackend::createVisualProgram() { @@ -730,19 +1120,59 @@ SoGLRenderBackend::createVisualProgram() this->visualProgram.handle = program; VisualProgram::Uniforms & uniforms = this->visualProgram.uniforms; - uniforms.view = cc_glglue_glGetUniformLocation(this->glue, program, "u_view"); - uniforms.projection = cc_glglue_glGetUniformLocation( + uniforms.transforms.view = cc_glglue_glGetUniformLocation(this->glue, program, "u_view"); + uniforms.transforms.projection = cc_glglue_glGetUniformLocation( this->glue, program, "u_proj"); - uniforms.model = cc_glglue_glGetUniformLocation(this->glue, program, "u_model"); - uniforms.color = cc_glglue_glGetUniformLocation(this->glue, program, "u_color"); - uniforms.useVertexColor = cc_glglue_glGetUniformLocation( + 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.texture = cc_glglue_glGetUniformLocation( + 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.textureEnabled = cc_glglue_glGetUniformLocation( + uniforms.texture.enabled = cc_glglue_glGetUniformLocation( this->glue, program, "u_textureEnabled"); - uniforms.textureModulation = cc_glglue_glGetUniformLocation( - this->glue, program, "u_texModColor"); + 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"); return true; } @@ -771,8 +1201,8 @@ SoGLRenderBackend::render(const SoDrawList & drawlist, this->emitError("render plan references a missing DrawList command"); return FALSE; } - this->drawCommand(drawlist.getCommand(static_cast(commandIndex)), - view, projection, params); + this->drawCommand(drawlist, drawlist.getCommand( + static_cast(commandIndex)), view, projection, params); } cc_glglue_glUseProgram(this->glue, 0); return TRUE; diff --git a/src/rendering/SoGLRenderBackend.h b/src/rendering/SoGLRenderBackend.h index 81e671d6af..26534e988e 100644 --- a/src/rendering/SoGLRenderBackend.h +++ b/src/rendering/SoGLRenderBackend.h @@ -61,6 +61,7 @@ class COIN_DLL_API SoGLRenderBackend : public SoRenderBackend { struct CachedCommand { GLuint positionBuffer = 0; + GLuint normalBuffer = 0; GLuint colorBuffer = 0; GLuint texcoordBuffer = 0; GLuint texture = 0; @@ -68,11 +69,13 @@ class COIN_DLL_API SoGLRenderBackend : public SoRenderBackend { GLuint vertexArray = 0; const float * positionsKey = nullptr; + const float * normalsKey = nullptr; const float * colorsKey = nullptr; const float * texcoordsKey = nullptr; const unsigned char * texturePixelsKey = nullptr; const uint32_t * indicesKey = nullptr; uint32_t vertexCount = 0; + uint32_t normalCount = 0; uint32_t indexCount = 0; uint32_t vertexStride = 0; uint32_t texcoordStride = 0; @@ -80,6 +83,11 @@ class COIN_DLL_API SoGLRenderBackend : public SoRenderBackend { int textureHeight = 0; int textureComponents = 0; SoTextureColorSpace textureColorSpace = SO_TEXTURE_COLORSPACE_LEGACY; + SoTextureFilter textureMinFilter = SO_TEXTURE_FILTER_NEAREST; + SoTextureFilter textureMagFilter = SO_TEXTURE_FILTER_NEAREST; + SoTextureWrap textureWrapS = SO_TEXTURE_WRAP_CLAMP_TO_EDGE; + SoTextureWrap textureWrapT = SO_TEXTURE_WRAP_CLAMP_TO_EDGE; + bool textureAnisotropic = false; uint64_t geometryCacheKey = 0; uint64_t geometryRevision = 0; uint64_t textureCacheKey = 0; @@ -92,26 +100,61 @@ class COIN_DLL_API SoGLRenderBackend : public SoRenderBackend { GLuint handle = 0; struct Uniforms { - GLint view = -1; - GLint projection = -1; - GLint model = -1; - GLint color = -1; - GLint useVertexColor = -1; - GLint texture = -1; - GLint textureEnabled = -1; - GLint textureModulation = -1; + 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; } uniforms; } visualProgram; bool createShaders(); bool createVisualProgram(); + const VisualProgram & selectSurfaceProgram( + const SoRenderCommand & command) const; void beginFrame(const SoRenderParams & params); void invalidateCache(); void updateGeometryCache(const SoDrawList & drawlist); - void drawCommand(const SoRenderCommand & command, + void drawCommand(const SoDrawList & drawlist, + const SoRenderCommand & command, const SbMat & viewMat, const SbMat & projMat, const SoRenderParams & params); + void uploadLighting(const SoDrawList & drawlist, + const SoRenderCommand & command); CachedCommand & getOrCreateCache(const SoRenderCommand * command); void uploadGeometry(CachedCommand & entry, @@ -129,11 +172,22 @@ class COIN_DLL_API SoGLRenderBackend : public SoRenderBackend { uint32_t vertexStride); void setupVisualVAO(CachedCommand & entry); void destroyCacheEntry(CachedCommand & entry); - void bindVisualCommand(const SoRenderCommand & command, + 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, diff --git a/src/rendering/SoRenderIR.cpp b/src/rendering/SoRenderIR.cpp index 76a37180d1..554fdc1104 100644 --- a/src/rendering/SoRenderIR.cpp +++ b/src/rendering/SoRenderIR.cpp @@ -1,6 +1,8 @@ // src/rendering/SoRenderIR.cpp #include "rendering/SoRenderIRP.h" +#include "rendering/SoTextureQualityPolicy.h" +#include "elements/SoLazyElementP.h" #include #include @@ -11,9 +13,12 @@ #include #include #include +#include +#include #include #include #include +#include #include #include #include @@ -29,6 +34,7 @@ #include #include #include +#include #include namespace { @@ -140,6 +146,49 @@ alphaTestFunctionFromLegacyGL(const int value) } } +SoTextureModel +textureModelFromLegacy(SoMultiTextureImageElement::Model model) +{ + switch (model) { + case SoMultiTextureImageElement::DECAL: + return SO_TEXTURE_MODEL_DECAL; + case SoMultiTextureImageElement::BLEND: + return SO_TEXTURE_MODEL_BLEND; + case SoMultiTextureImageElement::REPLACE: + return SO_TEXTURE_MODEL_REPLACE; + case SoMultiTextureImageElement::MODULATE: + default: + return SO_TEXTURE_MODEL_MODULATE; + } +} + +void +textureFiltersFromQuality(const float quality, SoTextureData & texture) +{ + const CoinTextureQualityPolicy policy = + coin_get_texture_quality_policy(quality); + if (!policy.linear) { + texture.minFilter = SO_TEXTURE_FILTER_NEAREST; + texture.magFilter = SO_TEXTURE_FILTER_NEAREST; + } + else if (!policy.mipmap) { + texture.minFilter = SO_TEXTURE_FILTER_LINEAR; + texture.magFilter = SO_TEXTURE_FILTER_LINEAR; + } + else if (!policy.linearMipmap) { + texture.minFilter = SO_TEXTURE_FILTER_NEAREST_MIPMAP_LINEAR; + texture.magFilter = SO_TEXTURE_FILTER_LINEAR; + } + else { + texture.minFilter = SO_TEXTURE_FILTER_LINEAR_MIPMAP_LINEAR; + texture.magFilter = SO_TEXTURE_FILTER_LINEAR; + } + + // Preserve the LegacyGL quality policy in the neutral IR. The GL + // executor selects the active driver's supported anisotropy level. + texture.anisotropic = policy.anisotropic; +} + } // namespace SbBool @@ -387,29 +436,76 @@ SoIRDumpFirstN(const SoDrawList & drawlist, int count) namespace SoRenderIR { +static void fillTextureFromState(SoState * state, SoIRRenderAction * action, + SoMaterialData & material); + +static SoTextureWrap +textureWrapFromLegacy(SoMultiTextureImageElement::Wrap wrap) +{ + switch (wrap) { + case SoMultiTextureImageElement::REPEAT: + return SO_TEXTURE_WRAP_REPEAT; + case SoMultiTextureImageElement::CLAMP_TO_BORDER: + return SO_TEXTURE_WRAP_CLAMP_TO_BORDER; + case SoMultiTextureImageElement::CLAMP: + default: + // GL_CLAMP is the historical Coin spelling for edge clamping here. + return SO_TEXTURE_WRAP_CLAMP_TO_EDGE; + } +} + +static bool +textureHasTransparency(const SoTextureData & texture) +{ + // DECAL uses texture alpha as a color interpolation factor; it does not + // change the fragment alpha that controls coverage or blending. + if (texture.model == SO_TEXTURE_MODEL_DECAL) return false; + if (texture.hasTransparency) return true; + if (!texture.pixels || texture.width <= 0 || texture.height <= 0 || + (texture.numComponents != 2 && texture.numComponents != 4)) { + return false; + } + const size_t pixelCount = static_cast(texture.width) * + static_cast(texture.height); + for (size_t pixel = 0; pixel < pixelCount; ++pixel) { + if (texture.pixels[pixel * static_cast(texture.numComponents) + + static_cast(texture.numComponents - 1)] != 0xffu) { + return true; + } + } + return false; +} + void -fillCommandStateFromState(SoState * state, SoDrawList & drawlist, - SoRenderCommand & command) +fillCommandStateFromAction(SoIRRenderAction * action, + SoRenderCommand & command, + const int materialIndex) { + 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); + fillMaterialFromState(state, command.material, materialIndex); + fillTextureFromState(state, action, command.material); fillRenderStateFromState(state, command.state); - ensureMaterialBlendState(command.state, command.material); command.lightingHandle = fillLightingFromState(state, drawlist); } void -fillMaterialFromState(SoState * state, SoMaterialData & material) +fillMaterialFromState(SoState * state, SoMaterialData & material, + int materialIndex) { SoState * mutableState = state; - const SbColor & diffuse = SoLazyElement::getDiffuse(mutableState, 0); + const SbColor & diffuse = SoLazyElement::getDiffuse(mutableState, materialIndex); const SbColor & ambient = SoLazyElement::getAmbient(mutableState); const SbColor & specular = SoLazyElement::getSpecular(mutableState); const SbColor & emissive = SoLazyElement::getEmissive(mutableState); - const float transparency = SoLazyElement::getTransparency(mutableState, 0); + const float transparency = SoLazyElement::getTransparency(mutableState, materialIndex); + // Keep diffuse and emissive independent. The explicit lighting shader owns + // emissive contribution, so inferring diffuse from a default-looking + // material would double-count emissive-only materials. material.diffuse.setValue(diffuse[0], diffuse[1], diffuse[2], 1.0f - transparency); @@ -429,10 +525,53 @@ fillMaterialFromState(SoState * state, SoMaterialData & material) material.shininess = SoLazyElement::getShininess(mutableState); material.opacity = 1.0f - transparency; + material.texture = SoTextureData(); material.textureAlphaIncludesOpacity = false; material.vertexColorAlphaIncludesOpacity = false; } +static void +fillTextureFromState(SoState * state, SoIRRenderAction * action, + SoMaterialData & material) +{ + if (!state || !action || !SoMultiTextureEnabledElement::get(state, 0)) { + return; + } + + 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 < 1 || numComponents > 4) { + return; + } + + const size_t pixelCount = static_cast(size[0]) * + static_cast(size[1]); + const size_t byteCount = pixelCount * static_cast(numComponents); + bool hasTransparency = false; + const unsigned char * copy = action->allocateTextureStorage( + bytes, byteCount, size[0], size[1], numComponents, hasTransparency); + + material.texture.pixels = copy; + material.texture.width = size[0]; + material.texture.height = size[1]; + material.texture.numComponents = numComponents; + material.texture.hasTransparency = hasTransparency; + material.texture.wrapS = textureWrapFromLegacy(wrapS); + material.texture.wrapT = textureWrapFromLegacy(wrapT); + material.texture.model = textureModelFromLegacy(model); + material.texture.blendColor.setValue(blendColor[0], blendColor[1], + blendColor[2], 1.0f); + textureFiltersFromQuality(SoTextureQualityElement::get(state), + material.texture); +} + void fillRenderStateFromState(SoState * state, SoRenderState & rs) { @@ -468,6 +607,7 @@ fillRenderStateFromState(SoState * state, SoRenderState & rs) rs.blend.dstAlphaFactor = rs.blend.dstRGBFactor; } + // LegacyGL does not expose a Coin state element for blend equations. ADD // is its effective equation and is the only value that can be captured // deterministically from traversal. @@ -475,9 +615,9 @@ fillRenderStateFromState(SoState * state, SoRenderState & rs) rs.blend.alphaEquation = SO_BLEND_EQUATION_ADD; float alphaTestValue = 0.5f; - const int alphaTestFunction = SoLazyElement::getAlphaTest(mutableState, - alphaTestValue); - rs.alphaTest.function = alphaTestFunctionFromLegacyGL(alphaTestFunction); + const int alphaTestFunction = SoLazyElementP::getAlphaTestSemantic( + mutableState, alphaTestValue); + rs.alphaTest.function = static_cast(alphaTestFunction); rs.alphaTest.reference = alphaTestValue; rs.alphaTest.policy = rs.alphaTest.function == SO_ALPHA_TEST_NONE ? SO_ALPHA_TEST_POLICY_NONE @@ -613,7 +753,7 @@ fillLightingFromState(SoState * state, SoDrawList & drawlist) bool isMaterialTransparent(const SoMaterialData & material) { - return material.opacity < 0.999f; + return material.opacity < 0.999f || textureHasTransparency(material.texture); } void @@ -624,8 +764,7 @@ ensureMaterialBlendState(SoRenderState & renderState, // legacy GL action enables the conventional blend function as part of its // transparency setup. Make that implicit IR contract explicit without // replacing an actual non-standard blend state. - if (renderState.blend.enabled || - !isMaterialTransparent(material)) { + if (renderState.blend.enabled || !isMaterialTransparent(material)) { return; } @@ -638,4 +777,28 @@ ensureMaterialBlendState(SoRenderState & renderState, renderState.blend.alphaEquation = SO_BLEND_EQUATION_ADD; } +void +finalizeCommand(SoRenderCommand & command) +{ + ensureMaterialBlendState(command.state, command.material); + bool transparent = isMaterialTransparent(command.material); + if (!transparent && command.geometry.colors) { + for (uint32_t i = 0; i < command.geometry.vertexCount; ++i) { + if (command.geometry.colors[i * 4 + 3] < 0.999f) { + transparent = true; + break; + } + } + } + command.opacityClass = transparent + ? SO_OPACITY_TRANSPARENT : SO_OPACITY_OPAQUE; + if (transparent && !command.state.blend.enabled) { + 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; + } +} + } // namespace SoRenderIR diff --git a/src/rendering/SoRenderIRP.h b/src/rendering/SoRenderIRP.h index 774472c692..6f0178b55c 100644 --- a/src/rendering/SoRenderIRP.h +++ b/src/rendering/SoRenderIRP.h @@ -58,10 +58,12 @@ SbBool coin_render_ir_trace_enabled(); */ namespace SoRenderIR { //! Capture the ordinary traversal state shared by retained shape producers. -void fillCommandStateFromState(SoState * state, SoDrawList & drawlist, - SoRenderCommand & command); +void fillCommandStateFromAction(SoIRRenderAction * action, + SoRenderCommand & command, + int materialIndex = 0); //! Fill a material snapshot from the current Inventor traversal state. -void fillMaterialFromState(SoState * state, SoMaterialData & material); +void fillMaterialFromState(SoState * state, SoMaterialData & material, + int materialIndex = 0); //! Fill render-state fields from the current Inventor traversal state. void fillRenderStateFromState(SoState * state, SoRenderState & renderState); //! Complete blend state after material opacity has been captured. @@ -71,6 +73,8 @@ void ensureMaterialBlendState(SoRenderState & renderState, SoLightingHandle fillLightingFromState(SoState * state, SoDrawList & drawlist); //! Return whether the material should be treated as translucent. bool isMaterialTransparent(const SoMaterialData & material); +//! Complete derived command state after producer-specific adjustments. +void finalizeCommand(SoRenderCommand & command); } #endif // COIN_SORENDERIRP_H diff --git a/src/rendering/SoRenderPlan.cpp b/src/rendering/SoRenderPlan.cpp index ee7765961d..5b29e0adf2 100644 --- a/src/rendering/SoRenderPlan.cpp +++ b/src/rendering/SoRenderPlan.cpp @@ -1,18 +1,64 @@ #include "rendering/SoRenderPlan.h" +#include + void SoRenderPlanner::build(const SoDrawList & drawlist, - const SbMatrix &, + const SbMatrix & frameViewMatrix, SoRenderPlan & plan) const { - plan.draws.clear(); - plan.draws.reserve(static_cast(drawlist.getNumCommands())); + struct PlannedEntry { + SoPlannedDraw draw; + SoOpacityClass opacity = SO_OPACITY_OPAQUE; + float depth = 0.0f; + }; + + std::vector entries; + entries.reserve(static_cast(drawlist.getNumCommands())); for (uint32_t commandIndex = 0; commandIndex < static_cast(drawlist.getNumCommands()); ++commandIndex) { - SoPlannedDraw draw; - draw.commandIndex = commandIndex; - plan.draws.push_back(draw); + const SoRenderCommand & command = drawlist.getCommand( + static_cast(commandIndex)); + SbMat view; + const SbMatrix & effectiveView = command.state.useCommandMatrices + ? command.viewMatrix : frameViewMatrix; + effectiveView.getValue(view); + SbVec3f worldCenter; + command.modelMatrix.multVecMatrix( + command.geometry.hasBounds ? command.geometry.boundsCenter + : SbVec3f(0.0f, 0.0f, 0.0f), + worldCenter); + const float worldX = worldCenter[0]; + const float worldY = worldCenter[1]; + const float worldZ = worldCenter[2]; + const float eyeZ = view[0][2] * worldX + + view[1][2] * worldY + + view[2][2] * worldZ + + view[3][2]; + + PlannedEntry entry; + entry.draw.commandIndex = commandIndex; + entry.opacity = command.opacityClass; + entry.depth = -eyeZ; + entries.push_back(entry); + } + + std::stable_sort(entries.begin(), entries.end(), + [](const PlannedEntry & lhs, const PlannedEntry & rhs) { + if (lhs.opacity != rhs.opacity) { + return lhs.opacity == SO_OPACITY_OPAQUE; + } + if (lhs.opacity == SO_OPACITY_OPAQUE) { + return false; + } + return lhs.depth > rhs.depth; + }); + + plan.draws.clear(); + plan.draws.reserve(entries.size()); + for (const PlannedEntry & entry : entries) { + plan.draws.push_back(entry.draw); } } diff --git a/src/rendering/SoRenderPlan.h b/src/rendering/SoRenderPlan.h index a54d03259e..4b4e12b956 100644 --- a/src/rendering/SoRenderPlan.h +++ b/src/rendering/SoRenderPlan.h @@ -21,6 +21,8 @@ struct SoPlannedDraw { The plan owns no command data. It contains only stable indices into the source SoDrawList and is therefore cheap to rebuild for each invocation. + Opaque commands retain insertion order; transparent commands are resolved + back-to-front after opaque commands. */ class SoRenderPlan { public: @@ -41,6 +43,7 @@ class SoRenderPlan { */ class COIN_DLL_API SoRenderPlanner { public: + //! Resolve semantic command state into backend execution order. void build(const SoDrawList & drawlist, const SbMatrix & frameViewMatrix, SoRenderPlan & plan) const; }; diff --git a/src/rendering/SoTextureQualityPolicy.h b/src/rendering/SoTextureQualityPolicy.h new file mode 100644 index 0000000000..5b0f845af2 --- /dev/null +++ b/src/rendering/SoTextureQualityPolicy.h @@ -0,0 +1,82 @@ +// src/rendering/SoTextureQualityPolicy.h + +#ifndef COIN_SOTEXTUREQUALITYPOLICY_H +#define COIN_SOTEXTUREQUALITYPOLICY_H + +#include "tidbitsp.h" + +#include + +// This is the shared semantic quality decision used by LegacyGL and retained +// capture. API-specific code maps the result to its own sampler state. +struct CoinTextureQualityLimits { + float linear = 0.2f; + float mipmap = 0.5f; + float linearMipmap = 0.8f; + float anisotropic = 0.85f; +}; + +inline float +coin_texture_quality_limit(const char * name, const float fallback) +{ + const char * value = coin_getenv(name); + if (!value) return fallback; + const float parsed = static_cast(std::atof(value)); + return parsed >= 0.0f && parsed <= 1.0f ? parsed : fallback; +} + +inline float +coin_texture_anisotropic_limit() +{ + const char * value = coin_getenv("COIN_TEX2_ANISOTROPIC_LIMIT"); + return value ? static_cast(std::atof(value)) : 0.85f; +} + +inline CoinTextureQualityLimits +coin_read_texture_quality_limits() +{ + CoinTextureQualityLimits limits; + limits.linear = coin_texture_quality_limit( + "COIN_TEX2_LINEAR_LIMIT", limits.linear); + limits.mipmap = coin_texture_quality_limit( + "COIN_TEX2_MIPMAP_LIMIT", limits.mipmap); + limits.linearMipmap = coin_texture_quality_limit( + "COIN_TEX2_LINEAR_MIPMAP_LIMIT", limits.linearMipmap); + // LegacyGL intentionally accepts the anisotropic threshold verbatim. In + // particular, it does not clamp out-of-range environment values the way + // the ordinary quality thresholds are validated. + limits.anisotropic = coin_texture_anisotropic_limit(); + return limits; +} + +inline const CoinTextureQualityLimits & +coin_get_texture_quality_limits() +{ + // SoGLImage caches these values on first use. Keep retained capture on the + // same one-time snapshot instead of rereading the environment for every + // command. + static const CoinTextureQualityLimits limits = + coin_read_texture_quality_limits(); + return limits; +} + +struct CoinTextureQualityPolicy { + bool linear = false; + bool mipmap = false; + bool linearMipmap = false; + bool anisotropic = false; +}; + +inline CoinTextureQualityPolicy +coin_get_texture_quality_policy(const float quality) +{ + const CoinTextureQualityLimits & limits = coin_get_texture_quality_limits(); + CoinTextureQualityPolicy policy; + policy.linear = quality >= limits.linear; + policy.mipmap = quality >= limits.mipmap; + policy.linearMipmap = quality >= limits.linearMipmap; + policy.anisotropic = quality > limits.anisotropic; + return policy; +} + +#endif // COIN_SOTEXTUREQUALITYPOLICY_H diff --git a/src/shapenodes/SoShape.cpp b/src/shapenodes/SoShape.cpp index f277d0828f..14363f5b41 100644 --- a/src/shapenodes/SoShape.cpp +++ b/src/shapenodes/SoShape.cpp @@ -50,6 +50,8 @@ class SoVBO; #include #include +#include "elements/SoLazyElementP.h" + #include #include #include @@ -154,52 +156,54 @@ class SoVBO; namespace { -static SbVec4f -so_ir_effective_vertex_color(SoState * state, int materialIndex) -{ - if (materialIndex < 0) materialIndex = 0; - const SbColor & diffuse = SoLazyElement::getDiffuse(state, materialIndex); - const float transparency = SoLazyElement::getTransparency( - state, materialIndex); - return SbVec4f(diffuse[0], diffuse[1], diffuse[2], 1.0f - transparency); -} - struct SoIRVertex { SbVec3f position; SbVec3f normal; SbVec4f texcoord; - SbVec4f color; + int materialIndex = 0; +}; + +struct SoIRBatch { + size_t first = 0; + size_t count = 0; + int materialIndex = -1; + + SoIRBatch(size_t first, size_t count, int materialIndex) + : first(first), count(count), materialIndex(materialIndex) {} +}; + +struct SoIRMaterialBatchPlan { + std::vector batches; + bool needsVertexColors = false; }; class SoIRPrimitiveAssembler : public SoIRRenderAction::PrimitiveCollector { public: SoIRPrimitiveAssembler(SoIRRenderAction * action, SoShape * shape) - : action(action), shape(shape), topology(SO_TOPOLOGY_COUNT), - hasVertexColors(SoMaterialBindingElement::get(action->getState()) != - SoMaterialBindingElement::OVERALL) {} + : action(action), shape(shape), topology(SO_TOPOLOGY_COUNT) {} void onTriangle(const SoPrimitiveVertex * v1, const SoPrimitiveVertex * v2, const SoPrimitiveVertex * v3) override { this->setTopology(SO_TOPOLOGY_TRIANGLES); - this->append(v1); - this->append(v2); - this->append(v3); + this->append(v1, v1->getMaterialIndex()); + this->append(v2, v2->getMaterialIndex()); + this->append(v3, v3->getMaterialIndex()); } void onLine(const SoPrimitiveVertex * v1, const SoPrimitiveVertex * v2) override { this->setTopology(SO_TOPOLOGY_LINES); - this->append(v1); - this->append(v2); + this->append(v1, v1->getMaterialIndex()); + this->append(v2, v2->getMaterialIndex()); } void onPoint(const SoPrimitiveVertex * v) override { this->setTopology(SO_TOPOLOGY_POINTS); - this->append(v); + this->append(v, v->getMaterialIndex()); } void finalize() @@ -212,18 +216,67 @@ class SoIRPrimitiveAssembler : public SoIRRenderAction::PrimitiveCollector { { if (this->vertices.empty()) return; - SoRenderCommand command = {}; - this->fillGeometry(command.geometry); - SoRenderIR::fillCommandStateFromState( - this->action->getState(), this->action->getMutableDrawList(), command); - command.material.vertexColorAlphaIncludesOpacity = - command.geometry.colors != nullptr; - this->action->addCommand(command); - + SoState * state = this->action->getState(); + SoGeometryDesc geometry = {}; + std::vector batches; + this->fillGeometry(state, geometry, batches); + this->emitCommands(state, geometry, batches); this->vertices.clear(); } - void fillGeometry(SoGeometryDesc & geometry) + SoIRMaterialBatchPlan buildMaterialBatches(SoState * state) + { + SoIRMaterialBatchPlan plan; + const size_t count = this->vertices.size(); + const size_t primitiveWidth = this->topology == SO_TOPOLOGY_TRIANGLES ? 3 + : this->topology == SO_TOPOLOGY_LINES ? 2 : 1; + + const SoMaterialBindingElement::Binding materialBinding = + SoMaterialBindingElement::get(state); + const bool hasExplicitMaterialIndices = + materialBinding == SoMaterialBindingElement::PER_PART || + materialBinding == SoMaterialBindingElement::PER_PART_INDEXED || + materialBinding == SoMaterialBindingElement::PER_FACE || + materialBinding == SoMaterialBindingElement::PER_FACE_INDEXED || + materialBinding == SoMaterialBindingElement::PER_VERTEX_INDEXED; + const bool hasPerVertexMaterials = + materialBinding == SoMaterialBindingElement::PER_VERTEX || + materialBinding == SoMaterialBindingElement::PER_VERTEX_INDEXED; + + if (!hasExplicitMaterialIndices) { + plan.batches.push_back(SoIRBatch(0, count, 0)); + } + for (size_t first = 0; hasExplicitMaterialIndices && first < count;) { + const size_t primitiveCount = std::min(primitiveWidth, count - first); + int materialIndex = this->vertices[first].materialIndex; + for (size_t i = 1; i < primitiveCount; ++i) { + if (this->vertices[first + i].materialIndex != materialIndex) { + materialIndex = -1; + break; + } + } + if (plan.batches.empty() || + plan.batches.back().materialIndex != materialIndex) { + plan.batches.push_back(SoIRBatch(first, primitiveCount, materialIndex)); + } + else { + plan.batches.back().count += primitiveCount; + } + first += primitiveCount; + } + plan.needsVertexColors = hasPerVertexMaterials; + for (const SoIRBatch & batch : plan.batches) { + if (batch.materialIndex < 0) { + plan.needsVertexColors = true; + break; + } + } + return plan; + } + + void fillGeometry(SoState * state, + SoGeometryDesc & geometry, + std::vector & batches) { const size_t count = this->vertices.size(); geometry.topology = this->topology; @@ -238,8 +291,11 @@ class SoIRPrimitiveAssembler : public SoIRRenderAction::PrimitiveCollector { this->action->allocateGeometryStorage(sizeof(float) * 3 * count)); float * texcoords = static_cast( this->action->allocateGeometryStorage(sizeof(float) * 4 * count)); + const SoIRMaterialBatchPlan plan = this->buildMaterialBatches(state); + batches = plan.batches; + float * colors = nullptr; - if (this->hasVertexColors) { + if (plan.needsVertexColors) { colors = static_cast( this->action->allocateGeometryStorage(sizeof(float) * 4 * count)); } @@ -257,10 +313,13 @@ class SoIRPrimitiveAssembler : public SoIRRenderAction::PrimitiveCollector { texcoords[i * 4 + 2] = vertex.texcoord[2]; texcoords[i * 4 + 3] = vertex.texcoord[3]; if (colors) { - colors[i * 4 + 0] = vertex.color[0]; - colors[i * 4 + 1] = vertex.color[1]; - colors[i * 4 + 2] = vertex.color[2]; - colors[i * 4 + 3] = vertex.color[3]; + const int materialIndex = std::max(vertex.materialIndex, 0); + const SbColor & color = SoLazyElement::getDiffuse(state, materialIndex); + const float alpha = 1.0f - SoLazyElement::getTransparency(state, materialIndex); + colors[i * 4 + 0] = color[0]; + colors[i * 4 + 1] = color[1]; + colors[i * 4 + 2] = color[2]; + colors[i * 4 + 3] = alpha; } } @@ -270,6 +329,39 @@ class SoIRPrimitiveAssembler : public SoIRRenderAction::PrimitiveCollector { geometry.colors = colors; } + void emitCommands(SoState * state, + const SoGeometryDesc & sourceGeometry, + const std::vector & batches) + { + for (const SoIRBatch & batch : batches) { + SoRenderCommand command = {}; + command.geometry = sourceGeometry; + command.geometry.vertexCount = static_cast(batch.count); + command.geometry.normalCount = command.geometry.vertexCount; + command.geometry.positions = sourceGeometry.positions + batch.first * 3; + command.geometry.normals = sourceGeometry.normals + batch.first * 3; + command.geometry.texcoords = sourceGeometry.texcoords + batch.first * 4; + command.geometry.colors = sourceGeometry.colors + ? sourceGeometry.colors + batch.first * 4 : nullptr; + + SoRenderIR::fillCommandStateFromAction( + this->action, command, std::max(batch.materialIndex, 0)); + const bool packedVertexColors = + SoLazyElementP::hasPackedVertexColorState(state); + if (packedVertexColors) { + const float inheritedOpacity = + SoLazyElementP::getPackedVertexColorOpacity( + state, std::max(batch.materialIndex, 0)); + command.material.opacity = inheritedOpacity; + command.material.diffuse[3] = inheritedOpacity; + } + command.material.vertexColorAlphaIncludesOpacity = + command.geometry.colors != nullptr && !packedVertexColors; + SoRenderIR::finalizeCommand(command); + this->action->addCommand(command); + } + } + void setTopology(SoPrimitiveTopology candidate) { if (this->topology == candidate) return; @@ -278,23 +370,19 @@ class SoIRPrimitiveAssembler : public SoIRRenderAction::PrimitiveCollector { this->topology = candidate; } - void append(const SoPrimitiveVertex * vertex) + void append(const SoPrimitiveVertex * vertex, int materialIndex) { SoIRVertex copy; copy.position = vertex->getPoint(); copy.normal = vertex->getNormal(); copy.texcoord = vertex->getTextureCoords(); - if (this->hasVertexColors) { - copy.color = so_ir_effective_vertex_color( - this->action->getState(), vertex->getMaterialIndex()); - } + copy.materialIndex = materialIndex; this->vertices.push_back(copy); } SoIRRenderAction * action; SoShape * shape; SoPrimitiveTopology topology; - bool hasVertexColors; std::vector vertices; }; diff --git a/testsuite/CMakeLists.txt b/testsuite/CMakeLists.txt index be63634b76..cd1c783ebb 100644 --- a/testsuite/CMakeLists.txt +++ b/testsuite/CMakeLists.txt @@ -196,6 +196,8 @@ if(COIN_BUILD_GL_TESTS_EFFECTIVE) target_link_libraries(OffscreenReadbackTest CoinGLReadbackTestSupport) coin_add_gl_test(NAME DrawListGLTest PROFILE core SOURCES DrawListGLTest.cpp) + coin_add_gl_test(NAME RetainedMaterialLightingGLTest PROFILE core + SOURCES RetainedMaterialLightingGLTest.cpp) endif() function(coin_add_egl_test) @@ -311,7 +313,15 @@ target_include_directories(RetainedMixedTopologyTest PRIVATE ${PROJECT_BINARY_DIR}/include ${COIN_TARGET_INCLUDE_DIRECTORIES} ) +add_executable(RetainedMaterialLightingTest RetainedMaterialLightingTest.cpp) +target_link_libraries(RetainedMaterialLightingTest Coin ${COIN_TARGET_LINK_LIBRARIES}) +target_include_directories(RetainedMaterialLightingTest PRIVATE + ${PROJECT_SOURCE_DIR}/include + ${PROJECT_BINARY_DIR}/include + ${COIN_TARGET_INCLUDE_DIRECTORIES} +) add_test(NAME RetainedMixedTopologyTest COMMAND RetainedMixedTopologyTest) +add_test(NAME RetainedMaterialLightingTest COMMAND RetainedMaterialLightingTest) if(HAVE_EGL) coin_add_egl_test(NAME EGLBindingTest SOURCES EGLBindingTest.cpp LABELS requires-egl) diff --git a/testsuite/RenderPlanTest.cpp b/testsuite/RenderPlanTest.cpp index 3078729950..e40251e15b 100644 --- a/testsuite/RenderPlanTest.cpp +++ b/testsuite/RenderPlanTest.cpp @@ -70,5 +70,58 @@ main() plan.getDraw(0).commandIndex == 0, "reusing a cleared plan did not rebuild its operations") && result; + + SoDrawList transparencyDrawList; + SoRenderCommand opaque; + SoRenderCommand transparentNear; + SoRenderCommand transparentFar; + opaque.viewMatrix.makeIdentity(); + opaque.modelMatrix.makeIdentity(); + transparentNear.viewMatrix.makeIdentity(); + transparentNear.modelMatrix.makeIdentity(); + transparentFar.viewMatrix.makeIdentity(); + transparentFar.modelMatrix.makeIdentity(); + transparentNear.opacityClass = SO_OPACITY_TRANSPARENT; + transparentFar.opacityClass = SO_OPACITY_TRANSPARENT; + transparentFar.modelMatrix.setTranslate(SbVec3f(0.0f, 0.0f, -2.0f)); + transparencyDrawList.addCommand(opaque); + transparencyDrawList.addCommand(transparentNear); + transparencyDrawList.addCommand(transparentFar); + planner.build(transparencyDrawList, frameViewMatrix, plan); + result = check(plan.getNumDraws() == 3 && + plan.getDraw(0).commandIndex == 0 && + plan.getDraw(1).commandIndex == 2 && + plan.getDraw(2).commandIndex == 1, + "planner did not schedule transparent commands back-to-front") && + result; + + SbMatrix movedFrameView = SbMatrix::identity(); + movedFrameView.setTranslate(SbVec3f(0.0f, 0.0f, -4.0f)); + transparentNear.geometry.boundsCenter.setValue(0.0f, 0.0f, -3.0f); + transparentNear.geometry.hasBounds = TRUE; + transparentFar.geometry.boundsCenter.setValue(0.0f, 0.0f, 1.0f); + transparentFar.geometry.hasBounds = TRUE; + transparencyDrawList.clear(); + transparencyDrawList.addCommand(opaque); + transparencyDrawList.addCommand(transparentNear); + transparencyDrawList.addCommand(transparentFar); + planner.build(transparencyDrawList, movedFrameView, plan); + result = check(plan.getDraw(1).commandIndex == 1 && + plan.getDraw(2).commandIndex == 2, + "planner ignored frame camera and geometry bounds") && result; + + SoRenderCommand overrideNear = transparentNear; + overrideNear.state.useCommandMatrices = TRUE; + overrideNear.viewMatrix.makeIdentity(); + overrideNear.geometry.boundsCenter.setValue(0.0f, 0.0f, 2.0f); + transparencyDrawList.clear(); + transparencyDrawList.addCommand(opaque); + transparencyDrawList.addCommand(overrideNear); + transparencyDrawList.addCommand(transparentFar); + planner.build(transparencyDrawList, movedFrameView, plan); + result = check(plan.getDraw(1).commandIndex == 2 && + plan.getDraw(2).commandIndex == 1, + "planner ignored an explicit command-matrix override") && result; + return result ? 0 : 1; } diff --git a/testsuite/RetainedMaterialLightingGLTest.cpp b/testsuite/RetainedMaterialLightingGLTest.cpp new file mode 100644 index 0000000000..9cbe2bd057 --- /dev/null +++ b/testsuite/RetainedMaterialLightingGLTest.cpp @@ -0,0 +1,601 @@ +#include "rendering/SoGLRenderBackend.h" +#include "rendering/SoRenderPlan.h" +#include "support/GLTestContext.h" + +#include + +#include +#include +#include +#include + +namespace { + +int skip(const char * reason) +{ + std::cout << "SKIP: " << reason << std::endl; + return 77; +} + +struct RenderFixture { + 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(SoDrawList & drawlist, + const SbVec4f & clearColor) + { + SoRenderParams params = {}; + params.viewport = SbViewportRegion(64, 64); + params.viewport.setViewportPixels(SbVec2s(0, 0), SbVec2s(64, 64)); + params.viewMatrix.makeIdentity(); + params.projMatrix.makeIdentity(); + params.clearColor = clearColor; + params.clearDepth = 1.0f; + params.flags = SO_PARAM_CLEAR_WINDOW | SO_PARAM_CLEAR_DEPTH; + SoRenderPlanner planner; + SoRenderPlan plan; + planner.build(drawlist, params.viewMatrix, plan); + backend.render(drawlist, plan, params); + glFinish(); + return context.readPixels(); + } + + void shutdown() + { + backend.shutdown(); + context.shutdown(); + } +}; + +const uint32_t quadIndices[] = { 0, 1, 2, 0, 2, 3 }; +const float quadPositions[] = { + -0.8f, -0.8f, 0.0f, 0.8f, -0.8f, 0.0f, + 0.8f, 0.8f, 0.0f, -0.8f, 0.8f, 0.0f +}; +const float halfLeftPositions[] = { + -1.0f, -0.8f, 0.0f, 0.0f, -0.8f, 0.0f, + 0.0f, 0.8f, 0.0f, -1.0f, 0.8f, 0.0f +}; +const float halfRightPositions[] = { + 0.0f, -0.8f, 0.0f, 1.0f, -0.8f, 0.0f, + 1.0f, 0.8f, 0.0f, 0.0f, 0.8f, 0.0f +}; +const float sideFacingPositions[] = { + -0.25f, -0.25f, 0.0f, 0.25f, -0.25f, 0.0f, + 0.25f, 0.25f, 0.0f, -0.25f, 0.25f, 0.0f +}; + +SoRenderCommand baseCommand(const float * positions) +{ + 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 = quadIndices; + command.geometry.vertexStride = sizeof(float) * 3; + command.material.shadingModel = SO_SHADING_UNLIT; + command.state.depth.enabled = TRUE; + command.state.depth.writeEnabled = TRUE; + command.state.depth.func = SO_DEPTH_LEQUAL; + return command; +} + +void enableAlphaBlend(SoRenderCommand & command) +{ + 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_ONE; + command.state.blend.dstAlphaFactor = SO_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; +} + +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; +} + +bool testTextureWrap(RenderFixture & fixture) +{ + const unsigned char pixels[] = { + 255, 0, 0, 255, + 0, 255, 0, 255 + }; + const float texcoords[] = { + 1.25f, 0.5f, 0.0f, 0.0f, 1.25f, 0.5f, 0.0f, 0.0f, + 1.25f, 0.5f, 0.0f, 0.0f, 1.25f, 0.5f, 0.0f, 0.0f + }; + SoDrawList drawlist; + SoRenderCommand command = baseCommand(quadPositions); + command.geometry.texcoords = texcoords; + command.geometry.texcoordStride = sizeof(float) * 4; + command.material.texture.pixels = pixels; + command.material.texture.width = 2; + 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.wrapT = SO_TEXTURE_WRAP_CLAMP_TO_EDGE; + + command.material.texture.wrapS = SO_TEXTURE_WRAP_REPEAT; + drawlist.addCommand(command); + std::vector repeated = fixture.render(drawlist, SbVec4f(0, 0, 0, 1)); + drawlist.clear(); + command.material.texture.wrapS = SO_TEXTURE_WRAP_CLAMP_TO_EDGE; + drawlist.addCommand(command); + std::vector clamped = fixture.render(drawlist, SbVec4f(0, 0, 0, 1)); + const uint8_t * repeatPixel = pixelAt(repeated, 32, 32); + const uint8_t * clampPixel = pixelAt(clamped, 32, 32); + return check(repeatPixel[0] > 200 && repeatPixel[1] < 50 && + clampPixel[1] > 200 && clampPixel[0] < 50, + "retained texture wrap state did not affect sampling"); +} + +bool testTextureAlphaOnce(RenderFixture & fixture) +{ + const unsigned char pixels[] = { 255, 0, 0, 128 }; + const float texcoords[] = { + 0.5f, 0.5f, 0.0f, 0.0f, 0.5f, 0.5f, 0.0f, 0.0f, + 0.5f, 0.5f, 0.0f, 0.0f, 0.5f, 0.5f, 0.0f, 0.0f + }; + SoRenderCommand command = baseCommand(quadPositions); + command.material.diffuse = SbVec4f(1, 1, 1, 0.5f); + command.geometry.texcoords = texcoords; + command.geometry.texcoordStride = sizeof(float) * 4; + command.material.texture.pixels = pixels; + command.material.texture.width = 1; + command.material.texture.height = 1; + command.material.texture.numComponents = 4; + command.material.textureAlphaIncludesOpacity = false; + enableAlphaBlend(command); + SoDrawList drawlist; + drawlist.addCommand(command); + const std::vector rendered = fixture.render( + drawlist, SbVec4f(0, 0, 1, 1)); + const uint8_t * pixel = pixelAt(rendered, 32, 32); + return check(pixel[0] >= 50 && pixel[0] <= 80 && + pixel[2] >= 175 && pixel[2] <= 205, + "texture and material opacity were not composed exactly once"); +} + +bool testTextureModels(RenderFixture & fixture) +{ + const unsigned char pixels[] = { 128, 128, 128, 128 }; + const float texcoords[] = { + 0.5f, 0.5f, 0.0f, 0.0f, 0.5f, 0.5f, 0.0f, 0.0f, + 0.5f, 0.5f, 0.0f, 0.0f, 0.5f, 0.5f, 0.0f, 0.0f + }; + const SoTextureModel models[] = { + SO_TEXTURE_MODEL_MODULATE, SO_TEXTURE_MODEL_DECAL, + SO_TEXTURE_MODEL_BLEND, SO_TEXTURE_MODEL_REPLACE + }; + const uint8_t expected[][3] = { + { 100, 25, 12 }, { 164, 89, 76 }, { 100, 152, 12 }, { 128, 128, 128 } + }; + + for (int i = 0; i < 4; ++i) { + SoRenderCommand command = baseCommand(quadPositions); + command.material.diffuse = SbVec4f(200.0f / 255.0f, + 50.0f / 255.0f, + 25.0f / 255.0f, 1.0f); + command.geometry.texcoords = texcoords; + command.geometry.texcoordStride = sizeof(float) * 4; + command.material.texture.pixels = pixels; + command.material.texture.width = 1; + command.material.texture.height = 1; + command.material.texture.numComponents = 4; + command.material.texture.model = models[i]; + command.material.texture.blendColor = SbVec4f(0, 1, 0, 1); + + SoDrawList drawlist; + drawlist.addCommand(command); + const std::vector rendered = fixture.render( + drawlist, SbVec4f(0, 0, 0, 1)); + const uint8_t * pixel = pixelAt(rendered, 32, 32); + if (!check(std::abs(static_cast(pixel[0]) - expected[i][0]) < 25 && + std::abs(static_cast(pixel[1]) - expected[i][1]) < 25 && + std::abs(static_cast(pixel[2]) - expected[i][2]) < 25, + "retained texture model semantics were not executed")) { + return false; + } + } + + const float alphaTexcoords[] = { + 0.5f, 0.5f, 0.0f, 0.0f, 0.5f, 0.5f, 0.0f, 0.0f, + 0.5f, 0.5f, 0.0f, 0.0f, 0.5f, 0.5f, 0.0f, 0.0f + }; + auto renderAlphaCase = [&](const unsigned char * data, int components, + SoTextureModel model, float materialAlpha) { + SoRenderCommand command = baseCommand(quadPositions); + command.material.diffuse = SbVec4f(1, 1, 1, materialAlpha); + command.geometry.texcoords = alphaTexcoords; + command.geometry.texcoordStride = sizeof(float) * 4; + command.material.texture.pixels = data; + command.material.texture.width = 1; + command.material.texture.height = 1; + command.material.texture.numComponents = components; + command.material.texture.model = model; + command.material.texture.blendColor = SbVec4f(1, 0, 0, 1); + command.material.texture.minFilter = SO_TEXTURE_FILTER_NEAREST; + command.material.texture.magFilter = SO_TEXTURE_FILTER_NEAREST; + enableAlphaBlend(command); + SoDrawList drawlist; + drawlist.addCommand(command); + const std::vector rendered = fixture.render( + drawlist, SbVec4f(0, 0, 1, 1)); + const uint8_t * pixel = pixelAt(rendered, 32, 32); + return std::array{ pixel[0], pixel[1], pixel[2], pixel[3] }; + }; + auto closeTo = [](uint8_t value, int expected) { + return std::abs(static_cast(value) - expected) < 30; + }; + + const unsigned char rgbRed[] = { 255, 0, 0 }; + const unsigned char rgbaRed[] = { 255, 0, 0, 64 }; + const unsigned char lWhite[] = { 255 }; + const unsigned char laWhite[] = { 255, 64 }; + const unsigned char rgbWhite[] = { 255, 255, 255 }; + const unsigned char rgbaWhite[] = { 255, 255, 255, 64 }; + const auto replaceRGB = renderAlphaCase( + rgbRed, 3, SO_TEXTURE_MODEL_REPLACE, 0.5f); + const auto replaceRGBA = renderAlphaCase( + rgbaRed, 4, SO_TEXTURE_MODEL_REPLACE, 0.5f); + const auto decalRGB = renderAlphaCase( + rgbWhite, 3, SO_TEXTURE_MODEL_DECAL, 0.5f); + const auto decalRGBA = renderAlphaCase( + rgbaWhite, 4, SO_TEXTURE_MODEL_DECAL, 0.5f); + const auto modulateL = renderAlphaCase( + lWhite, 1, SO_TEXTURE_MODEL_MODULATE, 0.5f); + const auto modulateLA = renderAlphaCase( + laWhite, 2, SO_TEXTURE_MODEL_MODULATE, 0.5f); + const auto blendRGB = renderAlphaCase( + rgbWhite, 3, SO_TEXTURE_MODEL_BLEND, 0.5f); + const auto blendRGBA = renderAlphaCase( + rgbaWhite, 4, SO_TEXTURE_MODEL_BLEND, 0.5f); + + if (!check(closeTo(replaceRGB[0], 128) && closeTo(replaceRGB[2], 128) && + closeTo(replaceRGBA[0], 64) && closeTo(replaceRGBA[2], 191), + "REPLACE did not distinguish RGB and RGBA texture alpha")) { + return false; + } + if (!check(closeTo(decalRGB[0], 128) && closeTo(decalRGB[2], 255) && + closeTo(decalRGBA[0], 128) && closeTo(decalRGBA[2], 255), + "DECAL incorrectly used texture alpha for final alpha")) { + return false; + } + if (!check(closeTo(modulateL[0], 128) && + closeTo(modulateLA[0], 32), + "MODULATE did not preserve L versus LA alpha semantics")) { + return false; + } + return check(closeTo(blendRGB[0], 128) && closeTo(blendRGB[2], 128) && + closeTo(blendRGBA[0], 32) && closeTo(blendRGBA[2], 223), + "BLEND did not preserve RGB versus RGBA alpha semantics"); +} + +bool testTexturedLightingComposition(RenderFixture & fixture) +{ + const unsigned char pixels[] = { 255, 255, 255, 255 }; + const float texcoords[] = { + 0.5f, 0.5f, 0.0f, 0.0f, 0.5f, 0.5f, 0.0f, 0.0f, + 0.5f, 0.5f, 0.0f, 0.0f, 0.5f, 0.5f, 0.0f, 0.0f + }; + SoRenderCommand command = baseCommand(quadPositions); + command.material.shadingModel = SO_SHADING_LEGACY_GOURAUD; + command.material.diffuse = SbVec4f(0.8f, 0.8f, 0.8f, 1.0f); + command.material.ambient = SbVec4f(0, 0, 0, 1); + command.material.specular = SbVec4f(0, 0, 0, 1); + command.material.emissive = SbVec4f(0, 0, 0, 1); + command.material.texture.pixels = pixels; + command.material.texture.width = 1; + command.material.texture.height = 1; + command.material.texture.numComponents = 4; + command.geometry.texcoords = texcoords; + command.geometry.texcoordStride = sizeof(float) * 4; + + SoLightingData lighting; + lighting.ambient.setValue(0, 0, 0); + SoLightData directional; + directional.color.setValue(0.25f, 0.25f, 0.25f); + directional.direction.setValue(0, 0, 1); + lighting.lights.push_back(directional); + SoDrawList drawlist; + command.lightingHandle = drawlist.addLightingSetup(lighting); + drawlist.addCommand(command); + const std::vector rendered = fixture.render( + drawlist, SbVec4f(0, 0, 0, 1)); + const uint8_t * pixel = pixelAt(rendered, 32, 32); + return check(pixel[0] >= 40 && pixel[0] <= 65 && + pixel[1] >= 40 && pixel[1] <= 65 && + pixel[2] >= 40 && pixel[2] <= 65, + "textured geometry bypassed retained lighting"); +} + +bool testShadingModelSelection(RenderFixture & fixture) +{ + SoRenderCommand command = baseCommand(quadPositions); + command.material.diffuse = SbVec4f(1, 1, 1, 1); + command.material.ambient = SbVec4f(0, 0, 0, 1); + command.material.specular = SbVec4f(0, 0, 0, 1); + command.material.emissive = SbVec4f(0, 0, 0, 1); + + SoLightingData lighting; + lighting.ambient.setValue(0, 0, 0); + SoDrawList drawlist; + command.lightingHandle = drawlist.addLightingSetup(lighting); + command.material.shadingModel = SO_SHADING_UNLIT; + drawlist.addCommand(command); + const std::vector unlit = fixture.render( + drawlist, SbVec4f(0, 0, 0, 1)); + const uint8_t * unlitPixel = pixelAt(unlit, 32, 32); + if (!check(unlitPixel[0] > 220 && unlitPixel[1] > 220 && + unlitPixel[2] > 220, + "UNLIT did not select unlit surface evaluation")) { + return false; + } + + drawlist.clear(); + command.lightingHandle = drawlist.addLightingSetup(lighting); + command.material.shadingModel = SO_SHADING_LEGACY_GOURAUD; + drawlist.addCommand(command); + const std::vector lit = fixture.render( + drawlist, SbVec4f(0, 0, 0, 1)); + const uint8_t * litPixel = pixelAt(lit, 32, 32); + return check(litPixel[0] < 10 && litPixel[1] < 10 && litPixel[2] < 10, + "LEGACY_GOURAUD did not select Inventor lighting evaluation"); +} + +bool testEmissiveIsIndependent(RenderFixture & fixture) +{ + SoRenderCommand command = baseCommand(quadPositions); + command.material.shadingModel = SO_SHADING_LEGACY_GOURAUD; + command.material.diffuse = SbVec4f(0.8f, 0.8f, 0.8f, 1.0f); + command.material.ambient = SbVec4f(0, 0, 0, 1); + command.material.specular = SbVec4f(0, 0, 0, 1); + command.material.emissive = SbVec4f(0.2f, 0.2f, 0.2f, 1.0f); + SoLightingData lighting; + lighting.ambient.setValue(0, 0, 0); + SoDrawList drawlist; + command.lightingHandle = drawlist.addLightingSetup(lighting); + drawlist.addCommand(command); + const std::vector rendered = fixture.render( + drawlist, SbVec4f(0, 0, 0, 1)); + const uint8_t * pixel = pixelAt(rendered, 32, 32); + return check(pixel[0] >= 45 && pixel[0] <= 60 && + pixel[1] >= 45 && pixel[1] <= 60 && + pixel[2] >= 45 && pixel[2] <= 60, + "emissive material was folded into diffuse twice"); +} + +bool testTwoSidedLightingUsesFacing(RenderFixture & fixture) +{ + const float normals[] = { + 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0 + }; + SoRenderCommand command = baseCommand(sideFacingPositions); + command.material.shadingModel = SO_SHADING_LEGACY_GOURAUD; + command.material.diffuse = SbVec4f(1, 1, 1, 1); + command.material.ambient = SbVec4f(0, 0, 0, 1); + command.material.specular = SbVec4f(0, 0, 0, 1); + command.material.emissive = SbVec4f(0, 0, 0, 1); + command.material.twoSidedLighting = true; + command.geometry.normals = normals; + command.geometry.normalCount = 4; + command.modelMatrix.setTranslate(SbVec3f(0.5f, 0, 0)); + SoLightingData lighting; + lighting.ambient.setValue(0, 0, 0); + SoLightData directional; + directional.direction.setValue(-1, 0, 0); + lighting.lights.push_back(directional); + SoDrawList drawlist; + command.lightingHandle = drawlist.addLightingSetup(lighting); + drawlist.addCommand(command); + const std::vector rendered = fixture.render( + drawlist, SbVec4f(0, 0, 0, 1)); + const uint8_t * pixel = pixelAt(rendered, 48, 32); + return check(pixel[0] > 220 && pixel[1] > 220 && pixel[2] > 220, + "two-sided lighting did not use the actual viewer-facing side"); +} + +bool testExecutorLightLimit(RenderFixture & fixture) +{ + SoRenderCommand command = baseCommand(quadPositions); + command.material.shadingModel = SO_SHADING_LEGACY_GOURAUD; + command.material.diffuse = SbVec4f(1, 1, 1, 1); + command.material.ambient = SbVec4f(0, 0, 0, 1); + command.material.specular = SbVec4f(0, 0, 0, 1); + command.material.emissive = SbVec4f(0, 0, 0, 1); + SoLightingData lighting; + lighting.ambient.setValue(0, 0, 0); + lighting.lights.resize(9); + for (int i = 0; i < 8; ++i) { + lighting.lights[static_cast(i)].color.setValue(0, 0, 0); + } + lighting.lights[8].direction.setValue(0, 0, 1); + SoDrawList drawlist; + command.lightingHandle = drawlist.addLightingSetup(lighting); + drawlist.addCommand(command); + const std::vector rendered = fixture.render( + drawlist, SbVec4f(0, 0, 0, 1)); + const uint8_t * pixel = pixelAt(rendered, 32, 32); + return check(pixel[0] < 10 && pixel[1] < 10 && pixel[2] < 10, + "GL executor uploaded more lights than its declared shader cap"); +} + +bool testTransparentDepthWriteFaithful(RenderFixture & fixture) +{ + SoRenderCommand command = baseCommand(quadPositions); + command.material.shadingModel = SO_SHADING_UNLIT; + command.material.diffuse = SbVec4f(1, 0, 0, 0.5f); + command.state.depth.writeEnabled = TRUE; + enableAlphaBlend(command); + SoDrawList drawlist; + drawlist.addCommand(command); + fixture.render(drawlist, SbVec4f(0, 0, 0, 1)); + GLboolean depthWrite = GL_FALSE; + glGetBooleanv(GL_DEPTH_WRITEMASK, &depthWrite); + return check(depthWrite == GL_TRUE, + "transparent retained command changed the requested depth-write state"); +} + +bool testMaterialTransparency(RenderFixture & fixture) +{ + SoRenderCommand left = baseCommand(halfLeftPositions); + left.material.diffuse = SbVec4f(1, 0, 0, 0.25f); + enableAlphaBlend(left); + SoRenderCommand right = baseCommand(halfRightPositions); + right.material.diffuse = SbVec4f(0, 1, 0, 0.75f); + enableAlphaBlend(right); + SoDrawList drawlist; + drawlist.addCommand(left); + drawlist.addCommand(right); + const std::vector rendered = fixture.render( + drawlist, SbVec4f(0, 0, 0, 1)); + const uint8_t * leftPixel = pixelAt(rendered, 16, 32); + const uint8_t * rightPixel = pixelAt(rendered, 48, 32); + return check(leftPixel[0] >= 50 && leftPixel[0] <= 80 && + rightPixel[1] >= 175 && rightPixel[1] <= 205, + "per-material transparency was not preserved per command"); +} + +bool testNonUniformScaleLighting(RenderFixture & fixture) +{ + const float normals[] = { + 0.7071067f, 0.0f, 0.7071067f, + 0.7071067f, 0.0f, 0.7071067f, + 0.7071067f, 0.0f, 0.7071067f, + 0.7071067f, 0.0f, 0.7071067f + }; + SoRenderCommand command = baseCommand(quadPositions); + command.material.shadingModel = SO_SHADING_LEGACY_GOURAUD; + command.material.diffuse = SbVec4f(1, 1, 1, 1); + command.material.ambient = SbVec4f(0, 0, 0, 1); + command.material.specular = SbVec4f(0, 0, 0, 1); + command.material.emissive = SbVec4f(0, 0, 0, 1); + command.material.shininess = 0.0f; + command.geometry.normals = normals; + command.geometry.normalCount = 4; + SoLightingData lighting; + lighting.ambient.setValue(0, 0, 0); + SoLightData directional; + directional.type = SO_LIGHT_DIRECTIONAL; + directional.direction.setValue(0, 0, 1); + lighting.lights.push_back(directional); + SoDrawList drawlist; + command.lightingHandle = drawlist.addLightingSetup(lighting); + command.modelMatrix.setScale(SbVec3f(4, 1, 1)); + drawlist.addCommand(command); + const std::vector rendered = fixture.render( + drawlist, SbVec4f(0, 0, 0, 1)); + const uint8_t * pixel = pixelAt(rendered, 32, 32); + return check(pixel[0] > 220 && pixel[1] > 220 && pixel[2] > 220, + "non-uniform model scale corrupted retained normal lighting"); +} + +bool testDepth(RenderFixture & fixture) +{ + SoRenderCommand back = baseCommand(quadPositions); + back.material.diffuse = SbVec4f(0, 0, 1, 1); + back.modelMatrix.setTranslate(SbVec3f(0, 0, 0.5f)); + SoRenderCommand front = baseCommand(quadPositions); + front.material.diffuse = SbVec4f(1, 0, 0, 1); + front.modelMatrix.setTranslate(SbVec3f(0, 0, 0.0f)); + SoDrawList drawlist; + drawlist.addCommand(back); + drawlist.addCommand(front); + const std::vector rendered = fixture.render( + drawlist, SbVec4f(0, 0, 0, 1)); + const uint8_t * pixel = pixelAt(rendered, 32, 32); + return check(pixel[0] > 200 && pixel[1] < 50 && pixel[2] < 50, + "retained depth testing did not select the nearer command"); +} + +bool testAlphaTest(RenderFixture & fixture) +{ + SoRenderCommand command = baseCommand(quadPositions); + command.material.diffuse = SbVec4f(1, 0, 0, 0.5f); + command.state.alphaTest.policy = SO_ALPHA_TEST_POLICY_EXPLICIT; + command.state.alphaTest.function = SO_ALPHA_TEST_GREATER; + command.state.alphaTest.reference = 0.75f; + SoDrawList drawlist; + drawlist.addCommand(command); + const std::vector rejected = fixture.render( + drawlist, SbVec4f(0, 0, 1, 1)); + const uint8_t * rejectedPixel = pixelAt(rejected, 32, 32); + if (!check(rejectedPixel[2] > 200 && rejectedPixel[0] < 50, + "alpha-test rejection did not discard the fragment")) { + return false; + } + + drawlist.clear(); + command.state.alphaTest.function = SO_ALPHA_TEST_LESS; + drawlist.addCommand(command); + const std::vector accepted = fixture.render( + drawlist, SbVec4f(0, 0, 1, 1)); + const uint8_t * acceptedPixel = pixelAt(accepted, 32, 32); + return check(acceptedPixel[0] > 200 && acceptedPixel[2] < 50, + "alpha-test acceptance did not preserve the fragment"); +} + +} // namespace + +static int runTest() +{ + SoDB::init(); + RenderFixture fixture; + const int initializationResult = fixture.initialize(); + if (initializationResult != 0) { + if (initializationResult == 77) { + return skip("core GLFW retained-material context is unavailable"); + } + std::cerr << "FAIL: retained material backend did not initialize on the " + << "verified OpenGL 3.3/GLSL 330 context" << std::endl; + return 1; + } + + int result = 0; + if (!testTextureWrap(fixture)) result = 1; + if (!testTextureAlphaOnce(fixture)) result = 1; + if (!testTextureModels(fixture)) result = 1; + if (!testShadingModelSelection(fixture)) result = 1; + if (!testTexturedLightingComposition(fixture)) result = 1; + if (!testEmissiveIsIndependent(fixture)) result = 1; + if (!testTwoSidedLightingUsesFacing(fixture)) result = 1; + if (!testExecutorLightLimit(fixture)) result = 1; + if (!testTransparentDepthWriteFaithful(fixture)) result = 1; + if (!testMaterialTransparency(fixture)) result = 1; + if (!testNonUniformScaleLighting(fixture)) result = 1; + if (!testDepth(fixture)) result = 1; + if (!testAlphaTest(fixture)) result = 1; + fixture.shutdown(); + return result; +} + +int main() +{ + const int result = runTest(); + SoDB::finish(); + return result; +} diff --git a/testsuite/RetainedMaterialLightingTest.cpp b/testsuite/RetainedMaterialLightingTest.cpp new file mode 100644 index 0000000000..fe7bf058c0 --- /dev/null +++ b/testsuite/RetainedMaterialLightingTest.cpp @@ -0,0 +1,497 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace { + +bool nearlyEqual(float lhs, float rhs) +{ + return std::fabs(lhs - rhs) < 0.0001f; +} + +bool checkTextureClassification(int components, unsigned char alpha, + SoTexture2::Model model, + bool expectedTransparent) +{ + const unsigned char pixels[] = { 255, 128, 64, alpha }; + SoSeparator * root = new SoSeparator; + root->ref(); + SoTexture2 * texture = new SoTexture2; + texture->model = model; + texture->image.setValue(SbVec2s(1, 1), components, pixels); + root->addChild(texture); + root->addChild(new SoCube); + + SoIRRenderAction action(SbViewportRegion(32, 32)); + action.apply(root); + bool result = action.getDrawList().getNumCommands() == 1; + if (result) { + const SoRenderCommand & command = action.getDrawList().getCommand(0); + const bool payloadHasTransparency = (components == 2 || components == 4) && + alpha != 255; + result = (command.opacityClass == (expectedTransparent + ? SO_OPACITY_TRANSPARENT + : SO_OPACITY_OPAQUE)) && + (command.state.blend.enabled == (expectedTransparent ? TRUE : FALSE)) && + (command.material.texture.hasTransparency == payloadHasTransparency); + } + root->unref(); + return result; +} + +} + +int +runTest() +{ + SoDB::init(); + + SoSeparator * root = new SoSeparator; + root->ref(); + + SoDepthBuffer * depth = new SoDepthBuffer; + depth->test = TRUE; + depth->write = FALSE; + depth->function = SoDepthBuffer::GREATER; + depth->range = SbVec2f(0.2f, 0.8f); + root->addChild(depth); + + SoAlphaTest * alphaTest = new SoAlphaTest; + alphaTest->function = SoAlphaTest::GREATER; + alphaTest->value = 0.25f; + root->addChild(alphaTest); + + const unsigned char texturePixel[] = { 255, 128, 64, 255 }; + SoTexture2 * texture = new SoTexture2; + texture->image.setValue(SbVec2s(1, 1), 4, texturePixel); + texture->wrapS = SoTexture2::REPEAT; + texture->wrapT = SoTexture2::CLAMP; + texture->model = SoTexture2::BLEND; + texture->blendColor.setValue(0.1f, 0.2f, 0.3f); + root->addChild(texture); + + SoDirectionalLight * light = new SoDirectionalLight; + light->color.setValue(0.25f, 0.5f, 0.75f); + root->addChild(light); + + SoPointLight * pointLight = new SoPointLight; + pointLight->location.setValue(1.0f, -2.0f, 3.0f); + pointLight->color.setValue(0.8f, 0.7f, 0.6f); + root->addChild(pointLight); + + SoSpotLight * spotLight = new SoSpotLight; + spotLight->location.setValue(-1.0f, -2.0f, 3.0f); + spotLight->direction.setValue(0.0f, 0.0f, -1.0f); + spotLight->cutOffAngle = 0.5f; + spotLight->dropOffRate = 0.25f; + root->addChild(spotLight); + + for (int i = 0; i < 8; ++i) { + root->addChild(new SoDirectionalLight); + } + + SoMaterial * material = new SoMaterial; + material->ambientColor.setValue(0.1f, 0.2f, 0.3f); + material->diffuseColor.setValue(0.4f, 0.5f, 0.6f); + material->specularColor.setValue(0.7f, 0.8f, 0.9f); + material->emissiveColor.setValue(0.15f, 0.1f, 0.05f); + material->shininess = 0.5f; + material->transparency = 0.25f; + root->addChild(material); + root->addChild(new SoCube); + + SoIRRenderAction action(SbViewportRegion(64, 64)); + action.apply(root); + + int result = 0; + if (action.getDrawList().getNumCommands() != 1) { + std::cerr << "FAIL: material scene did not emit one retained command" << std::endl; + result = 1; + } + else { + const SoRenderCommand & command = action.getDrawList().getCommand(0); + if (!command.state.depth.enabled || command.state.depth.writeEnabled || + command.state.depth.func != SO_DEPTH_GREATER || + !nearlyEqual(command.state.depth.range[0], 0.2f) || + !nearlyEqual(command.state.depth.range[1], 0.8f)) { + std::cerr << "FAIL: SoDepthBuffer state was not retained" << std::endl; + result = 1; + } + if (command.state.alphaTest.function != SO_ALPHA_TEST_GREATER || + !nearlyEqual(command.state.alphaTest.reference, 0.25f)) { + std::cerr << "FAIL: SoAlphaTest state was not retained" << std::endl; + result = 1; + } + if (command.material.texture.wrapS != SO_TEXTURE_WRAP_REPEAT || + command.material.texture.wrapT != SO_TEXTURE_WRAP_CLAMP_TO_EDGE || + command.material.texture.model != SO_TEXTURE_MODEL_BLEND || + !nearlyEqual(command.material.texture.blendColor[1], 0.2f) || + command.material.texture.minFilter != SO_TEXTURE_FILTER_NEAREST_MIPMAP_LINEAR || + command.material.texture.magFilter != SO_TEXTURE_FILTER_LINEAR || + command.material.texture.pixels == nullptr) { + std::cerr << "FAIL: texture model and sampler state were not retained" << std::endl; + result = 1; + } + if (!nearlyEqual(command.material.ambient[0], 0.1f) || + !nearlyEqual(command.material.diffuse[1], 0.5f) || + !nearlyEqual(command.material.specular[2], 0.9f) || + !nearlyEqual(command.material.diffuse[3], 0.75f) || + !nearlyEqual(command.material.emissive[0], 0.15f)) { + std::cerr << "FAIL: complete material state was not retained" << std::endl; + result = 1; + } + const SoLightingData * lighting = action.getDrawList().getLighting(command.lightingHandle); + if (!lighting || lighting->lights.size() != 11 || + !nearlyEqual(lighting->lights[0].color[1], 0.5f)) { + std::cerr << "FAIL: SoLight state was not retained" << std::endl; + result = 1; + } + else { + const SoLightData & point = lighting->lights[1]; + const SoLightData & spot = lighting->lights[2]; + if (point.type != SO_LIGHT_POINT || + !nearlyEqual(point.position[0], 1.0f) || + !nearlyEqual(point.position[1], -2.0f) || + !nearlyEqual(point.position[2], 3.0f) || + spot.type != SO_LIGHT_SPOT || + !nearlyEqual(spot.position[0], -1.0f) || + !nearlyEqual(spot.position[1], -2.0f) || + !nearlyEqual(spot.position[2], 3.0f) || + !nearlyEqual(spot.direction[2], -1.0f) || + !nearlyEqual(spot.spotCutoffCos, std::cos(0.5f)) || + !nearlyEqual(spot.spotExponent, 32.0f)) { + std::cerr << "FAIL: point and spot light state was not retained" + << std::endl; + result = 1; + } + } + } + + root->unref(); + + // SoVertexProperty packed alpha is an independent vertex contribution in + // retained rendering. Verify both ways that Coin can introduce the + // property: through the SoVertexShape field and as a traversal node. + const SbVec3f alphaPoints[] = { + SbVec3f(-1.0f, -1.0f, 0.0f), SbVec3f(1.0f, -1.0f, 0.0f), + SbVec3f(1.0f, 1.0f, 0.0f), SbVec3f(-1.0f, 1.0f, 0.0f) + }; + const uint32_t alphaColors[] = { + 0xFF000080, 0xFF000080, 0xFF000080, 0xFF000080 + }; + + for (int propertyMode = 0; propertyMode < 2; ++propertyMode) { + SoSeparator * alphaRoot = new SoSeparator; + alphaRoot->ref(); + + SoMaterial * alphaMaterial = new SoMaterial; + alphaMaterial->transparency = 0.25f; + alphaRoot->addChild(alphaMaterial); + + SoVertexProperty * alphaVertexProperty = new SoVertexProperty; + alphaVertexProperty->vertex.setValues(0, 4, alphaPoints); + alphaVertexProperty->orderedRGBA.setValues(0, 4, alphaColors); + alphaVertexProperty->materialBinding = SoVertexProperty::PER_VERTEX; + + SoFaceSet * alphaFaces = new SoFaceSet; + if (propertyMode == 0) { + alphaFaces->vertexProperty = alphaVertexProperty; + } + else { + alphaRoot->addChild(alphaVertexProperty); + } + alphaFaces->numVertices = 4; + alphaRoot->addChild(alphaFaces); + + SoIRRenderAction alphaAction(SbViewportRegion(64, 64)); + alphaAction.apply(alphaRoot); + if (alphaAction.getDrawList().getNumCommands() != 1) { + std::cerr << "FAIL: packed vertex alpha did not emit one retained command" + << std::endl; + result = 1; + } + else { + const SoRenderCommand & command = alphaAction.getDrawList().getCommand(0); + if (!command.geometry.colors || + command.material.vertexColorAlphaIncludesOpacity || + !nearlyEqual(command.material.opacity, 0.75f) || + !nearlyEqual(command.material.diffuse[3], 0.75f) || + !nearlyEqual(command.geometry.colors[3], 128.0f / 255.0f)) { + std::cerr << "FAIL: packed vertex alpha did not compose with material opacity" + << std::endl; + result = 1; + } + } + + alphaRoot->unref(); + } + + // The provenance must retain the complete inherited opacity array, not + // only material zero. This also verifies that packed colors do not make a + // later per-face material use the vertex alpha as material opacity. + SoSeparator * indexedAlphaRoot = new SoSeparator; + indexedAlphaRoot->ref(); + SoMaterial * indexedAlphaMaterial = new SoMaterial; + const float indexedTransparency[] = { 0.25f, 0.5f }; + indexedAlphaMaterial->transparency.setValues(0, 2, indexedTransparency); + indexedAlphaRoot->addChild(indexedAlphaMaterial); + + SoVertexProperty * indexedAlphaVertexProperty = new SoVertexProperty; + const SbVec3f indexedAlphaPoints[] = { + SbVec3f(-1.0f, -1.0f, 0.0f), SbVec3f(0.0f, -1.0f, 0.0f), + SbVec3f(-0.5f, 0.0f, 0.0f), SbVec3f(0.0f, 0.0f, 0.0f), + SbVec3f(1.0f, 0.0f, 0.0f), SbVec3f(0.5f, 1.0f, 0.0f) + }; + const uint32_t indexedAlphaColors[] = { + 0xFF000080, 0xFF000080, 0xFF000080, + 0xFF000080, 0xFF000080, 0xFF000080 + }; + indexedAlphaVertexProperty->vertex.setValues(0, 6, indexedAlphaPoints); + indexedAlphaVertexProperty->orderedRGBA.setValues( + 0, 6, indexedAlphaColors); + indexedAlphaVertexProperty->materialBinding = SoVertexProperty::PER_FACE; + indexedAlphaRoot->addChild(indexedAlphaVertexProperty); + + SoFaceSet * indexedAlphaFaces = new SoFaceSet; + const int32_t indexedAlphaCounts[] = { 3, 3 }; + indexedAlphaFaces->numVertices.setValues(0, 2, indexedAlphaCounts); + indexedAlphaRoot->addChild(indexedAlphaFaces); + + SoIRRenderAction indexedAlphaAction(SbViewportRegion(64, 64)); + indexedAlphaAction.apply(indexedAlphaRoot); + if (indexedAlphaAction.getDrawList().getNumCommands() != 2) { + std::cerr << "FAIL: packed vertex alpha did not preserve material batches" + << std::endl; + result = 1; + } + else { + const SoRenderCommand & first = + indexedAlphaAction.getDrawList().getCommand(0); + const SoRenderCommand & second = + indexedAlphaAction.getDrawList().getCommand(1); + if (!nearlyEqual(first.material.opacity, 0.75f) || + !nearlyEqual(second.material.opacity, 0.5f) || + first.material.vertexColorAlphaIncludesOpacity || + second.material.vertexColorAlphaIncludesOpacity) { + std::cerr << "FAIL: packed vertex alpha lost per-material opacity" + << std::endl; + result = 1; + } + } + indexedAlphaRoot->unref(); + + if (!checkTextureClassification(3, 255, SoTexture2::MODULATE, false) || + !checkTextureClassification(4, 255, SoTexture2::MODULATE, false) || + !checkTextureClassification(4, 127, SoTexture2::MODULATE, true) || + !checkTextureClassification(4, 127, SoTexture2::DECAL, false)) { + std::cerr << "FAIL: texture alpha was not classified consistently with retained blend state" + << std::endl; + result = 1; + } + + // Two commands using one scene texture must borrow one frame-local payload + // rather than copying the image once per command. + { + const unsigned char pixels[] = { 255, 128, 64, 127 }; + SoSeparator * textureRoot = new SoSeparator; + textureRoot->ref(); + SoTexture2 * sharedTexture = new SoTexture2; + sharedTexture->image.setValue(SbVec2s(1, 1), 4, pixels); + textureRoot->addChild(sharedTexture); + textureRoot->addChild(new SoCube); + textureRoot->addChild(new SoCube); + SoIRRenderAction textureAction(SbViewportRegion(32, 32)); + textureAction.apply(textureRoot); + if (textureAction.getDrawList().getNumCommands() != 2 || + textureAction.getDrawList().getCommand(0).material.texture.pixels != + textureAction.getDrawList().getCommand(1).material.texture.pixels) { + std::cerr << "FAIL: frame texture payload was copied once per command" + << std::endl; + result = 1; + } + textureRoot->unref(); + } + + // A later ordinary material update must invalidate the packed-color + // provenance. Otherwise a retained command could reuse opacity from the + // material that preceded SoVertexProperty. + SoSeparator * invalidatedAlphaRoot = new SoSeparator; + invalidatedAlphaRoot->ref(); + SoMaterial * inheritedAlphaMaterial = new SoMaterial; + inheritedAlphaMaterial->transparency = 0.5f; + invalidatedAlphaRoot->addChild(inheritedAlphaMaterial); + + SoVertexProperty * invalidatedAlphaVertexProperty = new SoVertexProperty; + invalidatedAlphaVertexProperty->vertex.setValues(0, 4, alphaPoints); + invalidatedAlphaVertexProperty->orderedRGBA.setValues(0, 4, alphaColors); + invalidatedAlphaVertexProperty->materialBinding = SoVertexProperty::PER_VERTEX; + invalidatedAlphaRoot->addChild(invalidatedAlphaVertexProperty); + + SoMaterial * replacementMaterial = new SoMaterial; + replacementMaterial->transparency = 0.0f; + invalidatedAlphaRoot->addChild(replacementMaterial); + SoFaceSet * invalidatedAlphaFaces = new SoFaceSet; + invalidatedAlphaFaces->numVertices = 4; + invalidatedAlphaRoot->addChild(invalidatedAlphaFaces); + + SoIRRenderAction invalidatedAlphaAction(SbViewportRegion(64, 64)); + invalidatedAlphaAction.apply(invalidatedAlphaRoot); + if (invalidatedAlphaAction.getDrawList().getNumCommands() != 1 || + !nearlyEqual(invalidatedAlphaAction.getDrawList().getCommand(0).material.opacity, + 1.0f)) { + std::cerr << "FAIL: ordinary material state did not clear packed-color provenance" + << std::endl; + result = 1; + } + invalidatedAlphaRoot->unref(); + + struct BindingCase { + SoMaterialBinding::Binding binding; + bool indexed; + const char * name; + }; + const BindingCase cases[] = { + { SoMaterialBinding::PER_PART, false, "PER_PART" }, + { SoMaterialBinding::PER_PART_INDEXED, true, "PER_PART_INDEXED" }, + { SoMaterialBinding::PER_FACE, false, "PER_FACE" }, + { SoMaterialBinding::PER_FACE_INDEXED, true, "PER_FACE_INDEXED" }, + { SoMaterialBinding::PER_VERTEX, false, "PER_VERTEX" }, + { SoMaterialBinding::PER_VERTEX_INDEXED, true, "PER_VERTEX_INDEXED" } + }; + + const SbVec3f points[] = { + SbVec3f(-1.0f, -1.0f, 0.0f), SbVec3f(0.0f, -1.0f, 0.0f), + SbVec3f(-0.5f, 0.0f, 0.0f), SbVec3f(0.0f, 0.0f, 0.0f), + SbVec3f(1.0f, 0.0f, 0.0f), SbVec3f(0.5f, 1.0f, 0.0f) + }; + const int32_t coordIndices[] = { 0, 1, 2, -1, 3, 4, 5, -1 }; + const int32_t faceMaterialIndices[] = { 0, 1 }; + const int32_t vertexMaterialIndices[] = { 0, 1, 2, 3, 4, 5 }; + const SbColor materialColors[] = { + SbColor(1.0f, 0.0f, 0.0f), SbColor(0.0f, 1.0f, 0.0f), + SbColor(0.0f, 0.0f, 1.0f), SbColor(1.0f, 1.0f, 0.0f), + SbColor(1.0f, 0.0f, 1.0f), SbColor(0.0f, 1.0f, 1.0f) + }; + const float materialTransparency[] = { 0.0f, 0.5f, 0.0f, + 0.5f, 0.0f, 0.5f }; + + for (const BindingCase & bindingCase : cases) { + SoSeparator * batchRoot = new SoSeparator; + batchRoot->ref(); + + SoCoordinate3 * coordinates = new SoCoordinate3; + coordinates->point.setValues(0, 6, points); + batchRoot->addChild(coordinates); + + SoMaterial * batchMaterial = new SoMaterial; + batchMaterial->diffuseColor.setValues(0, 6, materialColors); + batchMaterial->transparency.setValues(0, 6, materialTransparency); + batchRoot->addChild(batchMaterial); + + SoMaterialBinding * materialBinding = new SoMaterialBinding; + materialBinding->value = bindingCase.binding; + batchRoot->addChild(materialBinding); + + SoIndexedFaceSet * faces = new SoIndexedFaceSet; + faces->coordIndex.setValues(0, 8, coordIndices); + if (bindingCase.binding == SoMaterialBinding::PER_FACE_INDEXED || + bindingCase.binding == SoMaterialBinding::PER_PART_INDEXED) { + faces->materialIndex.setValues(0, 2, faceMaterialIndices); + } + else if (bindingCase.binding == SoMaterialBinding::PER_VERTEX_INDEXED) { + faces->materialIndex.setValues(0, 6, vertexMaterialIndices); + } + const std::vector originalCoordIndices( + faces->coordIndex.getValues(0), faces->coordIndex.getValues(0) + 8); + const int originalMaterialIndexCount = faces->materialIndex.getNum(); + const std::vector originalMaterialIndices = originalMaterialIndexCount + ? std::vector(faces->materialIndex.getValues(0), + faces->materialIndex.getValues(0) + originalMaterialIndexCount) + : std::vector(); + batchRoot->addChild(faces); + + SoIRRenderAction batchAction(SbViewportRegion(64, 64)); + batchAction.apply(batchRoot); + + bool hasGeneratedColors = false; + bool hasTransparentBatch = false; + const int expectedCommands = + (bindingCase.binding == SoMaterialBinding::PER_PART || + bindingCase.binding == SoMaterialBinding::PER_PART_INDEXED || + (bindingCase.binding == SoMaterialBinding::PER_FACE || + bindingCase.binding == SoMaterialBinding::PER_FACE_INDEXED)) ? 2 : 1; + if (batchAction.getDrawList().getNumCommands() != expectedCommands) { + std::cerr << "FAIL: " << bindingCase.name + << " did not produce two material batches" << std::endl; + result = 1; + } + for (int i = 0; i < batchAction.getDrawList().getNumCommands(); ++i) { + const SoRenderCommand & command = batchAction.getDrawList().getCommand(i); + if (command.geometry.colors) { + hasGeneratedColors = true; + if (!command.material.vertexColorAlphaIncludesOpacity) { + std::cerr << "FAIL: " << bindingCase.name + << " lost generated vertex alpha policy" << std::endl; + result = 1; + } + } + if (command.opacityClass == SO_OPACITY_TRANSPARENT) { + hasTransparentBatch = true; + } + } + if ((bindingCase.binding == SoMaterialBinding::PER_VERTEX || + bindingCase.binding == SoMaterialBinding::PER_VERTEX_INDEXED) && + !hasGeneratedColors) { + std::cerr << "FAIL: " << bindingCase.name + << " did not retain per-vertex colors" << std::endl; + result = 1; + } + if (!hasTransparentBatch) { + std::cerr << "FAIL: " << bindingCase.name + << " did not classify transparent material data" << std::endl; + result = 1; + } + + if (std::vector(faces->coordIndex.getValues(0), + faces->coordIndex.getValues(0) + 8) != originalCoordIndices || + faces->materialIndex.getNum() != originalMaterialIndexCount || + (originalMaterialIndexCount && + std::vector(faces->materialIndex.getValues(0), + faces->materialIndex.getValues(0) + originalMaterialIndexCount) + != originalMaterialIndices)) { + std::cerr << "FAIL: " << bindingCase.name + << " modified the source scene graph" << std::endl; + result = 1; + } + + batchRoot->unref(); + } + + return result; +} + +int +main() +{ + const int result = runTest(); + SoDB::finish(); + return result; +} diff --git a/testsuite/render/baselines/gl/alpha_test_basic.png b/testsuite/render/baselines/gl/alpha_test_basic.png new file mode 100644 index 0000000000..7f9b8405b5 Binary files /dev/null and b/testsuite/render/baselines/gl/alpha_test_basic.png differ diff --git a/testsuite/render/baselines/gl/depth_buffer_basic.png b/testsuite/render/baselines/gl/depth_buffer_basic.png new file mode 100644 index 0000000000..2b78453e0a Binary files /dev/null and b/testsuite/render/baselines/gl/depth_buffer_basic.png differ diff --git a/testsuite/render/baselines/gl/material_emissive.png b/testsuite/render/baselines/gl/material_emissive.png new file mode 100644 index 0000000000..8a08df11d0 Binary files /dev/null and b/testsuite/render/baselines/gl/material_emissive.png differ diff --git a/testsuite/render/baselines/gl/point_spot_lights_basic.png b/testsuite/render/baselines/gl/point_spot_lights_basic.png new file mode 100644 index 0000000000..2db5320e14 Binary files /dev/null and b/testsuite/render/baselines/gl/point_spot_lights_basic.png differ diff --git a/testsuite/render/baselines/gl/specular_shininess_basic.png b/testsuite/render/baselines/gl/specular_shininess_basic.png new file mode 100644 index 0000000000..c41eaa7efc Binary files /dev/null and b/testsuite/render/baselines/gl/specular_shininess_basic.png differ diff --git a/testsuite/render/baselines/gl/texture_luminance_alpha.png b/testsuite/render/baselines/gl/texture_luminance_alpha.png new file mode 100644 index 0000000000..77e137d6b6 Binary files /dev/null and b/testsuite/render/baselines/gl/texture_luminance_alpha.png differ diff --git a/testsuite/render/baselines/gl/texture_rgb.png b/testsuite/render/baselines/gl/texture_rgb.png new file mode 100644 index 0000000000..12cd03ca84 Binary files /dev/null and b/testsuite/render/baselines/gl/texture_rgb.png differ diff --git a/testsuite/render/baselines/gl/transparency_depth_basic.png b/testsuite/render/baselines/gl/transparency_depth_basic.png new file mode 100644 index 0000000000..f4ecaa1b4a Binary files /dev/null and b/testsuite/render/baselines/gl/transparency_depth_basic.png differ diff --git a/testsuite/render/baselines/gl/transparency_order_basic.png b/testsuite/render/baselines/gl/transparency_order_basic.png new file mode 100644 index 0000000000..db3637d8e0 Binary files /dev/null and b/testsuite/render/baselines/gl/transparency_order_basic.png differ diff --git a/testsuite/render/baselines/gl/transparent_overlap.png b/testsuite/render/baselines/gl/transparent_overlap.png new file mode 100644 index 0000000000..8e2e6593f3 Binary files /dev/null and b/testsuite/render/baselines/gl/transparent_overlap.png differ diff --git a/testsuite/render/baselines/gl/two_lights.png b/testsuite/render/baselines/gl/two_lights.png new file mode 100644 index 0000000000..65e312b7ea Binary files /dev/null and b/testsuite/render/baselines/gl/two_lights.png differ diff --git a/testsuite/render/scenes/alpha_test_basic.iv b/testsuite/render/scenes/alpha_test_basic.iv new file mode 100644 index 0000000000..9461d2971c --- /dev/null +++ b/testsuite/render/scenes/alpha_test_basic.iv @@ -0,0 +1,28 @@ +#Inventor V2.1 ascii + +Separator { + PerspectiveCamera { + position 0 0 5 + heightAngle 0.785398 + nearDistance 0.1 + farDistance 10 + } + LightModel { model BASE_COLOR } + AlphaTest { function GREATER value 0.5 } + Material { diffuseColor 0.95 0.8 0.1 } + Texture2 { + model MODULATE + wrapS CLAMP + wrapT CLAMP + image 2 2 4 + 0xe63b3bff 0xe63b3b00 + 0xe63b3b00 0xe63b3bff + } + TextureCoordinate2 { + point [ 0 0, 1 0, 1 1, 0 1 ] + } + Coordinate3 { + point [ -1 -1 0, 1 -1 0, 1 1 0, -1 1 0 ] + } + FaceSet { numVertices 4 } +} diff --git a/testsuite/render/scenes/depth_buffer_basic.iv b/testsuite/render/scenes/depth_buffer_basic.iv new file mode 100644 index 0000000000..76c33b510a --- /dev/null +++ b/testsuite/render/scenes/depth_buffer_basic.iv @@ -0,0 +1,26 @@ +#Inventor V2.1 ascii + +Separator { + PerspectiveCamera { + position 0 0 6 + heightAngle 0.785398 + nearDistance 0.1 + farDistance 20 + } + LightModel { model BASE_COLOR } + DepthBuffer { test TRUE write TRUE function LEQUAL } + Separator { + Material { diffuseColor 0.1 0.2 0.9 } + Coordinate3 { + point [ -1.1 -1 -0.6, 1.1 -1 -0.6, 1.1 1 -0.6, -1.1 1 -0.6 ] + } + FaceSet { numVertices 4 } + } + Separator { + Material { diffuseColor 0.9 0.15 0.1 } + Coordinate3 { + point [ -0.75 -0.75 0.2, 0.75 -0.75 0.2, 0.75 0.75 0.2, -0.75 0.75 0.2 ] + } + FaceSet { numVertices 4 } + } +} diff --git a/testsuite/render/scenes/material_emissive.iv b/testsuite/render/scenes/material_emissive.iv new file mode 100644 index 0000000000..bc3180be4a --- /dev/null +++ b/testsuite/render/scenes/material_emissive.iv @@ -0,0 +1,36 @@ +#Inventor V2.1 ascii + +Separator { + PerspectiveCamera { + position 0 0 5 + heightAngle 0.785398 + nearDistance 0.1 + farDistance 10 + } + LightModel { model PHONG } + DirectionalLight { + direction 0 0 -1 + color 1 1 1 + intensity 1 + } + Separator { + Translation { translation -1 0 0 } + Material { + ambientColor 0 0 0 + diffuseColor 0 0 0 + emissiveColor 0.75 0.05 0.02 + specularColor 0 0 0 + } + Sphere { radius 0.85 } + } + Separator { + Translation { translation 1 0 0 } + Material { + ambientColor 0 0 0 + diffuseColor 0.55 0.55 0.55 + emissiveColor 0 0 0 + specularColor 0 0 0 + } + Sphere { radius 0.85 } + } +} diff --git a/testsuite/render/scenes/point_spot_lights_basic.iv b/testsuite/render/scenes/point_spot_lights_basic.iv new file mode 100644 index 0000000000..5099714d7a --- /dev/null +++ b/testsuite/render/scenes/point_spot_lights_basic.iv @@ -0,0 +1,31 @@ +#Inventor V2.1 ascii + +Separator { + PerspectiveCamera { + position 0 0 5.5 + heightAngle 0.7 + nearDistance 0.1 + farDistance 20 + } + LightModel { model PHONG } + PointLight { + location 2 1.5 3 + color 1 0.18 0.05 + intensity 0.9 + } + SpotLight { + location -2 1.5 3 + direction 0.5 -0.4 -1 + color 0.1 0.3 1 + intensity 1 + cutOffAngle 0.9 + dropOffRate 0.35 + } + Material { + ambientColor 0 0 0 + diffuseColor 0.65 0.65 0.65 + specularColor 0.25 0.25 0.25 + shininess 0.35 + } + Sphere { radius 1.25 } +} diff --git a/testsuite/render/scenes/specular_shininess_basic.iv b/testsuite/render/scenes/specular_shininess_basic.iv new file mode 100644 index 0000000000..6d23280333 --- /dev/null +++ b/testsuite/render/scenes/specular_shininess_basic.iv @@ -0,0 +1,36 @@ +#Inventor V2.1 ascii + +Separator { + PerspectiveCamera { + position 0 0 7 + heightAngle 0.785398 + nearDistance 0.1 + farDistance 20 + } + LightModel { model PHONG } + DirectionalLight { + direction 0 0 -1 + color 1 0.95 0.8 + intensity 1 + } + Separator { + Translation { translation -1.25 0 0 } + Material { + ambientColor 0.03 0.03 0.03 + diffuseColor 0.3 0.35 0.45 + specularColor 1 0.95 0.8 + shininess 0.12 + } + Sphere { radius 1.05 } + } + Separator { + Translation { translation 1.25 0 0 } + Material { + ambientColor 0.03 0.03 0.03 + diffuseColor 0.3 0.35 0.45 + specularColor 1 0.95 0.8 + shininess 0.35 + } + Sphere { radius 1.05 } + } +} diff --git a/testsuite/render/scenes/texture_luminance_alpha.iv b/testsuite/render/scenes/texture_luminance_alpha.iv new file mode 100644 index 0000000000..6a2c7a014d --- /dev/null +++ b/testsuite/render/scenes/texture_luminance_alpha.iv @@ -0,0 +1,19 @@ +#Inventor V2.1 ascii + +Separator { + PerspectiveCamera { + position 0 0 5 + heightAngle 0.785398 + nearDistance 0.1 + farDistance 10 + } + LightModel { model BASE_COLOR } + Material { diffuseColor 0.2 0.8 0.35 } + Texture2 { + model MODULATE + image 2 2 2 + 0x66ff 0xccff + 0x99aa 0xeeaa + } + Cube { } +} diff --git a/testsuite/render/scenes/texture_rgb.iv b/testsuite/render/scenes/texture_rgb.iv new file mode 100644 index 0000000000..6765d4efee --- /dev/null +++ b/testsuite/render/scenes/texture_rgb.iv @@ -0,0 +1,19 @@ +#Inventor V2.1 ascii + +Separator { + PerspectiveCamera { + position 0 0 5 + heightAngle 0.785398 + nearDistance 0.1 + farDistance 10 + } + LightModel { model BASE_COLOR } + Material { diffuseColor 1 1 1 } + Texture2 { + model MODULATE + image 2 2 3 + 0x00e63b3b 0x003b6ee6 + 0x003be66e 0x00e6c93b + } + Cube { } +} diff --git a/testsuite/render/scenes/transparency_depth_basic.iv b/testsuite/render/scenes/transparency_depth_basic.iv new file mode 100644 index 0000000000..3846e72f43 --- /dev/null +++ b/testsuite/render/scenes/transparency_depth_basic.iv @@ -0,0 +1,34 @@ +#Inventor V2.1 ascii + +Separator { + PerspectiveCamera { + position 0 0 6 + heightAngle 0.785398 + nearDistance 0.1 + farDistance 20 + } + LightModel { model BASE_COLOR } + DepthBuffer { test TRUE write TRUE function LEQUAL } + Separator { + Translation { translation 0 0 0.25 } + Material { + diffuseColor 0.9 0.1 0.05 + transparency 0.35 + } + Coordinate3 { + point [ -0.9 -0.9 0, 0.9 -0.9 0, 0.9 0.9 0, -0.9 0.9 0 ] + } + FaceSet { numVertices 4 } + } + Separator { + Translation { translation 0 0 -0.35 } + Material { + diffuseColor 0.05 0.2 0.95 + transparency 0.35 + } + Coordinate3 { + point [ -1.1 -1.1 0, 1.1 -1.1 0, 1.1 1.1 0, -1.1 1.1 0 ] + } + FaceSet { numVertices 4 } + } +} diff --git a/testsuite/render/scenes/transparency_order_basic.iv b/testsuite/render/scenes/transparency_order_basic.iv new file mode 100644 index 0000000000..6fd2a851c5 --- /dev/null +++ b/testsuite/render/scenes/transparency_order_basic.iv @@ -0,0 +1,33 @@ +#Inventor V2.1 ascii + +Separator { + PerspectiveCamera { + position 0 0 6 + heightAngle 0.785398 + nearDistance 0.1 + farDistance 20 + } + LightModel { model BASE_COLOR } + DepthBuffer { test TRUE write TRUE function LEQUAL } + Separator { + Translation { translation 0 0 0.25 } + Material { + diffuseColor 0.9 0.1 0.05 + transparency 0.35 + } + Coordinate3 { + point [ -0.9 -0.9 0, 0.9 -0.9 0, 0.9 0.9 0, -0.9 0.9 0 ] + } + FaceSet { numVertices 4 } + } + Separator { + Translation { translation 0 0 -0.35 } + Material { + diffuseColor 0.05 0.2 0.95 + } + Coordinate3 { + point [ -1.1 -1.1 0, 1.1 -1.1 0, 1.1 1.1 0, -1.1 1.1 0 ] + } + FaceSet { numVertices 4 } + } +} diff --git a/testsuite/render/scenes/transparent_overlap.iv b/testsuite/render/scenes/transparent_overlap.iv new file mode 100644 index 0000000000..477e44b3ea --- /dev/null +++ b/testsuite/render/scenes/transparent_overlap.iv @@ -0,0 +1,32 @@ +#Inventor V2.1 ascii + +Separator { + PerspectiveCamera { + position 0 0 6 + heightAngle 0.785398 + nearDistance 0.1 + farDistance 10 + } + LightModel { model BASE_COLOR } + DepthBuffer { test FALSE } + Separator { + Material { + diffuseColor 0.9 0.15 0.1 + transparency 0.35 + } + Coordinate3 { + point [ -1.1 -1 0, 0.3 -1 0, 0.3 1 0, -1.1 1 0 ] + } + FaceSet { numVertices 4 } + } + Separator { + Material { + diffuseColor 0.1 0.25 0.9 + transparency 0.35 + } + Coordinate3 { + point [ -0.3 -1 0, 1.1 -1 0, 1.1 1 0, -0.3 1 0 ] + } + FaceSet { numVertices 4 } + } +} diff --git a/testsuite/render/scenes/two_lights.iv b/testsuite/render/scenes/two_lights.iv new file mode 100644 index 0000000000..f8bf6478cb --- /dev/null +++ b/testsuite/render/scenes/two_lights.iv @@ -0,0 +1,27 @@ +#Inventor V2.1 ascii + +Separator { + PerspectiveCamera { + position 0 0 5 + heightAngle 0.785398 + nearDistance 0.1 + farDistance 10 + } + LightModel { model PHONG } + DirectionalLight { + direction -1 -1 -1 + color 1 0.55 0.2 + intensity 0.8 + } + DirectionalLight { + direction 1 -0.5 -1 + color 0.2 0.45 1 + intensity 0.7 + } + Material { + ambientColor 0 0 0 + diffuseColor 0.7 0.7 0.7 + specularColor 0 0 0 + } + Sphere { radius 1.25 } +} diff --git a/testsuite/render/specs/alpha_test_basic.yml b/testsuite/render/specs/alpha_test_basic.yml new file mode 100644 index 0000000000..c5dd1831c9 --- /dev/null +++ b/testsuite/render/specs/alpha_test_basic.yml @@ -0,0 +1,8 @@ +id: alpha_test_basic +scene: ../scenes/alpha_test_basic.iv + +viewport: + background: [0.08, 0.08, 0.08, 1.0] + +baseline: ../baselines/gl/alpha_test_basic.png +compare: relaxed diff --git a/testsuite/render/specs/depth_buffer_basic.yml b/testsuite/render/specs/depth_buffer_basic.yml new file mode 100644 index 0000000000..83707a40c0 --- /dev/null +++ b/testsuite/render/specs/depth_buffer_basic.yml @@ -0,0 +1,7 @@ +id: depth_buffer_basic +scene: ../scenes/depth_buffer_basic.iv + +viewport: + background: [0.08, 0.08, 0.08, 1.0] + +baseline: ../baselines/gl/depth_buffer_basic.png diff --git a/testsuite/render/specs/material_emissive.yml b/testsuite/render/specs/material_emissive.yml new file mode 100644 index 0000000000..3c5d9f772f --- /dev/null +++ b/testsuite/render/specs/material_emissive.yml @@ -0,0 +1,7 @@ +id: material_emissive +scene: ../scenes/material_emissive.iv + +viewport: + background: [0.03, 0.03, 0.03, 1.0] + +baseline: ../baselines/gl/material_emissive.png diff --git a/testsuite/render/specs/point_spot_lights_basic.yml b/testsuite/render/specs/point_spot_lights_basic.yml new file mode 100644 index 0000000000..6c5f3eb2ba --- /dev/null +++ b/testsuite/render/specs/point_spot_lights_basic.yml @@ -0,0 +1,7 @@ +id: point_spot_lights_basic +scene: ../scenes/point_spot_lights_basic.iv + +viewport: + background: [0.03, 0.03, 0.03, 1.0] + +baseline: ../baselines/gl/point_spot_lights_basic.png diff --git a/testsuite/render/specs/specular_shininess_basic.yml b/testsuite/render/specs/specular_shininess_basic.yml new file mode 100644 index 0000000000..eff8d52146 --- /dev/null +++ b/testsuite/render/specs/specular_shininess_basic.yml @@ -0,0 +1,7 @@ +id: specular_shininess_basic +scene: ../scenes/specular_shininess_basic.iv + +viewport: + background: [0.04, 0.04, 0.04, 1.0] + +baseline: ../baselines/gl/specular_shininess_basic.png diff --git a/testsuite/render/specs/texture_luminance_alpha.yml b/testsuite/render/specs/texture_luminance_alpha.yml new file mode 100644 index 0000000000..6805a91113 --- /dev/null +++ b/testsuite/render/specs/texture_luminance_alpha.yml @@ -0,0 +1,7 @@ +id: texture_luminance_alpha +scene: ../scenes/texture_luminance_alpha.iv + +viewport: + background: [0.5, 0.5, 0.5, 1.0] + +baseline: ../baselines/gl/texture_luminance_alpha.png diff --git a/testsuite/render/specs/texture_rgb.yml b/testsuite/render/specs/texture_rgb.yml new file mode 100644 index 0000000000..5bdce2bbdb --- /dev/null +++ b/testsuite/render/specs/texture_rgb.yml @@ -0,0 +1,7 @@ +id: texture_rgb +scene: ../scenes/texture_rgb.iv + +viewport: + background: [0.5, 0.5, 0.5, 1.0] + +baseline: ../baselines/gl/texture_rgb.png diff --git a/testsuite/render/specs/transparency_depth_basic.yml b/testsuite/render/specs/transparency_depth_basic.yml new file mode 100644 index 0000000000..44dadf9e69 --- /dev/null +++ b/testsuite/render/specs/transparency_depth_basic.yml @@ -0,0 +1,7 @@ +id: transparency_depth_basic +scene: ../scenes/transparency_depth_basic.iv + +viewport: + background: [0.08, 0.08, 0.08, 1.0] + +baseline: ../baselines/gl/transparency_depth_basic.png diff --git a/testsuite/render/specs/transparency_order_basic.yml b/testsuite/render/specs/transparency_order_basic.yml new file mode 100644 index 0000000000..c3982c6459 --- /dev/null +++ b/testsuite/render/specs/transparency_order_basic.yml @@ -0,0 +1,7 @@ +id: transparency_order_basic +scene: ../scenes/transparency_order_basic.iv + +viewport: + background: [0.08, 0.08, 0.08, 1.0] + +baseline: ../baselines/gl/transparency_order_basic.png diff --git a/testsuite/render/specs/transparent_overlap.yml b/testsuite/render/specs/transparent_overlap.yml new file mode 100644 index 0000000000..003c1b081f --- /dev/null +++ b/testsuite/render/specs/transparent_overlap.yml @@ -0,0 +1,7 @@ +id: transparent_overlap +scene: ../scenes/transparent_overlap.iv + +viewport: + background: [0.5, 0.5, 0.5, 1.0] + +baseline: ../baselines/gl/transparent_overlap.png diff --git a/testsuite/render/specs/two_lights.yml b/testsuite/render/specs/two_lights.yml new file mode 100644 index 0000000000..8a0e4cd6ef --- /dev/null +++ b/testsuite/render/specs/two_lights.yml @@ -0,0 +1,7 @@ +id: two_lights +scene: ../scenes/two_lights.iv + +viewport: + background: [0.08, 0.08, 0.08, 1.0] + +baseline: ../baselines/gl/two_lights.png