diff --git a/filament/backend/CMakeLists.txt b/filament/backend/CMakeLists.txt index 9129099800f..3d35d38d22a 100644 --- a/filament/backend/CMakeLists.txt +++ b/filament/backend/CMakeLists.txt @@ -412,6 +412,7 @@ if (APPLE) test/test_RenderExternalImage.cpp test/test_StencilBuffer.cpp test/test_Scissor.cpp + test/test_MipLevels.cpp ) target_link_libraries(backend_test PRIVATE diff --git a/filament/backend/src/metal/MetalDriver.mm b/filament/backend/src/metal/MetalDriver.mm index c8b219cceb7..9fbba163bcd 100644 --- a/filament/backend/src/metal/MetalDriver.mm +++ b/filament/backend/src/metal/MetalDriver.mm @@ -345,7 +345,7 @@ auto colorTexture = handle_cast(buffer.handle); ASSERT_PRECONDITION(colorTexture->getMtlTextureForWrite(), "Color texture passed to render target has no texture allocation"); - colorTexture->updateLodRange(buffer.level); + colorTexture->extendLodRangeTo(buffer.level); colorAttachments[i] = { colorTexture, color[i].level, color[i].layer }; } @@ -356,7 +356,7 @@ auto depthTexture = handle_cast(depth.handle); ASSERT_PRECONDITION(depthTexture->getMtlTextureForWrite(), "Depth texture passed to render target has no texture allocation."); - depthTexture->updateLodRange(depth.level); + depthTexture->extendLodRangeTo(depth.level); depthAttachment = { depthTexture, depth.level, depth.layer }; } @@ -367,7 +367,7 @@ auto stencilTexture = handle_cast(stencil.handle); ASSERT_PRECONDITION(stencilTexture->getMtlTextureForWrite(), "Stencil texture passed to render target has no texture allocation."); - stencilTexture->updateLodRange(stencil.level); + stencilTexture->extendLodRangeTo(stencil.level); stencilAttachment = { stencilTexture, stencil.level, stencil.layer }; } @@ -790,7 +790,7 @@ void MetalDriver::setMinMaxLevels(Handle th, uint32_t minLevel, uint32_t maxLevel) { auto tex = handle_cast(th); - tex->updateLodRange(minLevel, maxLevel); + tex->setLodRange(minLevel, maxLevel); } void MetalDriver::update3DImage(Handle th, uint32_t level, diff --git a/filament/backend/src/metal/MetalHandles.h b/filament/backend/src/metal/MetalHandles.h index ec6da22d90a..f21891a701e 100644 --- a/filament/backend/src/metal/MetalHandles.h +++ b/filament/backend/src/metal/MetalHandles.h @@ -223,8 +223,8 @@ class MetalTexture : public HwTexture { // - using the texture as a render target attachment // - calling setMinMaxLevels // A texture's available mips are consistent throughout a render pass. - void updateLodRange(uint32_t level); - void updateLodRange(uint32_t minLevel, uint32_t maxLevel); + void setLodRange(uint32_t minLevel, uint32_t maxLevel); + void extendLodRangeTo(uint32_t level); static MTLPixelFormat decidePixelFormat(MetalContext* context, TextureFormat format); diff --git a/filament/backend/src/metal/MetalHandles.mm b/filament/backend/src/metal/MetalHandles.mm index 0b4d0b3c4dd..ed8a894ffb8 100644 --- a/filament/backend/src/metal/MetalHandles.mm +++ b/filament/backend/src/metal/MetalHandles.mm @@ -513,7 +513,7 @@ void presentDrawable(bool presentFrame, void* user) { : HwTexture(target, levels, samples, width, height, depth, format, usage), context(context), externalImage(context) { texture = metalTexture; - updateLodRange(0, levels - 1); + setLodRange(0, levels - 1); } MetalTexture::~MetalTexture() { @@ -658,14 +658,14 @@ void presentDrawable(bool presentFrame, void* user) { } } - updateLodRange(level); + extendLodRangeTo(level); } void MetalTexture::generateMipmaps() noexcept { id blitEncoder = [getPendingCommandBuffer(&context) blitCommandEncoder]; [blitEncoder generateMipmapsForTexture:texture]; [blitEncoder endEncoding]; - updateLodRange(0, texture.mipmapLevelCount - 1); + setLodRange(0, texture.mipmapLevelCount - 1); } void MetalTexture::loadSlice(uint32_t level, MTLRegion region, uint32_t byteOffset, uint32_t slice, @@ -788,18 +788,18 @@ void presentDrawable(bool presentFrame, void* user) { context.blitter->blit(getPendingCommandBuffer(&context), args, "Texture upload blit"); } -void MetalTexture::updateLodRange(uint32_t level) { +void MetalTexture::extendLodRangeTo(uint32_t level) { assert_invariant(!isInRenderPass(&context)); minLod = std::min(minLod, level); maxLod = std::max(maxLod, level); lodTextureView = nil; } -void MetalTexture::updateLodRange(uint32_t min, uint32_t max) { +void MetalTexture::setLodRange(uint32_t min, uint32_t max) { assert_invariant(!isInRenderPass(&context)); assert_invariant(min <= max); - minLod = std::min(minLod, min); - maxLod = std::max(maxLod, max); + minLod = min; + maxLod = max; lodTextureView = nil; } diff --git a/filament/backend/test/BackendTest.cpp b/filament/backend/test/BackendTest.cpp index c061d721f7f..70261e8bf83 100644 --- a/filament/backend/test/BackendTest.cpp +++ b/filament/backend/test/BackendTest.cpp @@ -211,66 +211,5 @@ int runTests() { return RUN_ALL_TESTS(); } -void getPixelInfo(PixelDataFormat format, PixelDataType type, size_t& outComponents, int& outBpp) { - assert_invariant(type != PixelDataType::COMPRESSED); - switch (format) { - case PixelDataFormat::UNUSED: - case PixelDataFormat::R: - case PixelDataFormat::R_INTEGER: - case PixelDataFormat::DEPTH_COMPONENT: - case PixelDataFormat::ALPHA: - outComponents = 1; - break; - case PixelDataFormat::RG: - case PixelDataFormat::RG_INTEGER: - case PixelDataFormat::DEPTH_STENCIL: - outComponents = 2; - break; - case PixelDataFormat::RGB: - case PixelDataFormat::RGB_INTEGER: - outComponents = 3; - break; - case PixelDataFormat::RGBA: - case PixelDataFormat::RGBA_INTEGER: - outComponents = 4; - break; - } - - outBpp = outComponents; - switch (type) { - case PixelDataType::COMPRESSED: // Impossible -- to squash the IDE warnings - case PixelDataType::UBYTE: - case PixelDataType::BYTE: - // nothing to do - break; - case PixelDataType::USHORT: - case PixelDataType::SHORT: - case PixelDataType::HALF: - outBpp *= 2; - break; - case PixelDataType::UINT: - case PixelDataType::INT: - case PixelDataType::FLOAT: - outBpp *= 4; - break; - case PixelDataType::UINT_10F_11F_11F_REV: - // Special case, format must be RGB and uses 4 bytes - assert_invariant(format == PixelDataFormat::RGB); - outBpp = 4; - break; - case PixelDataType::UINT_2_10_10_10_REV: - // Special case, format must be RGBA and uses 4 bytes - assert_invariant(format == PixelDataFormat::RGBA); - outBpp = 4; - break; - case PixelDataType::USHORT_565: - // Special case, format must be RGB and uses 2 bytes - assert_invariant(format == PixelDataFormat::RGB); - outBpp = 2; - break; - } -} - - } // namespace test diff --git a/filament/backend/test/BackendTest.h b/filament/backend/test/BackendTest.h index 933c076d679..777a96f0404 100644 --- a/filament/backend/test/BackendTest.h +++ b/filament/backend/test/BackendTest.h @@ -75,12 +75,6 @@ class BackendTest : public ::testing::Test { filament::backend::Handle uniform; }; - -// Utilities - -void getPixelInfo(filament::backend::PixelDataFormat format, filament::backend::PixelDataType type, - size_t& outComponents, int& outBpp); - } // namespace test #endif diff --git a/filament/backend/test/BackendTestUtils.h b/filament/backend/test/BackendTestUtils.h new file mode 100644 index 00000000000..209ea4abd46 --- /dev/null +++ b/filament/backend/test/BackendTestUtils.h @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#ifndef TNT_BACKENDTESTUTILS_H +#define TNT_BACKENDTESTUTILS_H + +#include + +#include + +using namespace filament; +using namespace filament::backend; + +inline void getPixelInfo(PixelDataFormat format, PixelDataType type, size_t& outComponents, int& outBpp) { + assert_invariant(type != PixelDataType::COMPRESSED); + switch (format) { + case PixelDataFormat::UNUSED: + case PixelDataFormat::R: + case PixelDataFormat::R_INTEGER: + case PixelDataFormat::DEPTH_COMPONENT: + case PixelDataFormat::ALPHA: + outComponents = 1; + break; + case PixelDataFormat::RG: + case PixelDataFormat::RG_INTEGER: + case PixelDataFormat::DEPTH_STENCIL: + outComponents = 2; + break; + case PixelDataFormat::RGB: + case PixelDataFormat::RGB_INTEGER: + outComponents = 3; + break; + case PixelDataFormat::RGBA: + case PixelDataFormat::RGBA_INTEGER: + outComponents = 4; + break; + } + + outBpp = outComponents; + switch (type) { + case PixelDataType::COMPRESSED: // Impossible -- to squash the IDE warnings + case PixelDataType::UBYTE: + case PixelDataType::BYTE: + // nothing to do + break; + case PixelDataType::USHORT: + case PixelDataType::SHORT: + case PixelDataType::HALF: + outBpp *= 2; + break; + case PixelDataType::UINT: + case PixelDataType::INT: + case PixelDataType::FLOAT: + outBpp *= 4; + break; + case PixelDataType::UINT_10F_11F_11F_REV: + // Special case, format must be RGB and uses 4 bytes + assert_invariant(format == PixelDataFormat::RGB); + outBpp = 4; + break; + case PixelDataType::UINT_2_10_10_10_REV: + // Special case, format must be RGBA and uses 4 bytes + assert_invariant(format == PixelDataFormat::RGBA); + outBpp = 4; + break; + case PixelDataType::USHORT_565: + // Special case, format must be RGB and uses 2 bytes + assert_invariant(format == PixelDataFormat::RGB); + outBpp = 2; + break; + } +} + +template +static void fillCheckerboard(void* buffer, size_t size, size_t stride, size_t components, + ComponentType value) { + ComponentType* row = (ComponentType*)buffer; + int p = 0; + for (int r = 0; r < size; r++) { + ComponentType* pixel = row; + for (int col = 0; col < size; col++) { + // Generate a checkerboard pattern. + if ((p & 0x0010) ^ ((p / size) & 0x0010)) { + // Turn on the first component (red). + pixel[0] = value; + } + pixel += components; + p++; + } + row += stride * components; + } +} + +static PixelBufferDescriptor checkerboardPixelBuffer(PixelDataFormat format, PixelDataType type, + size_t size, size_t bufferPadding = 0) { + size_t components; int bpp; + getPixelInfo(format, type, components, bpp); + + size_t bufferSize = size + bufferPadding * 2; + uint8_t* buffer = (uint8_t*) calloc(1, bufferSize * bufferSize * bpp); + + uint8_t* ptr = buffer + (bufferSize * bufferPadding * bpp) + (bufferPadding * bpp); + + switch (type) { + case PixelDataType::BYTE: + fillCheckerboard(ptr, size, bufferSize, components, 1); + break; + + case PixelDataType::UBYTE: + fillCheckerboard(ptr, size, bufferSize, components, 0xFF); + break; + + case PixelDataType::SHORT: + fillCheckerboard(ptr, size, bufferSize, components, 1); + break; + + case PixelDataType::USHORT: + fillCheckerboard(ptr, size, bufferSize, components, 1u); + break; + + case PixelDataType::UINT: + fillCheckerboard(ptr, size, bufferSize, components, 1u); + break; + + case PixelDataType::INT: + fillCheckerboard(ptr, size, bufferSize, components, 1); + break; + + case PixelDataType::FLOAT: + fillCheckerboard(ptr, size, bufferSize, components, 1.0f); + break; + + case PixelDataType::HALF: + fillCheckerboard(ptr, size, bufferSize, components, math::half(1.0f)); + break; + + case PixelDataType::UINT_2_10_10_10_REV: + fillCheckerboard(ptr, size, bufferSize, 1, 0xC00003FF /* red */); + break; + + case PixelDataType::USHORT_565: + fillCheckerboard(ptr, size, bufferSize, 1, 0xF800 /* red */); + break; + + case PixelDataType::UINT_10F_11F_11F_REV: + fillCheckerboard(ptr, size, bufferSize, 1, 0x000003C0 /* red */); + break; + + case PixelDataType::COMPRESSED: + break; + } + + PixelBufferDescriptor descriptor(buffer, bufferSize * bufferSize * bpp, format, type, + 1, bufferPadding, bufferPadding, bufferSize, [](void* buffer, size_t size, void* user) { + free(buffer); + }, nullptr); + return descriptor; +} + +#endif // TNT_BACKENDTESTUTILS_H diff --git a/filament/backend/test/test_LoadImage.cpp b/filament/backend/test/test_LoadImage.cpp index f4797f7aae0..ddfab69a8a8 100644 --- a/filament/backend/test/test_LoadImage.cpp +++ b/filament/backend/test/test_LoadImage.cpp @@ -18,6 +18,7 @@ #include "ShaderGenerator.h" #include "TrianglePrimitive.h" +#include "BackendTestUtils.h" #include "private/filament/SamplerInterfaceBlock.h" #include "private/backend/SamplerGroup.h" @@ -113,91 +114,7 @@ namespace test { template inline componentType getMaxValue(); -template -static void fillCheckerboard(void* buffer, size_t size, size_t stride, size_t components, - ComponentType value) { - ComponentType* row = (ComponentType*)buffer; - int p = 0; - for (int r = 0; r < size; r++) { - ComponentType* pixel = row; - for (int col = 0; col < size; col++) { - // Generate a checkerboard pattern. - if ((p & 0x0010) ^ ((p / size) & 0x0010)) { - // Turn on the first component (red). - pixel[0] = value; - } - pixel += components; - p++; - } - row += stride * components; - } -} - -static PixelBufferDescriptor checkerboardPixelBuffer(PixelDataFormat format, PixelDataType type, - size_t size, size_t bufferPadding = 0) { - size_t components; int bpp; - getPixelInfo(format, type, components, bpp); - - size_t bufferSize = size + bufferPadding * 2; - uint8_t* buffer = (uint8_t*) calloc(1, bufferSize * bufferSize * bpp); - - uint8_t* ptr = buffer + (bufferSize * bufferPadding * bpp) + (bufferPadding * bpp); - - switch (type) { - case PixelDataType::BYTE: - fillCheckerboard(ptr, size, bufferSize, components, 1); - break; - - case PixelDataType::UBYTE: - fillCheckerboard(ptr, size, bufferSize, components, 0xFF); - break; - - case PixelDataType::SHORT: - fillCheckerboard(ptr, size, bufferSize, components, 1); - break; - - case PixelDataType::USHORT: - fillCheckerboard(ptr, size, bufferSize, components, 1u); - break; - case PixelDataType::UINT: - fillCheckerboard(ptr, size, bufferSize, components, 1u); - break; - - case PixelDataType::INT: - fillCheckerboard(ptr, size, bufferSize, components, 1); - break; - - case PixelDataType::FLOAT: - fillCheckerboard(ptr, size, bufferSize, components, 1.0f); - break; - - case PixelDataType::HALF: - fillCheckerboard(ptr, size, bufferSize, components, math::half(1.0f)); - break; - - case PixelDataType::UINT_2_10_10_10_REV: - fillCheckerboard(ptr, size, bufferSize, 1, 0xC00003FF /* red */); - break; - - case PixelDataType::USHORT_565: - fillCheckerboard(ptr, size, bufferSize, 1, 0xF800 /* red */); - break; - - case PixelDataType::UINT_10F_11F_11F_REV: - fillCheckerboard(ptr, size, bufferSize, 1, 0x000003C0 /* red */); - break; - - case PixelDataType::COMPRESSED: - break; - } - - PixelBufferDescriptor descriptor(buffer, bufferSize * bufferSize * bpp, format, type, - 1, bufferPadding, bufferPadding, bufferSize, [](void* buffer, size_t size, void* user) { - free(buffer); - }, nullptr); - return descriptor; -} inline std::string stringReplace(const std::string& find, const std::string& replace, std::string source) { diff --git a/filament/backend/test/test_MipLevels.cpp b/filament/backend/test/test_MipLevels.cpp new file mode 100644 index 00000000000..e677e904d5b --- /dev/null +++ b/filament/backend/test/test_MipLevels.cpp @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "BackendTest.h" + +#include "ShaderGenerator.h" +#include "TrianglePrimitive.h" +#include "BackendTestUtils.h" + +#include "private/backend/SamplerGroup.h" + +namespace { + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Shaders +//////////////////////////////////////////////////////////////////////////////////////////////////// + +std::string vertex (R"(#version 450 core + +layout(location = 0) in vec4 mesh_position; +layout(location = 0) out vec2 uv; + +void main() { + gl_Position = vec4(mesh_position.xy, 0.0, 1.0); + uv = (mesh_position.xy * 0.5 + 0.5); +} +)"); + +std::string fragment (R"(#version 450 core + +layout(location = 0) out vec4 fragColor; +layout(location = 0) in vec2 uv; + +layout(location = 0, set = 1) uniform sampler2D backend_test_sib_tex; + +void main() { + fragColor = textureLod(backend_test_sib_tex, uv, 1); +} +)"); + +} + +namespace test { + +using namespace filament; +using namespace filament::backend; + +TEST_F(BackendTest, SetMinMaxLevel) { + auto& api = getDriverApi(); + api.startCapture(0); + + // The test is executed within this block scope to force destructors to run before + // executeCommands(). + { + // Create a SwapChain and make it current. + auto swapChain = createSwapChain(); + api.makeCurrent(swapChain, swapChain); + + // Create a program that samples a texture. + SamplerInterfaceBlock sib = filament::SamplerInterfaceBlock::Builder() + .name("backend_test_sib") + .stageFlags(backend::ShaderStageFlags::FRAGMENT) + .add( {{"tex", SamplerType::SAMPLER_2D, SamplerFormat::FLOAT, Precision::HIGH }} ) + .build(); + ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform, &sib); + Program p = shaderGen.getProgram(api); + Program::Sampler sampler { utils::CString("backend_test_sib_tex"), 0 }; + p.setSamplerGroup(0, ShaderStageFlags::FRAGMENT, &sampler, 1); + auto program = api.createProgram(std::move(p)); + + // Create a texture that has 4 mip levels. Each level is a different color. + // Level 0: 128x128 (red) + // Level 1: 64x64 (green) + // Level 2: 32x32 (blue) + // Level 3: 16x16 (yellow) + const size_t kTextureSize = 128; + const size_t kMipLevels = 4; + Handle texture = api.createTexture(SamplerType::SAMPLER_2D, kMipLevels, + TextureFormat::RGBA8, 1, kTextureSize, kTextureSize, 1, + TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE); + + // Create image data. + auto pixelFormat = PixelDataFormat::RGBA; + auto pixelType = PixelDataType::UBYTE; + size_t components; int bpp; + getPixelInfo(pixelFormat, pixelType, components, bpp); + uint32_t colors[] = { + 0xFF0000FF, /* red */ + 0xFF00FF00, /* green */ + 0xFFFF0000, /* blue */ + 0xFF00FFFF, /* yellow */ + }; + for (int l = 0; l < kMipLevels; l++) { + size_t mipSize = kTextureSize >> l; + auto* buffer = (uint8_t*)calloc(1, mipSize * mipSize * bpp); + fillCheckerboard(buffer, mipSize, mipSize, 1, colors[l]); + PixelBufferDescriptor descriptor( + buffer, mipSize * mipSize * bpp, pixelFormat, pixelType, 1, 0, 0, + mipSize, [](void* buffer, size_t size, void* user) { free(buffer); }, + nullptr); + api.update3DImage( + texture, l, 0, 0, 0, mipSize, mipSize, 1, std::move(descriptor)); + } + + api.setMinMaxLevels(texture, 1, 3); + + backend::Handle defaultRenderTarget = api.createDefaultRenderTarget(0); + + RenderPassParams params = {}; + fullViewport(params); + params.flags.clear = TargetBufferFlags::COLOR; + params.clearColor = {0.f, 0.f, 0.5f, 1.f}; + params.flags.discardStart = TargetBufferFlags::ALL; + params.flags.discardEnd = TargetBufferFlags::NONE; + + PipelineState state; + state.scissor = params.viewport; + state.program = program; + state.rasterState.colorWrite = true; + state.rasterState.depthWrite = false; + state.rasterState.depthFunc = SamplerCompareFunc::A; + state.rasterState.culling = CullingMode::NONE; + + api.beginFrame(0, 0); + + SamplerGroup samplers(1); + SamplerParams samplerParams {}; + samplerParams.filterMag = SamplerMagFilter::NEAREST; + samplerParams.filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST; + samplers.setSampler(0, { texture, samplerParams }); + backend::Handle samplerGroup = api.createSamplerGroup(1); + api.updateSamplerGroup(samplerGroup, samplers.toBufferDescriptor(api)); + api.bindSamplers(0, samplerGroup); + + // Render a triangle to the screen, sampling from mip level 1. + // Because the min level is 1, the result color should be blue. + TrianglePrimitive triangle(api); + api.beginRenderPass(defaultRenderTarget, params); + api.draw(state, triangle.getRenderPrimitive(), 1); + api.endRenderPass(); + + api.commit(swapChain); + api.endFrame(0); + + api.stopCapture(0); + + // Cleanup. + api.destroySwapChain(swapChain); + } + + api.finish(); + + executeCommands(); + getDriver().purge(); +} + +} // namespace test \ No newline at end of file diff --git a/filament/backend/test/test_ReadPixels.cpp b/filament/backend/test/test_ReadPixels.cpp index e824792602d..34b2a12337e 100644 --- a/filament/backend/test/test_ReadPixels.cpp +++ b/filament/backend/test/test_ReadPixels.cpp @@ -18,6 +18,7 @@ #include "ShaderGenerator.h" #include "TrianglePrimitive.h" +#include "BackendTestUtils.h" #include