// zlib open source license // // Copyright (c) 2017 to 2023 David Forsgren Piuva // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would be // appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not be // misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. #ifndef DFPSR_RENDER_SHADER_RGBA_MULTIPLY #define DFPSR_RENDER_SHADER_RGBA_MULTIPLY #include #include #include #include #include "Shader.h" #include "fillerTemplates.h" #include "../../image/Image.h" namespace dsr { struct RgbaMultiply_data { const TextureRgbaU8 diffuseMap; // Mip-mapping is allowed for diffuse textures. const TextureRgbaU8 lightMap; // Mip-mapping is not allowed for lightmaps, because it would increase the number of shaders to compile and still look worse. // Planar format with each vector representing the three triangle corners const TriangleTexCoords texCoords; const TriangleColors colors; // Normalize the color product by pre-multiplying the vertex colors float getVertexScale() { float result = 255.0f; // Scale from normalized to byte for the output if (texture_exists(this->diffuseMap)) { result *= 1.0f / 255.0f; // Normalize the diffuse map from 0..255 to 0..1 by dividing the vertex color } if (texture_exists(this->lightMap)) { result *= 1.0f / 255.0f; // Normalize the light map from 0..255 to 0..1 by dividing the vertex color } return result; } explicit RgbaMultiply_data(const TriangleInput &triangleInput) : diffuseMap(triangleInput.diffuseMap), lightMap(triangleInput.lightMap), texCoords(triangleInput.texCoords), colors(triangleInput.colors.getScaled(getVertexScale())) {} }; // TODO: Simplify by merging boolean flags into named states. // A texture can be: // Non-existing // Of a single layer // Of multiple layers // Of enough layers to avoid clamping the mip index // A color can be: // White // Constant // Faded // TODO: Skip converting to and from float colors when only sampling one texture or color. // TODO: Because colors are converted to 16-bit channels during multiplication, the shader's return value might as well use a 16-bit color format that is faster to multiply and shift. // 8 low bits for visible light, and 8 high bits for spill from multiplications before shifting results 8 bits to the right. // High intensity vertex colors multiplied at the end can use the high range for 10-bit image formats. template inline Rgba_F32 getPixels_2x2(void *data, const F32x4x3 &vertexWeights) { RgbaMultiply_data *assets = (RgbaMultiply_data*)data; if (HAS_DIFFUSE_MAP && !HAS_LIGHT_MAP && COLORLESS) { // Optimized for diffuse only F32x4 u1 = shaderMethods::interpolate(assets->texCoords.u1, vertexWeights); F32x4 v1 = shaderMethods::interpolate(assets->texCoords.v1, vertexWeights); return shaderMethods::sample_F32(assets->diffuseMap, u1, v1); } else if (HAS_LIGHT_MAP && !HAS_DIFFUSE_MAP && COLORLESS) { // Optimized for light only F32x4 u2 = shaderMethods::interpolate(assets->texCoords.u2, vertexWeights); F32x4 v2 = shaderMethods::interpolate(assets->texCoords.v2, vertexWeights); return shaderMethods::sample_F32(assets->lightMap, u2, v2); } else { // Interpolate the vertex color Rgba_F32 color = HAS_VERTEX_FADING ? shaderMethods::interpolateVertexColor(assets->colors.red, assets->colors.green, assets->colors.blue, assets->colors.alpha, vertexWeights) : Rgba_F32(F32x4(assets->colors.red.x), F32x4(assets->colors.green.x), F32x4(assets->colors.blue.x), F32x4(assets->colors.alpha.x)); // Sample diffuse if (HAS_DIFFUSE_MAP) { F32x4 u1 = shaderMethods::interpolate(assets->texCoords.u1, vertexWeights); F32x4 v1 = shaderMethods::interpolate(assets->texCoords.v1, vertexWeights); color = color * shaderMethods::sample_F32(assets->diffuseMap, u1, v1); } // Sample lightmap if (HAS_LIGHT_MAP) { F32x4 u2 = shaderMethods::interpolate(assets->texCoords.u2, vertexWeights); F32x4 v2 = shaderMethods::interpolate(assets->texCoords.v2, vertexWeights); color = color * shaderMethods::sample_F32(assets->lightMap, u2, v2); } return color; } } // The process method to take a function pointer to. // Must have the same signature as drawCallbackTemplate in Shader.h. static void processTriangle_RgbaMultiply(const TriangleInput &triangleInput, const ImageRgbaU8 &colorBuffer, const ImageF32 &depthBuffer, const ITriangle2D &triangle, const Projection &projection, const RowShape &shape, Filter filter) { // The pointers to textures may not be null, but can point to empty textures. RgbaMultiply_data data = RgbaMultiply_data(triangleInput); bool hasVertexFade = !(almostSame(data.colors.red) && almostSame(data.colors.green) && almostSame(data.colors.blue) && almostSame(data.colors.alpha)); bool colorless = almostOne(data.colors.red) && almostOne(data.colors.green) && almostOne(data.colors.blue) && almostOne(data.colors.alpha); // TODO: Should non-existing textures use null pointers in the data, or pointers to empty textures? if (texture_exists(data.diffuseMap)) { bool hasDiffusePyramid = texture_hasPyramid(data.diffuseMap); // TODO: Avoid generating mip levels for the lightmap texture instead of hard-coding it to no mip levels. if (texture_exists(data.lightMap)) { if (hasVertexFade) { // DiffuseLightVertex if (hasDiffusePyramid) { // With mipmap fillShape(&data, getPixels_2x2, colorBuffer, depthBuffer, triangle, projection, shape, filter); } else { // Without mipmap fillShape(&data, getPixels_2x2, colorBuffer, depthBuffer, triangle, projection, shape, filter); } } else { // DiffuseLight if (hasDiffusePyramid) { // With mipmap fillShape(&data, getPixels_2x2, colorBuffer, depthBuffer, triangle, projection, shape, filter); } else { // Without mipmap fillShape(&data, getPixels_2x2, colorBuffer, depthBuffer, triangle, projection, shape, filter); } } } else { if (hasVertexFade) { // DiffuseVertex if (hasDiffusePyramid) { // With mipmap fillShape(&data, getPixels_2x2, colorBuffer, depthBuffer, triangle, projection, shape, filter); } else { // Without mipmap fillShape(&data, getPixels_2x2, colorBuffer, depthBuffer, triangle, projection, shape, filter); } } else { if (colorless) { // Diffuse without normalization if (hasDiffusePyramid) { // With mipmap fillShape(&data, getPixels_2x2, colorBuffer, depthBuffer, triangle, projection, shape, filter); } else { // Without mipmap fillShape(&data, getPixels_2x2, colorBuffer, depthBuffer, triangle, projection, shape, filter); } } else { // Diffuse if (hasDiffusePyramid) { // With mipmap fillShape(&data, getPixels_2x2, colorBuffer, depthBuffer, triangle, projection, shape, filter); } else { // Without mipmap fillShape(&data, getPixels_2x2, colorBuffer, depthBuffer, triangle, projection, shape, filter); } } } } } else { if (texture_exists(data.lightMap)) { if (hasVertexFade) { // LightVertex fillShape(&data, getPixels_2x2, colorBuffer, depthBuffer, triangle, projection, shape, filter); } else { if (colorless) { // Light without normalization fillShape(&data, getPixels_2x2, colorBuffer, depthBuffer, triangle, projection, shape, filter); } else { // Light fillShape(&data, getPixels_2x2, colorBuffer, depthBuffer, triangle, projection, shape, filter); } } } else { if (hasVertexFade) { // Vertex fillShape(&data, getPixels_2x2, colorBuffer, depthBuffer, triangle, projection, shape, filter); } else { // Single color fillShape(&data, getPixels_2x2, colorBuffer, depthBuffer, triangle, projection, shape, filter); } } } } } #endif