|
| 1 | +/* |
| 2 | + * TestTextureStrides.cpp |
| 3 | + * |
| 4 | + * Copyright (c) 2015 Lukas Hermanns. All rights reserved. |
| 5 | + * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). |
| 6 | + */ |
| 7 | + |
| 8 | +#include "Testbed.h" |
| 9 | +#include <LLGL/Utils/Parse.h> |
| 10 | +#include <Gauss/Translate.h> |
| 11 | +#include <Gauss/Rotate.h> |
| 12 | +#include <Gauss/Scale.h> |
| 13 | + |
| 14 | + |
| 15 | +/* |
| 16 | +Test creating two textures from the same image source using row strides. Then render them onto two separate cubes. |
| 17 | +First frame: |
| 18 | + Create textures with *half* row stride, this will result in interleaved rows, |
| 19 | + i.e. row 0 from left side, row 1 from right side, row 2 from left side again, etc. |
| 20 | + Rendering this texture will create the "moire" effect, which is acceptable here. |
| 21 | +Second frame: |
| 22 | + Create textures with full row stride, this will result in a cutoff image as if only a region was copied from the image. |
| 23 | +*/ |
| 24 | +DEF_TEST( TextureStrides ) |
| 25 | +{ |
| 26 | + static TestResult result = TestResult::Passed; |
| 27 | + static PipelineState* pso; |
| 28 | + static PipelineLayout* psoLayout; |
| 29 | + |
| 30 | + constexpr unsigned numFrames = 2; |
| 31 | + |
| 32 | + if (frame == 0) |
| 33 | + { |
| 34 | + result = TestResult::Passed; |
| 35 | + |
| 36 | + if (shaders[VSTextured] == nullptr || shaders[PSTextured] == nullptr) |
| 37 | + { |
| 38 | + Log::Errorf("Missing shaders for backend\n"); |
| 39 | + return TestResult::FailedErrors; |
| 40 | + } |
| 41 | + |
| 42 | + // Create graphics PSO |
| 43 | + GraphicsPipelineDescriptor psoDesc; |
| 44 | + { |
| 45 | + psoDesc.debugName = "TextureStrides.PSO"; |
| 46 | + psoDesc.pipelineLayout = layouts[PipelineTextured]; |
| 47 | + psoDesc.renderPass = swapChain->GetRenderPass(); |
| 48 | + psoDesc.vertexShader = shaders[VSTextured]; |
| 49 | + psoDesc.fragmentShader = shaders[PSTextured]; |
| 50 | + psoDesc.depth.testEnabled = true; |
| 51 | + psoDesc.depth.writeEnabled = true; |
| 52 | + psoDesc.rasterizer.cullMode = CullMode::Back; |
| 53 | + } |
| 54 | + CREATE_GRAPHICS_PSO_EXT(pso, psoDesc, psoDesc.debugName); |
| 55 | + } |
| 56 | + |
| 57 | + // Create primary texture and two with different stride/offset |
| 58 | + const std::string imagePath = "../Media/Textures/"; |
| 59 | + Image image = TestbedContext::LoadImageFromFile(imagePath + "Grid10x10.png", opt.verbose); |
| 60 | + |
| 61 | + ImageView imageViewA; |
| 62 | + { |
| 63 | + imageViewA.format = image.GetFormat(); |
| 64 | + imageViewA.dataType = image.GetDataType(); |
| 65 | + imageViewA.data = image.GetData(); |
| 66 | + imageViewA.dataSize = image.GetDataSize(); |
| 67 | + imageViewA.rowStride = (frame == 0 ? image.GetRowStride()/2 : image.GetRowStride()); |
| 68 | + } |
| 69 | + TextureDescriptor texADesc; |
| 70 | + { |
| 71 | + texADesc.debugName = "texA-strides"; |
| 72 | + texADesc.format = Format::RGBA8UNorm; |
| 73 | + texADesc.extent.width = image.GetExtent().width/2; |
| 74 | + texADesc.extent.height = image.GetExtent().height; |
| 75 | + texADesc.extent.depth = 1; |
| 76 | + texADesc.mipLevels = 1; |
| 77 | + } |
| 78 | + CREATE_TEXTURE(texA, texADesc, texADesc.debugName, &imageViewA); |
| 79 | + |
| 80 | + const char* imageDataBytes = static_cast<const char*>(image.GetData()); |
| 81 | + const std::ptrdiff_t imageBOffset = (frame == 0 ? image.GetDataSize()/2 : image.GetRowStride()/2); |
| 82 | + |
| 83 | + ImageView imageViewB; |
| 84 | + { |
| 85 | + imageViewB.format = image.GetFormat(); |
| 86 | + imageViewB.dataType = image.GetDataType(); |
| 87 | + imageViewB.data = imageDataBytes + imageBOffset; |
| 88 | + imageViewB.dataSize = image.GetDataSize() - imageBOffset; |
| 89 | + imageViewB.rowStride = (frame == 0 ? image.GetRowStride()/2 : image.GetRowStride()); |
| 90 | + } |
| 91 | + TextureDescriptor texBDesc; |
| 92 | + { |
| 93 | + texBDesc.debugName = "texB-strides"; |
| 94 | + texBDesc.format = Format::RGBA8UNorm; |
| 95 | + texBDesc.extent.width = image.GetExtent().width/2; |
| 96 | + texBDesc.extent.height = image.GetExtent().height; |
| 97 | + texBDesc.extent.depth = 1; |
| 98 | + texBDesc.mipLevels = 1; |
| 99 | + } |
| 100 | + CREATE_TEXTURE(texB, texBDesc, texBDesc.debugName, &imageViewB); |
| 101 | + |
| 102 | + // Initialize scene constants |
| 103 | + sceneConstants = SceneConstants{}; |
| 104 | + |
| 105 | + sceneConstants.vpMatrix = projection; |
| 106 | + |
| 107 | + auto TransformWorldMatrixAndUpdateCbuffer = [this](Gs::Matrix4f& wMatrix, float posX, float turn) |
| 108 | + { |
| 109 | + wMatrix.LoadIdentity(); |
| 110 | + Gs::Translate(wMatrix, Gs::Vector3f{ posX, 0.0f, 3.5f }); |
| 111 | + Gs::RotateFree(wMatrix, Gs::Vector3f{ 0, 1, 0 }, Gs::Deg2Rad(turn)); |
| 112 | + Gs::Scale(wMatrix, Gs::Vector3f{ 0.5f }); |
| 113 | + |
| 114 | + cmdBuffer->UpdateBuffer(*sceneCbuffer, 0, &sceneConstants, sizeof(sceneConstants)); |
| 115 | + }; |
| 116 | + |
| 117 | + // Render scene |
| 118 | + Texture* readbackTex = nullptr; |
| 119 | + |
| 120 | + const IndexedTriangleMesh& mesh = models[ModelCube]; |
| 121 | + |
| 122 | + cmdBuffer->Begin(); |
| 123 | + { |
| 124 | + // Graphics can be set inside and outside a render pass, so test binding this PSO outside the render pass |
| 125 | + cmdBuffer->SetVertexBuffer(*meshBuffer); |
| 126 | + cmdBuffer->SetIndexBuffer(*meshBuffer, Format::R32UInt, mesh.indexBufferOffset); |
| 127 | + cmdBuffer->SetPipelineState(*pso); |
| 128 | + |
| 129 | + cmdBuffer->BeginRenderPass(*swapChain); |
| 130 | + { |
| 131 | + // Draw scene |
| 132 | + cmdBuffer->Clear(ClearFlags::ColorDepth, bgColorDarkBlue); |
| 133 | + cmdBuffer->SetViewport(opt.resolution); |
| 134 | + cmdBuffer->SetResource(0, *sceneCbuffer); |
| 135 | + cmdBuffer->SetResource(2, *samplers[SamplerNearestClamp]); |
| 136 | + |
| 137 | + // Draw left cube |
| 138 | + TransformWorldMatrixAndUpdateCbuffer(sceneConstants.wMatrix, -1.0f, +35.0f); |
| 139 | + cmdBuffer->SetResource(1, *texA); |
| 140 | + |
| 141 | + cmdBuffer->DrawIndexed(mesh.numIndices, 0); |
| 142 | + |
| 143 | + // Draw right cube |
| 144 | + TransformWorldMatrixAndUpdateCbuffer(sceneConstants.wMatrix, +1.0f, -35.0f); |
| 145 | + cmdBuffer->SetResource(1, *texB); |
| 146 | + |
| 147 | + cmdBuffer->DrawIndexed(mesh.numIndices, 0); |
| 148 | + |
| 149 | + // Capture framebuffer |
| 150 | + readbackTex = CaptureFramebuffer(*cmdBuffer, swapChain->GetColorFormat(), opt.resolution); |
| 151 | + } |
| 152 | + cmdBuffer->EndRenderPass(); |
| 153 | + } |
| 154 | + cmdBuffer->End(); |
| 155 | + |
| 156 | + // Match entire color buffer and create delta heat map |
| 157 | + const std::string colorBufferName = "TextureStrides_Frame" + std::to_string(frame); |
| 158 | + |
| 159 | + SaveCapture(readbackTex, colorBufferName); |
| 160 | + |
| 161 | + const int threshold = 20; // High threshold because of nearest texture filter |
| 162 | + const unsigned tolerance = (frame == 0 ? 250 : 50); // Higher tolerance for frame 0, because of moire effect |
| 163 | + |
| 164 | + const DiffResult diff = DiffImages(colorBufferName, threshold, tolerance); |
| 165 | + |
| 166 | + // Evaluate readback result and tolerate 5 pixel that are beyond the threshold due to GPU differences with the reinterpretation of pixel formats |
| 167 | + TestResult intermediateResult = diff.Evaluate("texture strides", frame); |
| 168 | + if (intermediateResult != TestResult::Passed) |
| 169 | + result = intermediateResult; |
| 170 | + |
| 171 | + renderer->Release(*texA); |
| 172 | + renderer->Release(*texB); |
| 173 | + |
| 174 | + if (intermediateResult == TestResult::Passed || opt.greedy) |
| 175 | + { |
| 176 | + if (frame + 1 < numFrames) |
| 177 | + return TestResult::Continue; |
| 178 | + } |
| 179 | + |
| 180 | + // Clear resources |
| 181 | + renderer->Release(*pso); |
| 182 | + |
| 183 | + return result; |
| 184 | +} |
| 185 | + |
0 commit comments