diff --git a/impeller/renderer/backend/gles/blit_command_gles.cc b/impeller/renderer/backend/gles/blit_command_gles.cc index 2dde4c9741381..14016f7123b0f 100644 --- a/impeller/renderer/backend/gles/blit_command_gles.cc +++ b/impeller/renderer/backend/gles/blit_command_gles.cc @@ -6,6 +6,7 @@ #include "flutter/fml/closure.h" #include "impeller/base/validation.h" +#include "impeller/renderer/backend/gles/device_buffer_gles.h" #include "impeller/renderer/backend/gles/texture_gles.h" namespace impeller { @@ -116,6 +117,44 @@ bool BlitCopyTextureToTextureCommandGLES::Encode( return true; }; +BlitCopyTextureToBufferCommandGLES::~BlitCopyTextureToBufferCommandGLES() = + default; + +std::string BlitCopyTextureToBufferCommandGLES::GetLabel() const { + return label; +} + +bool BlitCopyTextureToBufferCommandGLES::Encode( + const ReactorGLES& reactor) const { + if (source->GetTextureDescriptor().format != PixelFormat::kR8G8B8A8UNormInt) { + VALIDATION_LOG << "Only textures with pixel format RGBA are supported yet."; + return false; + } + + const auto& gl = reactor.GetProcTable(); + + GLuint read_fbo = GL_NONE; + fml::ScopedCleanupClosure delete_fbos( + [&gl, &read_fbo]() { DeleteFBO(gl, read_fbo, GL_READ_FRAMEBUFFER); }); + + { + auto read = ConfigureFBO(gl, source, GL_READ_FRAMEBUFFER); + if (!read.has_value()) { + return false; + } + read_fbo = read.value(); + } + + DeviceBufferGLES::Cast(*destination) + .UpdateBufferData([&gl, this](uint8_t* data, size_t length) { + gl.ReadPixels(source_region.origin.x, source_region.origin.y, + source_region.size.width, source_region.size.height, + GL_RGBA, GL_UNSIGNED_BYTE, data + destination_offset); + }); + + return true; +}; + BlitGenerateMipmapCommandGLES::~BlitGenerateMipmapCommandGLES() = default; std::string BlitGenerateMipmapCommandGLES::GetLabel() const { diff --git a/impeller/renderer/backend/gles/blit_command_gles.h b/impeller/renderer/backend/gles/blit_command_gles.h index 2fd57fbe1e1ab..570b1f19d30fb 100644 --- a/impeller/renderer/backend/gles/blit_command_gles.h +++ b/impeller/renderer/backend/gles/blit_command_gles.h @@ -29,6 +29,16 @@ struct BlitCopyTextureToTextureCommandGLES [[nodiscard]] bool Encode(const ReactorGLES& reactor) const override; }; +struct BlitCopyTextureToBufferCommandGLES + : public BlitEncodeGLES, + public BlitCopyTextureToBufferCommand { + ~BlitCopyTextureToBufferCommandGLES() override; + + std::string GetLabel() const override; + + [[nodiscard]] bool Encode(const ReactorGLES& reactor) const override; +}; + struct BlitGenerateMipmapCommandGLES : public BlitEncodeGLES, public BlitGenerateMipmapCommand { ~BlitGenerateMipmapCommandGLES() override; diff --git a/impeller/renderer/backend/gles/blit_pass_gles.cc b/impeller/renderer/backend/gles/blit_pass_gles.cc index 07d6c732bda00..8685d86aa4a29 100644 --- a/impeller/renderer/backend/gles/blit_pass_gles.cc +++ b/impeller/renderer/backend/gles/blit_pass_gles.cc @@ -95,7 +95,7 @@ bool BlitPassGLES::EncodeCommands( } // |BlitPass| -void BlitPassGLES::OnCopyTextureToTextureCommand( +bool BlitPassGLES::OnCopyTextureToTextureCommand( std::shared_ptr source, std::shared_ptr destination, IRect source_region, @@ -109,16 +109,36 @@ void BlitPassGLES::OnCopyTextureToTextureCommand( command->destination_origin = destination_origin; commands_.emplace_back(std::move(command)); + return true; } // |BlitPass| -void BlitPassGLES::OnGenerateMipmapCommand(std::shared_ptr texture, +bool BlitPassGLES::OnCopyTextureToBufferCommand( + std::shared_ptr source, + std::shared_ptr destination, + IRect source_region, + size_t destination_offset, + std::string label) { + auto command = std::make_unique(); + command->label = label; + command->source = std::move(source); + command->destination = std::move(destination); + command->source_region = source_region; + command->destination_offset = destination_offset; + + commands_.emplace_back(std::move(command)); + return true; +} + +// |BlitPass| +bool BlitPassGLES::OnGenerateMipmapCommand(std::shared_ptr texture, std::string label) { auto command = std::make_unique(); command->label = label; command->texture = std::move(texture); commands_.emplace_back(std::move(command)); + return true; } } // namespace impeller diff --git a/impeller/renderer/backend/gles/blit_pass_gles.h b/impeller/renderer/backend/gles/blit_pass_gles.h index 2268f5dd542ff..deaf1a41835f4 100644 --- a/impeller/renderer/backend/gles/blit_pass_gles.h +++ b/impeller/renderer/backend/gles/blit_pass_gles.h @@ -37,14 +37,21 @@ class BlitPassGLES final : public BlitPass { const std::shared_ptr& transients_allocator) const override; // |BlitPass| - void OnCopyTextureToTextureCommand(std::shared_ptr source, + bool OnCopyTextureToTextureCommand(std::shared_ptr source, std::shared_ptr destination, IRect source_region, IPoint destination_origin, std::string label) override; // |BlitPass| - void OnGenerateMipmapCommand(std::shared_ptr texture, + bool OnCopyTextureToBufferCommand(std::shared_ptr source, + std::shared_ptr destination, + IRect source_region, + size_t destination_offset, + std::string label) override; + + // |BlitPass| + bool OnGenerateMipmapCommand(std::shared_ptr texture, std::string label) override; FML_DISALLOW_COPY_AND_ASSIGN(BlitPassGLES); diff --git a/impeller/renderer/backend/gles/device_buffer_gles.cc b/impeller/renderer/backend/gles/device_buffer_gles.cc index 54a7d1fb10202..0ba29c1b67e31 100644 --- a/impeller/renderer/backend/gles/device_buffer_gles.cc +++ b/impeller/renderer/backend/gles/device_buffer_gles.cc @@ -109,4 +109,15 @@ bool DeviceBufferGLES::SetLabel(const std::string& label, Range range) { const uint8_t* DeviceBufferGLES::GetBufferData() const { return backing_store_->GetBuffer(); } + +void DeviceBufferGLES::UpdateBufferData( + const std::function& + update_buffer_data) { + if (update_buffer_data) { + update_buffer_data(backing_store_->GetBuffer(), + backing_store_->GetLength()); + ++generation_; + } +} + } // namespace impeller diff --git a/impeller/renderer/backend/gles/device_buffer_gles.h b/impeller/renderer/backend/gles/device_buffer_gles.h index fe572d7e3590a..70700040b0770 100644 --- a/impeller/renderer/backend/gles/device_buffer_gles.h +++ b/impeller/renderer/backend/gles/device_buffer_gles.h @@ -27,6 +27,9 @@ class DeviceBufferGLES final const uint8_t* GetBufferData() const; + void UpdateBufferData( + const std::function& update_buffer_data); + enum class BindingType { kArrayBuffer, kElementArrayBuffer, diff --git a/impeller/renderer/backend/gles/proc_table_gles.h b/impeller/renderer/backend/gles/proc_table_gles.h index fc113e3697af0..6fb00e83c4a5a 100644 --- a/impeller/renderer/backend/gles/proc_table_gles.h +++ b/impeller/renderer/backend/gles/proc_table_gles.h @@ -162,7 +162,8 @@ struct GLProc { PROC(UniformMatrix4fv); \ PROC(UseProgram); \ PROC(VertexAttribPointer); \ - PROC(Viewport); + PROC(Viewport); \ + PROC(ReadPixels); #define FOR_EACH_IMPELLER_GLES3_PROC(PROC) PROC(BlitFramebuffer); diff --git a/impeller/renderer/backend/metal/blit_command_mtl.h b/impeller/renderer/backend/metal/blit_command_mtl.h index e502cdd2c8d46..ffdd450f16c9b 100644 --- a/impeller/renderer/backend/metal/blit_command_mtl.h +++ b/impeller/renderer/backend/metal/blit_command_mtl.h @@ -31,6 +31,16 @@ struct BlitCopyTextureToTextureCommandMTL [[nodiscard]] bool Encode(id encoder) const override; }; +struct BlitCopyTextureToBufferCommandMTL + : public BlitCopyTextureToBufferCommand, + public BlitEncodeMTL { + ~BlitCopyTextureToBufferCommandMTL() override; + + std::string GetLabel() const override; + + [[nodiscard]] bool Encode(id encoder) const override; +}; + struct BlitGenerateMipmapCommandMTL : public BlitGenerateMipmapCommand, public BlitEncodeMTL { ~BlitGenerateMipmapCommandMTL() override; diff --git a/impeller/renderer/backend/metal/blit_command_mtl.mm b/impeller/renderer/backend/metal/blit_command_mtl.mm index c3704a951b5a0..7f589b4f54175 100644 --- a/impeller/renderer/backend/metal/blit_command_mtl.mm +++ b/impeller/renderer/backend/metal/blit_command_mtl.mm @@ -4,6 +4,7 @@ #include "impeller/renderer/backend/metal/blit_command_mtl.h" +#include "impeller/renderer/backend/metal/device_buffer_mtl.h" #include "impeller/renderer/backend/metal/texture_mtl.h" namespace impeller { @@ -49,6 +50,50 @@ return true; }; +BlitCopyTextureToBufferCommandMTL::~BlitCopyTextureToBufferCommandMTL() = + default; + +std::string BlitCopyTextureToBufferCommandMTL::GetLabel() const { + return label; +} + +bool BlitCopyTextureToBufferCommandMTL::Encode( + id encoder) const { + auto source_mtl = TextureMTL::Cast(*source).GetMTLTexture(); + if (!source_mtl) { + return false; + } + + auto destination_mtl = DeviceBufferMTL::Cast(*destination).GetMTLBuffer(); + if (!destination_mtl) { + return false; + } + + auto source_origin_mtl = + MTLOriginMake(source_region.origin.x, source_region.origin.y, 0); + auto source_size_mtl = + MTLSizeMake(source_region.size.width, source_region.size.height, 1); + + auto destination_bytes_per_pixel = + BytesPerPixelForPixelFormat(source->GetTextureDescriptor().format); + auto destination_bytes_per_row = + source_size_mtl.width * destination_bytes_per_pixel; + auto destination_bytes_per_image = + source_size_mtl.height * destination_bytes_per_row; + + [encoder copyFromTexture:source_mtl + sourceSlice:0 + sourceLevel:0 + sourceOrigin:source_origin_mtl + sourceSize:source_size_mtl + toBuffer:destination_mtl + destinationOffset:destination_offset + destinationBytesPerRow:destination_bytes_per_row + destinationBytesPerImage:destination_bytes_per_image]; + + return true; +}; + BlitGenerateMipmapCommandMTL::~BlitGenerateMipmapCommandMTL() = default; std::string BlitGenerateMipmapCommandMTL::GetLabel() const { diff --git a/impeller/renderer/backend/metal/blit_pass_mtl.h b/impeller/renderer/backend/metal/blit_pass_mtl.h index 4c9b41bbabded..108d320d98bf0 100644 --- a/impeller/renderer/backend/metal/blit_pass_mtl.h +++ b/impeller/renderer/backend/metal/blit_pass_mtl.h @@ -40,14 +40,21 @@ class BlitPassMTL final : public BlitPass { bool EncodeCommands(id pass) const; // |BlitPass| - void OnCopyTextureToTextureCommand(std::shared_ptr source, + bool OnCopyTextureToTextureCommand(std::shared_ptr source, std::shared_ptr destination, IRect source_region, IPoint destination_origin, std::string label) override; // |BlitPass| - void OnGenerateMipmapCommand(std::shared_ptr texture, + bool OnCopyTextureToBufferCommand(std::shared_ptr source, + std::shared_ptr destination, + IRect source_region, + size_t destination_offset, + std::string label) override; + + // |BlitPass| + bool OnGenerateMipmapCommand(std::shared_ptr texture, std::string label) override; FML_DISALLOW_COPY_AND_ASSIGN(BlitPassMTL); diff --git a/impeller/renderer/backend/metal/blit_pass_mtl.mm b/impeller/renderer/backend/metal/blit_pass_mtl.mm index d65cc544ce367..8de720677c3c6 100644 --- a/impeller/renderer/backend/metal/blit_pass_mtl.mm +++ b/impeller/renderer/backend/metal/blit_pass_mtl.mm @@ -88,7 +88,7 @@ } // |BlitPass| -void BlitPassMTL::OnCopyTextureToTextureCommand( +bool BlitPassMTL::OnCopyTextureToTextureCommand( std::shared_ptr source, std::shared_ptr destination, IRect source_region, @@ -102,16 +102,36 @@ command->destination_origin = destination_origin; commands_.emplace_back(std::move(command)); + return true; } // |BlitPass| -void BlitPassMTL::OnGenerateMipmapCommand(std::shared_ptr texture, +bool BlitPassMTL::OnCopyTextureToBufferCommand( + std::shared_ptr source, + std::shared_ptr destination, + IRect source_region, + size_t destination_offset, + std::string label) { + auto command = std::make_unique(); + command->label = label; + command->source = std::move(source); + command->destination = std::move(destination); + command->source_region = source_region; + command->destination_offset = destination_offset; + + commands_.emplace_back(std::move(command)); + return true; +} + +// |BlitPass| +bool BlitPassMTL::OnGenerateMipmapCommand(std::shared_ptr texture, std::string label) { auto command = std::make_unique(); command->label = label; command->texture = std::move(texture); commands_.emplace_back(std::move(command)); + return true; } } // namespace impeller diff --git a/impeller/renderer/backend/vulkan/blit_pass_vk.h b/impeller/renderer/backend/vulkan/blit_pass_vk.h index f05d878c725ec..a76e5a71ecb0b 100644 --- a/impeller/renderer/backend/vulkan/blit_pass_vk.h +++ b/impeller/renderer/backend/vulkan/blit_pass_vk.h @@ -30,14 +30,21 @@ class BlitPassVK final : public BlitPass { const std::shared_ptr& transients_allocator) const override; // |BlitPass| - void OnCopyTextureToTextureCommand(std::shared_ptr source, + bool OnCopyTextureToTextureCommand(std::shared_ptr source, std::shared_ptr destination, IRect source_region, IPoint destination_origin, std::string label) override; // |BlitPass| - void OnGenerateMipmapCommand(std::shared_ptr texture, + bool OnCopyTextureToBufferCommand(std::shared_ptr source, + std::shared_ptr destination, + IRect source_region, + size_t destination_offset, + std::string label) override; + + // |BlitPass| + bool OnGenerateMipmapCommand(std::shared_ptr texture, std::string label) override; FML_DISALLOW_COPY_AND_ASSIGN(BlitPassVK); diff --git a/impeller/renderer/blit_command.h b/impeller/renderer/blit_command.h index 289e95a35f72c..7fc646aa53248 100644 --- a/impeller/renderer/blit_command.h +++ b/impeller/renderer/blit_command.h @@ -4,6 +4,8 @@ #pragma once +#include "impeller/geometry/rect.h" +#include "impeller/renderer/device_buffer.h" #include "impeller/renderer/texture.h" namespace impeller { @@ -19,6 +21,13 @@ struct BlitCopyTextureToTextureCommand : public BlitCommand { IPoint destination_origin; }; +struct BlitCopyTextureToBufferCommand : public BlitCommand { + std::shared_ptr source; + std::shared_ptr destination; + IRect source_region; + size_t destination_offset; +}; + struct BlitGenerateMipmapCommand : public BlitCommand { std::shared_ptr texture; }; diff --git a/impeller/renderer/blit_pass.cc b/impeller/renderer/blit_pass.cc index c11c270c3bba9..dd53e125645b5 100644 --- a/impeller/renderer/blit_pass.cc +++ b/impeller/renderer/blit_pass.cc @@ -71,10 +71,49 @@ bool BlitPass::AddCopy(std::shared_ptr source, return true; // Nothing to blit. } - OnCopyTextureToTextureCommand(std::move(source), std::move(destination), - source_region.value(), destination_origin, - std::move(label)); - return true; + return OnCopyTextureToTextureCommand( + std::move(source), std::move(destination), source_region.value(), + destination_origin, std::move(label)); +} + +bool BlitPass::AddCopy(std::shared_ptr source, + std::shared_ptr destination, + std::optional source_region, + size_t destination_offset, + std::string label) { + if (!source) { + VALIDATION_LOG << "Attempted to add a texture blit with no source."; + return false; + } + if (!destination) { + VALIDATION_LOG << "Attempted to add a texture blit with no destination."; + return false; + } + + if (!source_region.has_value()) { + source_region = IRect::MakeSize(source->GetSize()); + } + + auto bytes_per_pixel = + BytesPerPixelForPixelFormat(source->GetTextureDescriptor().format); + auto bytes_per_image = source_region->size.Area() * bytes_per_pixel; + if (destination_offset + bytes_per_image > + destination->GetDeviceBufferDescriptor().size) { + VALIDATION_LOG + << "Attempted to add a texture blit with out fo bounds access."; + return false; + } + + // Clip the source image. + source_region = + source_region->Intersection(IRect::MakeSize(source->GetSize())); + if (!source_region.has_value()) { + return true; // Nothing to blit. + } + + return OnCopyTextureToBufferCommand(std::move(source), std::move(destination), + source_region.value(), destination_offset, + std::move(label)); } bool BlitPass::GenerateMipmap(std::shared_ptr texture, @@ -85,8 +124,7 @@ bool BlitPass::GenerateMipmap(std::shared_ptr texture, return false; } - OnGenerateMipmapCommand(std::move(texture), std::move(label)); - return true; + return OnGenerateMipmapCommand(std::move(texture), std::move(label)); } } // namespace impeller diff --git a/impeller/renderer/blit_pass.h b/impeller/renderer/blit_pass.h index e5fd8e0bb054e..7ac7b820e5972 100644 --- a/impeller/renderer/blit_pass.h +++ b/impeller/renderer/blit_pass.h @@ -8,6 +8,7 @@ #include #include "impeller/renderer/blit_command.h" +#include "impeller/renderer/device_buffer.h" #include "impeller/renderer/texture.h" namespace impeller { @@ -59,6 +60,30 @@ class BlitPass { IPoint destination_origin = {}, std::string label = ""); + //---------------------------------------------------------------------------- + /// @brief Record a command to copy the contents of the texture to + /// the buffer. + /// No work is encoded into the command buffer at this time. + /// + /// @param[in] source The texture to read for copying. + /// @param[in] destination The buffer to overwrite using the source + /// contents. + /// @param[in] source_region The optional region of the source texture + /// to use for copying. If not specified, the + /// full size of the source texture is used. + /// @param[in] destination_offset The offset to start writing to in the + /// destination buffer. + /// @param[in] label The optional debug label to give the + /// command. + /// + /// @return If the command was valid for subsequent commitment. + /// + bool AddCopy(std::shared_ptr source, + std::shared_ptr destination, + std::optional source_region = std::nullopt, + size_t destination_offset = 0, + std::string label = ""); + //---------------------------------------------------------------------------- /// @brief Record a command to generate all mip levels for a texture. /// No work is encoded into the command buffer at this time. @@ -88,14 +113,21 @@ class BlitPass { virtual void OnSetLabel(std::string label) = 0; - virtual void OnCopyTextureToTextureCommand( + virtual bool OnCopyTextureToTextureCommand( std::shared_ptr source, std::shared_ptr destination, IRect source_region, IPoint destination_origin, std::string label) = 0; - virtual void OnGenerateMipmapCommand(std::shared_ptr texture, + virtual bool OnCopyTextureToBufferCommand( + std::shared_ptr source, + std::shared_ptr destination, + IRect source_region, + size_t destination_offset, + std::string label) = 0; + + virtual bool OnGenerateMipmapCommand(std::shared_ptr texture, std::string label) = 0; private: diff --git a/impeller/renderer/device_buffer.cc b/impeller/renderer/device_buffer.cc index 6bb706d8bb98b..08daf8c8b655a 100644 --- a/impeller/renderer/device_buffer.cc +++ b/impeller/renderer/device_buffer.cc @@ -39,6 +39,10 @@ std::shared_ptr DeviceBuffer::AsTexture( return texture; } +const DeviceBufferDescriptor& DeviceBuffer::GetDeviceBufferDescriptor() const { + return desc_; +} + [[nodiscard]] bool DeviceBuffer::CopyHostBuffer(const uint8_t* source, Range source_range, size_t offset) { diff --git a/impeller/renderer/device_buffer.h b/impeller/renderer/device_buffer.h index e86fa2a7e7f93..a1ae3a041fc86 100644 --- a/impeller/renderer/device_buffer.h +++ b/impeller/renderer/device_buffer.h @@ -41,6 +41,8 @@ class DeviceBuffer : public Buffer, std::shared_ptr GetDeviceBuffer( Allocator& allocator) const; + const DeviceBufferDescriptor& GetDeviceBufferDescriptor() const; + protected: const DeviceBufferDescriptor desc_; diff --git a/impeller/renderer/renderer_unittests.cc b/impeller/renderer/renderer_unittests.cc index 4a1a52ca02bfe..cb84c3660a6c9 100644 --- a/impeller/renderer/renderer_unittests.cc +++ b/impeller/renderer/renderer_unittests.cc @@ -26,6 +26,7 @@ #include "impeller/playground/playground_test.h" #include "impeller/renderer/command.h" #include "impeller/renderer/command_buffer.h" +#include "impeller/renderer/device_buffer_descriptor.h" #include "impeller/renderer/formats.h" #include "impeller/renderer/pipeline_builder.h" #include "impeller/renderer/pipeline_library.h" @@ -556,6 +557,137 @@ TEST_P(RendererTest, CanBlitTextureToTexture) { OpenPlaygroundHere(callback); } +TEST_P(RendererTest, CanBlitTextureToBuffer) { + auto context = GetContext(); + ASSERT_TRUE(context); + + using VS = MipmapsVertexShader; + using FS = MipmapsFragmentShader; + auto desc = PipelineBuilder::MakeDefaultPipelineDescriptor(*context); + ASSERT_TRUE(desc.has_value()); + desc->SetSampleCount(SampleCount::kCount4); + auto mipmaps_pipeline = + context->GetPipelineLibrary()->GetPipeline(std::move(desc)).get(); + ASSERT_TRUE(mipmaps_pipeline); + + auto bridge = CreateTextureForFixture("bay_bridge.jpg"); + auto boston = CreateTextureForFixture("boston.jpg"); + ASSERT_TRUE(bridge && boston); + auto sampler = context->GetSamplerLibrary()->GetSampler({}); + ASSERT_TRUE(sampler); + + TextureDescriptor texture_desc; + texture_desc.storage_mode = StorageMode::kHostVisible; + texture_desc.format = PixelFormat::kR8G8B8A8UNormInt; + texture_desc.size = bridge->GetTextureDescriptor().size; + texture_desc.mip_count = 1u; + texture_desc.usage = + static_cast(TextureUsage::kRenderTarget) | + static_cast(TextureUsage::kShaderWrite) | + static_cast(TextureUsage::kShaderRead); + DeviceBufferDescriptor device_buffer_desc; + device_buffer_desc.storage_mode = StorageMode::kHostVisible; + device_buffer_desc.size = + bridge->GetTextureDescriptor().GetByteSizeOfBaseMipLevel(); + auto device_buffer = + context->GetResourceAllocator()->CreateBuffer(device_buffer_desc); + + // Vertex buffer. + VertexBufferBuilder vertex_builder; + vertex_builder.SetLabel("Box"); + auto size = Point(boston->GetSize()); + vertex_builder.AddVertices({ + {{0, 0}, {0.0, 0.0}}, // 1 + {{size.x, 0}, {1.0, 0.0}}, // 2 + {{size.x, size.y}, {1.0, 1.0}}, // 3 + {{0, 0}, {0.0, 0.0}}, // 1 + {{size.x, size.y}, {1.0, 1.0}}, // 3 + {{0, size.y}, {0.0, 1.0}}, // 4 + }); + auto vertex_buffer = + vertex_builder.CreateVertexBuffer(*context->GetResourceAllocator()); + ASSERT_TRUE(vertex_buffer); + + Renderer::RenderCallback callback = [&](RenderTarget& render_target) { + { + auto buffer = context->CreateCommandBuffer(); + if (!buffer) { + return false; + } + buffer->SetLabel("Playground Command Buffer"); + auto pass = buffer->CreateBlitPass(); + if (!pass) { + return false; + } + pass->SetLabel("Playground Blit Pass"); + + if (render_target.GetColorAttachments().empty()) { + return false; + } + + // Blit `bridge` to the top left corner of the texture. + pass->AddCopy(bridge, device_buffer); + + pass->EncodeCommands(context->GetResourceAllocator()); + + if (!buffer->SubmitCommands()) { + return false; + } + } + + { + auto buffer = context->CreateCommandBuffer(); + if (!buffer) { + return false; + } + buffer->SetLabel("Playground Command Buffer"); + + auto pass = buffer->CreateRenderPass(render_target); + if (!pass) { + return false; + } + pass->SetLabel("Playground Render Pass"); + { + Command cmd; + cmd.label = "Image"; + cmd.pipeline = mipmaps_pipeline; + + cmd.BindVertices(vertex_buffer); + + VS::VertInfo vert_info; + vert_info.mvp = Matrix::MakeOrthographic(pass->GetRenderTargetSize()) * + Matrix::MakeScale(GetContentScale()); + VS::BindVertInfo(cmd, + pass->GetTransientsBuffer().EmplaceUniform(vert_info)); + + FS::FragInfo frag_info; + frag_info.lod = 0; + FS::BindFragInfo(cmd, + pass->GetTransientsBuffer().EmplaceUniform(frag_info)); + + auto sampler = context->GetSamplerLibrary()->GetSampler({}); + auto buffer_view = device_buffer->AsBufferView(); + auto texture = + context->GetResourceAllocator()->CreateTexture(texture_desc); + if (!texture->SetContents(buffer_view.contents, + buffer_view.range.length)) { + VALIDATION_LOG << "Could not upload texture to device memory"; + return false; + } + FS::BindTex(cmd, texture, sampler); + + pass->AddCommand(std::move(cmd)); + } + pass->EncodeCommands(); + if (!buffer->SubmitCommands()) { + return false; + } + } + return true; + }; + OpenPlaygroundHere(callback); +} + TEST_P(RendererTest, CanGenerateMipmaps) { auto context = GetContext(); ASSERT_TRUE(context);