diff --git a/CMakeLists.txt b/CMakeLists.txt index 92d8184ea..aa0950752 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,6 +41,8 @@ option(AMBER_SKIP_SHADERC "Skip building Shaderc into the library" ${AMBER_SKIP_SHADERC}) option(AMBER_SKIP_SAMPLES "Skip building sample application" ${AMBER_SKIP_SAMPLES}) +option(AMBER_SKIP_LODEPNG + "Skip building lodepng into the library" ${AMBER_SKIP_LODEPNG}) option(AMBER_USE_DXC "Enable DXC integration" ${AMBER_USE_DXC}) option(AMBER_USE_LOCAL_VULKAN "Build with vulkan in third_party" OFF) option(AMBER_USE_CLSPV "Build with Clspv support" OFF) @@ -80,6 +82,12 @@ else() set(AMBER_ENABLE_SAMPLES TRUE) endif() +if (${AMBER_SKIP_LODEPNG}) + set(AMBER_ENABLE_LODEPNG FALSE) +else() + set(AMBER_ENABLE_LODEPNG TRUE) +endif() + if (${AMBER_ENABLE_SWIFTSHADER}) # Swiftshader requires the loader to be built. set(AMBER_USE_LOCAL_VULKAN TRUE) @@ -106,6 +114,7 @@ message(STATUS "Amber enable SPIRV-Tools: ${AMBER_ENABLE_SPIRV_TOOLS}") message(STATUS "Amber enable Shaderc: ${AMBER_ENABLE_SHADERC}") message(STATUS "Amber enable tests: ${AMBER_ENABLE_TESTS}") message(STATUS "Amber enable samples: ${AMBER_ENABLE_SAMPLES}") +message(STATUS "Amber enable lodepng: ${AMBER_ENABLE_LODEPNG}") message(STATUS "Amber enable SwiftShader: ${AMBER_ENABLE_SWIFTSHADER}") message(STATUS "Amber enable DXC: ${AMBER_ENABLE_DXC}") message(STATUS "Amber enable Clspv: ${AMBER_ENABLE_CLSPV}") @@ -130,6 +139,7 @@ add_definitions(-DAMBER_ENABLE_SPIRV_TOOLS=$) add_definitions(-DAMBER_ENABLE_SHADERC=$) add_definitions(-DAMBER_ENABLE_DXC=$) add_definitions(-DAMBER_ENABLE_CLSPV=$) +add_definitions(-DAMBER_ENABLE_LODEPNG=$) set(CMAKE_DEBUG_POSTFIX "") diff --git a/README.md b/README.md index efb136338..db70b6ed7 100644 --- a/README.md +++ b/README.md @@ -196,6 +196,9 @@ out/Debug/amber tests/cases/clear.vkscript The sample app returns a value of 0 on success or non-zero on error. Any issues encountered should be displayed on the console. +By default, `out/Debug/amber` supports saving the output image into '.png' +file. You can disable this by passing `-DAMBER_SKIP_LODEPNG=true` to cmake. + ## Contributing Please see the [CONTRIBUTING](CONTRIBUTING.md) and diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index 3831b8b41..d41ee21b4 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -19,12 +19,16 @@ set(AMBER_SOURCES config_helper.cc log.cc ppm.cc - png.cc timestamp.cc ${CMAKE_BINARY_DIR}/src/build-versions.h.fake ) -set(AMBER_EXTRA_LIBS "lodepng") +set(AMBER_EXTRA_LIBS "") + +if (${AMBER_ENABLE_LODEPNG}) + set(AMBER_SOURCES ${AMBER_SOURCES} png.cc) + list(APPEND AMBER_EXTRA_LIBS "lodepng") +endif() if (${Vulkan_FOUND}) set(AMBER_SOURCES ${AMBER_SOURCES} config_helper_vulkan.cc) @@ -45,6 +49,7 @@ endif() add_executable(amber ${AMBER_SOURCES}) target_include_directories(amber PRIVATE "${CMAKE_BINARY_DIR}") + set_target_properties(amber PROPERTIES OUTPUT_NAME "amber") target_link_libraries(amber libamber ${AMBER_EXTRA_LIBS}) amber_default_compile_options(amber) diff --git a/samples/amber.cc b/samples/amber.cc index 0f68a9f95..8c945b10d 100644 --- a/samples/amber.cc +++ b/samples/amber.cc @@ -25,12 +25,15 @@ #include "amber/recipe.h" #include "samples/config_helper.h" -#include "samples/png.h" #include "samples/ppm.h" #include "samples/timestamp.h" #include "src/build-versions.h" #include "src/make_unique.h" +#if AMBER_ENABLE_LODEPNG +#include "samples/png.h" +#endif // AMBER_ENABLE_LODEPNG + namespace { const char* kGeneratedColorBuffer = "framebuffer"; @@ -431,8 +434,12 @@ int main(int argc, const char** argv) { for (const amber::BufferInfo& buffer_info : amber_options.extractions) { if (buffer_info.buffer_name == options.fb_name) { if (usePNG) { +#if AMBER_ENABLE_LODEPNG result = png::ConvertToPNG(buffer_info.width, buffer_info.height, buffer_info.values, &out_buf); +#else // AMBER_ENABLE_LODEPNG + result = amber::Result("PNG support not enabled"); +#endif // AMBER_ENABLE_LODEPNG } else { ppm::ConvertToPPM(buffer_info.width, buffer_info.height, buffer_info.values, &out_buf); diff --git a/src/dawn/engine_dawn.cc b/src/dawn/engine_dawn.cc index 15af0ea0d..2fe1b8fb7 100644 --- a/src/dawn/engine_dawn.cc +++ b/src/dawn/engine_dawn.cc @@ -105,9 +105,11 @@ struct DawnPipelineHelper { const RenderPipelineInfo& render_pipeline, const ::dawn::Device& device, const bool ignore_vertex_and_Index_buffers); - Result CreateRenderPassDescriptor(const RenderPipelineInfo& render_pipeline, - const ::dawn::Device& device, - const ::dawn::TextureView texture_view); + Result CreateRenderPassDescriptor( + const RenderPipelineInfo& render_pipeline, + const ::dawn::Device& device, + const std::vector<::dawn::TextureView>& texture_view, + const ::dawn::LoadOp load_op); ::dawn::RenderPipelineDescriptor renderPipelineDescriptor; ::dawn::RenderPassDescriptor renderPassDescriptor; @@ -161,24 +163,28 @@ Result MakeTexture(const ::dawn::Device& device, return Result("Dawn: Failed to allocate a framebuffer texture"); } -// Creates a host-side buffer of |size| bytes for the framebuffer, and returns -// it through |result_ptr|. The buffer will be used as a transfer destination -// and for mapping-for-read. Returns a result code. -Result MakeFramebufferBuffer(const ::dawn::Device& device, - ::dawn::Buffer* result_ptr, - uint32_t size) { +// Creates a device-side texture, and returns it through |result_ptr|. +// Assumes the device exists and is valid. Assumes result_ptr is not null. +// Returns a result code. +::dawn::Texture MakeDawnTexture(const ::dawn::Device& device, + ::dawn::TextureFormat format, + uint32_t width, + uint32_t height) { assert(device); - assert(size > 0); - assert(result_ptr); + assert(width * height > 0); + ::dawn::TextureDescriptor descriptor; + descriptor.dimension = ::dawn::TextureDimension::e2D; + descriptor.size.width = width; + descriptor.size.height = height; + descriptor.size.depth = 1; + descriptor.arrayLayerCount = 1; + descriptor.format = format; + descriptor.mipLevelCount = 1; + descriptor.sampleCount = 1; + descriptor.usage = ::dawn::TextureUsageBit::CopySrc | + ::dawn::TextureUsageBit::OutputAttachment; - ::dawn::BufferDescriptor descriptor; - descriptor.size = size; - descriptor.usage = - ::dawn::BufferUsageBit::CopyDst | ::dawn::BufferUsageBit::MapRead; - *result_ptr = device.CreateBuffer(&descriptor); - if (*result_ptr) - return {}; - return Result("Dawn: Failed to allocate a framebuffer buffer"); + return device.CreateTexture(&descriptor); } // Result status object and data pointer resulting from a buffer mapping. @@ -224,6 +230,7 @@ uint32_t Align(uint32_t value, size_t alignment) { uint32_t alignment32 = static_cast(alignment); return (value + (alignment32 - 1)) & ~(alignment32 - 1); } + } // namespace // Maps the given buffer. Assumes the buffer has usage bit @@ -285,66 +292,6 @@ ::dawn::TextureCopyView CreateTextureCopyView(::dawn::Texture texture, return textureCopyView; } -// Creates and submits a command to copy the colour attachments back to the -// host. -// TODO(sarahM0): Handle more than one colour attachment -// TODO(sarahM0): Copy the buffer data for buffers that are not -// colour-attachments back into the Amber buffer objects. -MapResult MapTextureToHostBuffer(const RenderPipelineInfo& render_pipeline, - const ::dawn::Device& device) { - const auto width = render_pipeline.pipeline->GetFramebufferWidth(); - const auto height = render_pipeline.pipeline->GetFramebufferHeight(); - const auto pixelSize = render_pipeline.pipeline->GetColorAttachments()[0] - .buffer->GetTexelStride(); - const auto dawn_row_pitch = Align(width * pixelSize, kMinimumImageRowPitch); - { - ::dawn::Origin3D origin3D; - origin3D.x = 0; - origin3D.y = 0; - origin3D.z = 0; - ::dawn::TextureCopyView textureCopyView = - CreateTextureCopyView(render_pipeline.fb_texture, 0, 0, origin3D); - - ::dawn::BufferCopyView bufferCopyView = - CreateBufferCopyView(render_pipeline.fb_buffer, 0, dawn_row_pitch, 0); - - ::dawn::Extent3D copySize = {width, height, 1}; - - auto encoder = device.CreateCommandEncoder(); - encoder.CopyTextureToBuffer(&textureCopyView, &bufferCopyView, ©Size); - - auto commands = encoder.Finish(); - auto queue = device.CreateQueue(); - queue.Submit(1, &commands); - } - - MapResult map = MapBuffer(device, render_pipeline.fb_buffer); - const std::vector& out_color_attachment = - render_pipeline.pipeline->GetColorAttachments(); - - for (size_t i = 0; i < out_color_attachment.size(); ++i) { - auto& info = out_color_attachment[i]; - auto* values = info.buffer->ValuePtr(); - auto row_stride = pixelSize * width; - assert(row_stride * height == info.buffer->GetSizeInBytes()); - // Each Dawn row has enough data to fill the target row. - assert(dawn_row_pitch >= row_stride); - values->resize(info.buffer->GetSizeInBytes()); - // Copy the framebuffer contents back into the host-side - // framebuffer-buffer. In the Dawn buffer, the row stride is a multiple of - // kMinimumImageRowPitch bytes, so it might have padding therefore memcpy - // is done row by row. - for (uint h = 0; h < height; h++) { - std::memcpy(values->data() + h * row_stride, - static_cast(map.data) + h * dawn_row_pitch, - row_stride); - } - } - // Always unmap the buffer at the end of the engine's command. - render_pipeline.fb_buffer.Unmap(); - return map; -} - // Creates a dawn buffer of |size| bytes with TransferDst and the given usage // copied from Dawn utils source code ::dawn::Buffer CreateBufferFromData(const ::dawn::Device& device, @@ -621,6 +568,69 @@ Result EngineDawn::Initialize(EngineConfig* config, return {}; } +Result EngineDawn::MapDeviceTextureToHostBuffer( + const RenderPipelineInfo& render_pipeline, + const ::dawn::Device& device) { + const auto width = render_pipeline.pipeline->GetFramebufferWidth(); + const auto height = render_pipeline.pipeline->GetFramebufferHeight(); + + const auto pixelSize = render_pipeline.pipeline->GetColorAttachments()[0] + .buffer->GetTexelStride(); + const auto dawn_row_pitch = Align(width * pixelSize, kMinimumImageRowPitch); + const auto size = height * dawn_row_pitch; + // Create a temporary buffer to hold the color attachment content and can + // be mapped + ::dawn::BufferDescriptor descriptor; + descriptor.size = size; + descriptor.usage = + ::dawn::BufferUsageBit::CopyDst | ::dawn::BufferUsageBit::MapRead; + ::dawn::Buffer copy_buffer = device.CreateBuffer(&descriptor); + ::dawn::BufferCopyView copy_buffer_view = + CreateBufferCopyView(copy_buffer, 0, dawn_row_pitch, 0); + ::dawn::Origin3D origin3D; + origin3D.x = 0; + origin3D.y = 0; + origin3D.z = 0; + + for (uint32_t i = 0; + i < render_pipeline.pipeline->GetColorAttachments().size(); i++) { + ::dawn::TextureCopyView device_texture_view = + CreateTextureCopyView(textures_[i], 0, 0, origin3D); + ::dawn::Extent3D copySize = {width, height, 1}; + auto encoder = device.CreateCommandEncoder(); + encoder.CopyTextureToBuffer(&device_texture_view, ©_buffer_view, + ©Size); + auto commands = encoder.Finish(); + auto queue = device.CreateQueue(); + queue.Submit(1, &commands); + + MapResult mapped_device_texture = MapBuffer(device, copy_buffer); + if (!mapped_device_texture.result.IsSuccess()) + return mapped_device_texture.result; + + auto& host_texture = render_pipeline.pipeline->GetColorAttachments()[i]; + auto* values = host_texture.buffer->ValuePtr(); + auto row_stride = pixelSize * width; + assert(row_stride * height == host_texture.buffer->GetSizeInBytes()); + // Each Dawn row has enough data to fill the target row. + assert(dawn_row_pitch >= row_stride); + values->resize(host_texture.buffer->GetSizeInBytes()); + // Copy the framebuffer contents back into the host-side + // framebuffer-buffer. In the Dawn buffer, the row stride is a multiple of + // kMinimumImageRowPitch bytes, so it might have padding therefore memcpy + // is done row by row. + for (uint h = 0; h < height; h++) { + std::memcpy(values->data() + h * row_stride, + static_cast(mapped_device_texture.data) + + h * dawn_row_pitch, + row_stride); + } + // Always unmap the buffer at the end of the engine's command. + copy_buffer.Unmap(); + } + return {}; +} + Result EngineDawn::CreatePipeline(::amber::Pipeline* pipeline) { if (!device_) { return Result("Dawn::CreatePipeline: device is not created"); @@ -715,61 +725,35 @@ Result EngineDawn::DoClearDepth(const ClearDepthCommand* command) { } Result EngineDawn::DoClear(const ClearCommand* command) { + Result result; RenderPipelineInfo* render_pipeline = GetRenderPipeline(command); if (!render_pipeline) return Result("Clear invoked on invalid or missing render pipeline"); - // Record a render pass in a command on the command buffer. - // - // First describe the color attachments, and how they are initialized - // via the load op. The load op is "clear" to the clear colour. - ::dawn::RenderPassColorAttachmentDescriptor color_attachment = - ::dawn::RenderPassColorAttachmentDescriptor(); - color_attachment.attachment = texture_view_; - color_attachment.resolveTarget = nullptr; - color_attachment.clearColor = render_pipeline->clear_color_value; - color_attachment.loadOp = ::dawn::LoadOp::Clear; - color_attachment.storeOp = ::dawn::StoreOp::Store; - ::dawn::RenderPassColorAttachmentDescriptor* color_attachment_descriptor[] = { - &color_attachment}; - - // Then describe the depthStencil attachment, and how it is initialized - // via the load ops. Both load op are "clear" to the clear values. - ::dawn::RenderPassDepthStencilAttachmentDescriptor depth_stencil_attachment = - ::dawn::RenderPassDepthStencilAttachmentDescriptor(); - ::dawn::RenderPassDepthStencilAttachmentDescriptor* depth_stencil_descriptor = - nullptr; - if (render_pipeline->depth_stencil_texture) { - depth_stencil_attachment.attachment = - render_pipeline->depth_stencil_texture.CreateDefaultView(); - depth_stencil_attachment.clearDepth = render_pipeline->clear_depth_value; - depth_stencil_attachment.clearStencil = - render_pipeline->clear_stencil_value; - depth_stencil_attachment.depthLoadOp = ::dawn::LoadOp::Clear; - depth_stencil_attachment.depthStoreOp = ::dawn::StoreOp::Store; - depth_stencil_attachment.stencilLoadOp = ::dawn::LoadOp::Clear; - depth_stencil_attachment.stencilStoreOp = ::dawn::StoreOp::Store; - depth_stencil_descriptor = &depth_stencil_attachment; - } - // Attach the depth/stencil and colour attachments to the render pass. - ::dawn::RenderPassDescriptor rpd; - rpd.colorAttachmentCount = 1; - rpd.colorAttachments = color_attachment_descriptor; - rpd.depthStencilAttachment = depth_stencil_descriptor; + DawnPipelineHelper helper; + result = + helper.CreateRenderPipelineDescriptor(*render_pipeline, *device_, false); + if (!result.IsSuccess()) + return result; + result = helper.CreateRenderPassDescriptor( + *render_pipeline, *device_, texture_views_, ::dawn::LoadOp::Clear); + if (!result.IsSuccess()) + return result; - // Record the render pass as a command. - auto encoder = device_->CreateCommandEncoder(); - ::dawn::RenderPassEncoder pass = encoder.BeginRenderPass(&rpd); + ::dawn::RenderPassDescriptor* renderPassDescriptor = + &helper.renderPassDescriptor; + ::dawn::CommandEncoder encoder = device_->CreateCommandEncoder(); + ::dawn::RenderPassEncoder pass = + encoder.BeginRenderPass(renderPassDescriptor); pass.EndPass(); - // Finish recording the command buffer. It only has one command. - auto command_buffer = encoder.Finish(); - // Submit the command. - auto queue = device_->CreateQueue(); - queue.Submit(1, &command_buffer); - // Copy result back - MapResult map = MapTextureToHostBuffer(*render_pipeline, *device_); - - return map.result; + + ::dawn::CommandBuffer commands = encoder.Finish(); + ::dawn::Queue queue = device_->CreateQueue(); + queue.Submit(1, &commands); + + result = MapDeviceTextureToHostBuffer(*render_pipeline, *device_); + + return result; } // Creates a Dawn render pipeline descriptor for the given pipeline on the given @@ -899,7 +883,8 @@ Result DawnPipelineHelper::CreateRenderPipelineDescriptor( } // Set defaults for the color state descriptors. - renderPipelineDescriptor.colorStateCount = 1; + renderPipelineDescriptor.colorStateCount = + render_pipeline.pipeline->GetColorAttachments().size(); blend.operation = ::dawn::BlendOperation::Add; blend.srcFactor = ::dawn::BlendFactor::One; blend.dstFactor = ::dawn::BlendFactor::Zero; @@ -910,8 +895,23 @@ Result DawnPipelineHelper::CreateRenderPipelineDescriptor( for (uint32_t i = 0; i < kMaxColorAttachments; ++i) { colorStatesDescriptor[i] = colorStateDescriptor; colorStates[i] = &colorStatesDescriptor[i]; + ::dawn::TextureFormat fb_format{}; + { + if (i < render_pipeline.pipeline->GetColorAttachments().size()) { + auto* amber_format = render_pipeline.pipeline->GetColorAttachments()[i] + .buffer->GetFormat(); + if (!amber_format) + return Result( + "AttachBuffersAndTextures: One Color attachment has no format!"); + result = GetDawnTextureFormat(*amber_format, &fb_format); + if (!result.IsSuccess()) + return result; + } else { + fb_format = ::dawn::TextureFormat::RGBA8Unorm; + } + } + colorStates[i]->format = fb_format; } - colorStates[0]->format = fb_format; renderPipelineDescriptor.colorStates = &colorStates[0]; // Set defaults for the depth stencil state descriptors. @@ -919,7 +919,6 @@ Result DawnPipelineHelper::CreateRenderPipelineDescriptor( stencilFace.failOp = ::dawn::StencilOperation::Keep; stencilFace.depthFailOp = ::dawn::StencilOperation::Keep; stencilFace.passOp = ::dawn::StencilOperation::Keep; - depthStencilState.format = fb_format; depthStencilState.depthWriteEnabled = false; depthStencilState.depthCompare = ::dawn::CompareFunction::Always; depthStencilState.stencilBack = stencilFace; @@ -935,12 +934,10 @@ Result DawnPipelineHelper::CreateRenderPipelineDescriptor( Result DawnPipelineHelper::CreateRenderPassDescriptor( const RenderPipelineInfo& render_pipeline, const ::dawn::Device& device, - const ::dawn::TextureView texture_view) { - std::initializer_list<::dawn::TextureView> colorAttachmentInfo = { - texture_view}; - + const std::vector<::dawn::TextureView>& texture_view, + const ::dawn::LoadOp load_op) { for (uint32_t i = 0; i < kMaxColorAttachments; ++i) { - colorAttachmentsInfo[i].loadOp = ::dawn::LoadOp::Load; + colorAttachmentsInfo[i].loadOp = load_op; colorAttachmentsInfo[i].storeOp = ::dawn::StoreOp::Store; colorAttachmentsInfo[i].clearColor = render_pipeline.clear_color_value; colorAttachmentsInfoPtr[i] = nullptr; @@ -948,15 +945,15 @@ Result DawnPipelineHelper::CreateRenderPassDescriptor( depthStencilAttachmentInfo.clearDepth = render_pipeline.clear_depth_value; depthStencilAttachmentInfo.clearStencil = render_pipeline.clear_stencil_value; - depthStencilAttachmentInfo.depthLoadOp = ::dawn::LoadOp::Load; + depthStencilAttachmentInfo.depthLoadOp = load_op; depthStencilAttachmentInfo.depthStoreOp = ::dawn::StoreOp::Store; - depthStencilAttachmentInfo.stencilLoadOp = ::dawn::LoadOp::Load; + depthStencilAttachmentInfo.stencilLoadOp = load_op; depthStencilAttachmentInfo.stencilStoreOp = ::dawn::StoreOp::Store; renderPassDescriptor.colorAttachmentCount = - static_cast(colorAttachmentInfo.size()); + render_pipeline.pipeline->GetColorAttachments().size(); uint32_t colorAttachmentIndex = 0; - for (const ::dawn::TextureView& colorAttachment : colorAttachmentInfo) { + for (const ::dawn::TextureView& colorAttachment : texture_view) { if (colorAttachment.Get() != nullptr) { colorAttachmentsInfo[colorAttachmentIndex].attachment = colorAttachment; colorAttachmentsInfoPtr[colorAttachmentIndex] = @@ -1051,7 +1048,8 @@ Result EngineDawn::DoDrawRect(const DrawRectCommand* command) { DawnPipelineHelper helper; helper.CreateRenderPipelineDescriptor(*render_pipeline, *device_, true); - helper.CreateRenderPassDescriptor(*render_pipeline, *device_, texture_view_); + helper.CreateRenderPassDescriptor(*render_pipeline, *device_, texture_views_, + ::dawn::LoadOp::Load); ::dawn::RenderPipelineDescriptor* renderPipelineDescriptor = &helper.renderPipelineDescriptor; ::dawn::RenderPassDescriptor* renderPassDescriptor = @@ -1078,9 +1076,9 @@ Result EngineDawn::DoDrawRect(const DrawRectCommand* command) { ::dawn::Queue queue = device_->CreateQueue(); queue.Submit(1, &commands); - MapResult map = MapTextureToHostBuffer(*render_pipeline, *device_); + Result result = MapDeviceTextureToHostBuffer(*render_pipeline, *device_); - return map.result; + return result; } Result EngineDawn::DoDrawArrays(const DrawArraysCommand* command) { @@ -1113,8 +1111,8 @@ Result EngineDawn::DoDrawArrays(const DrawArraysCommand* command) { helper.CreateRenderPipelineDescriptor(*render_pipeline, *device_, false); if (!result.IsSuccess()) return result; - result = helper.CreateRenderPassDescriptor(*render_pipeline, *device_, - texture_view_); + result = helper.CreateRenderPassDescriptor( + *render_pipeline, *device_, texture_views_, ::dawn::LoadOp::Load); if (!result.IsSuccess()) return result; @@ -1149,7 +1147,7 @@ Result EngineDawn::DoDrawArrays(const DrawArraysCommand* command) { } // TODO(sarahM0): figure out what this offset means pass.SetIndexBuffer(render_pipeline->index_buffer, /* buffer */ - 0); /*offset*/ + 0); /* offset*/ pass.DrawIndexed(command->GetVertexCount(), /* indexCount */ instance_count, /* instanceCount */ 0, /* firstIndex */ @@ -1161,9 +1159,9 @@ Result EngineDawn::DoDrawArrays(const DrawArraysCommand* command) { ::dawn::Queue queue = device_->CreateQueue(); queue.Submit(1, &commands); - MapResult map = MapTextureToHostBuffer(*render_pipeline, *device_); + result = MapDeviceTextureToHostBuffer(*render_pipeline, *device_); - return map.result; + return result; } // namespace dawn Result EngineDawn::DoCompute(const ComputeCommand*) { @@ -1212,47 +1210,45 @@ Result EngineDawn::DoBuffer(const BufferCommand* command) { Result EngineDawn::AttachBuffersAndTextures( RenderPipelineInfo* render_pipeline) { Result result; - - // TODO(sarahM0): rewrite this for more than one color attachment. const uint32_t width = render_pipeline->pipeline->GetFramebufferWidth(); const uint32_t height = render_pipeline->pipeline->GetFramebufferHeight(); - const auto pixelSize = render_pipeline->pipeline->GetColorAttachments()[0] - .buffer->GetTexelStride(); - const auto dawn_row_pitch = Align(width * pixelSize, kMinimumImageRowPitch); - const auto size = height * dawn_row_pitch; - auto* amber_format = - render_pipeline->pipeline->GetColorAttachments()[0].buffer->GetFormat(); - if (!amber_format) - return Result( - "AttachBuffersAndTextures: Color attachment 0 has no format!"); - ::dawn::TextureFormat fb_format{}; - result = GetDawnTextureFormat(*amber_format, &fb_format); - if (!result.IsSuccess()) - return result; - - // First make the Dawn color attachment textures that the render pipeline - // will write into. - if (!fb_texture_) { - result = MakeTexture(*device_, fb_format, width, height, &fb_texture_); - if (!result.IsSuccess()) - return result; - render_pipeline->fb_texture = fb_texture_; - texture_view_ = render_pipeline->fb_texture.CreateDefaultView(); - } else { - render_pipeline->fb_texture = fb_texture_; + // Create textures and texture views if we haven't already + std::vector seen_idx( + render_pipeline->pipeline->GetColorAttachments().size(), -1); + for (auto info : render_pipeline->pipeline->GetColorAttachments()) { + if (info.location >= + render_pipeline->pipeline->GetColorAttachments().size()) + return Result("color attachment locations must be sequential from 0"); + if (seen_idx[info.location] != -1) { + return Result("duplicate attachment location: " + + std::to_string(info.location)); + } + seen_idx[info.location] = static_cast(info.location); } - // Now create the Dawn buffer to hold the framebuffer contents, but on the - // host side. This has to match dimensions of the framebuffer, but also - // be linearly addressible by the CPU. - if (!fb_buffer_) { - result = MakeFramebufferBuffer(*device_, &fb_buffer_, size); - if (!result.IsSuccess()) - return result; - render_pipeline->fb_buffer = fb_buffer_; - } else { - render_pipeline->fb_buffer = fb_buffer_; + if (textures_.size() == 0) { + for (uint32_t i = 0; i < kMaxColorAttachments; i++) { + ::dawn::TextureFormat fb_format{}; + + if (i < render_pipeline->pipeline->GetColorAttachments().size()) { + auto* amber_format = render_pipeline->pipeline->GetColorAttachments()[i] + .buffer->GetFormat(); + if (!amber_format) + return Result( + "AttachBuffersAndTextures: One Color attachment has no " + "format!"); + result = GetDawnTextureFormat(*amber_format, &fb_format); + if (!result.IsSuccess()) + return result; + } else { + fb_format = ::dawn::TextureFormat::RGBA8Unorm; + } + + textures_.emplace_back( + MakeDawnTexture(*device_, fb_format, width, height)); + texture_views_.emplace_back(textures_.back().CreateDefaultView()); + } } // Attach depth-stencil texture @@ -1311,6 +1307,16 @@ Result EngineDawn::AttachBuffersAndTextures( uint32_t max_descriptor_set = 0; // Attach storage/uniform buffers + ::dawn::BindGroupLayoutBinding empty_layout_info = {}; + + if (!render_pipeline->pipeline->GetBuffers().empty()) { + std::vector max_binding_seen(kMaxDawnBindGroup, -1); + for (auto& buf_info : render_pipeline->pipeline->GetBuffers()) { + while (layouts_info[buf_info.descriptor_set].size() <= buf_info.binding) + layouts_info[buf_info.descriptor_set].push_back(empty_layout_info); + } + } + for (const auto& buf_info : render_pipeline->pipeline->GetBuffers()) { ::dawn::BufferUsageBit bufferUsage; ::dawn::BindingType bindingType; @@ -1355,7 +1361,7 @@ Result EngineDawn::AttachBuffersAndTextures( layout_info.binding = buf_info.binding; layout_info.visibility = kAllStages; layout_info.type = bindingType; - layouts_info[buf_info.descriptor_set].push_back(layout_info); + layouts_info[buf_info.descriptor_set][buf_info.binding] = layout_info; BindingInitializationHelper tempBinding = BindingInitializationHelper( buf_info.binding, render_pipeline->buffers.back(), 0, @@ -1363,8 +1369,6 @@ Result EngineDawn::AttachBuffersAndTextures( bindingInitalizerHelper[buf_info.descriptor_set].push_back(tempBinding); } - // TODO(sarahM0): fix issue: Add support for doBuffer with sparse descriptor - // sets #573 if (render_pipeline->used_descriptor_set.size() != 0 && render_pipeline->used_descriptor_set.size() != max_descriptor_set + 1) { return Result( @@ -1377,6 +1381,15 @@ Result EngineDawn::AttachBuffersAndTextures( MakeBindGroupLayout(*device_, layouts_info[i]); render_pipeline->bind_group_layouts.push_back(bindGroupLayout); + ::dawn::BindGroup bindGroup = + MakeBindGroup(*device_, render_pipeline->bind_group_layouts[i], + bindingInitalizerHelper[i]); + render_pipeline->bind_groups.push_back(bindGroup); + } else if (i < max_descriptor_set) { + ::dawn::BindGroupLayout bindGroupLayout = + MakeBindGroupLayout(*device_, {}); + render_pipeline->bind_group_layouts.push_back(bindGroupLayout); + ::dawn::BindGroup bindGroup = MakeBindGroup(*device_, render_pipeline->bind_group_layouts[i], bindingInitalizerHelper[i]); diff --git a/src/dawn/engine_dawn.h b/src/dawn/engine_dawn.h index b6e736ac7..5fa699f72 100644 --- a/src/dawn/engine_dawn.h +++ b/src/dawn/engine_dawn.h @@ -69,23 +69,23 @@ class EngineDawn : public Engine { return pipeline_map_[command->GetPipeline()].render_pipeline.get(); } - // If they don't already exist, creates the framebuffer texture for use - // on the device, the buffer on the host that will eventually hold the - // resulting pixels for use in checking expectations, and bookkeeping info - // for that host-side buffer. + // Creates and attaches index, vertex, storage, uniform and depth-stencil + // buffers. Sets up bindings. Also creates textures and texture views if not + // created yet. Result AttachBuffersAndTextures(RenderPipelineInfo* render_pipeline); + // Creates and submits a command to copy dawn textures back to amber color + // attachments + Result MapDeviceTextureToHostBuffer(const RenderPipelineInfo& render_pipeline, + const ::dawn::Device& device); - ::dawn::Device* device_ = nullptr; // Borrowed from the engine config. - - // Host-side buffer for the framebuffer - ::dawn::Buffer fb_buffer_; - // Dawn color attachment texture - ::dawn::Texture fb_texture_; - // A view into fb_texture_ - ::dawn::TextureView texture_view_; + // Borrowed from the engine config + ::dawn::Device* device_ = nullptr; + // Dawn color attachment textures + std::vector<::dawn::Texture> textures_; + // Views into Dawn color attachment textures + std::vector<::dawn::TextureView> texture_views_; // Dawn depth/stencil texture ::dawn::Texture depth_stencil_texture_; - // Mapping from the generic engine's Pipeline object to our own Dawn-specific // pipelines. std::unordered_map pipeline_map_; diff --git a/src/dawn/pipeline_info.h b/src/dawn/pipeline_info.h index 4d09d262b..49b006140 100644 --- a/src/dawn/pipeline_info.h +++ b/src/dawn/pipeline_info.h @@ -55,18 +55,15 @@ struct RenderPipelineInfo { float clear_depth_value = 1.0f; uint32_t clear_stencil_value = 0; - /// The framebuffer color render target. This resides on the GPU. - ::dawn::Texture fb_texture; - /// The depth and stencil target. This resides on the GPU. + // Depth-stencil target. This resides on the GPU. ::dawn::Texture depth_stencil_texture; - /// The buffer to which we will copy the rendered pixel values, for - /// use on the host. - ::dawn::Buffer fb_buffer; + // Vertex buffers std::vector<::dawn::Buffer> vertex_buffers; + // Index buffer ::dawn::Buffer index_buffer; - /// storage and uniform buffers + // Storage and uniform buffers std::vector<::dawn::Buffer> buffers; - + // Binding info std::vector<::dawn::BindGroup> bind_groups; std::vector<::dawn::BindGroupLayout> bind_group_layouts; diff --git a/src/dawn/pipeline_info_test.cc b/src/dawn/pipeline_info_test.cc index 43525b9ff..a1ff2acee 100644 --- a/src/dawn/pipeline_info_test.cc +++ b/src/dawn/pipeline_info_test.cc @@ -46,8 +46,6 @@ TEST_F(DawnRenderPipelineInfoTest, DefaultValuesForMembers) { EXPECT_FLOAT_EQ(0.0f, rpi.clear_color_value.a); EXPECT_FLOAT_EQ(1.0f, rpi.clear_depth_value); EXPECT_EQ(0u, rpi.clear_stencil_value); - EXPECT_FALSE(static_cast(rpi.fb_texture)); - EXPECT_FALSE(static_cast(rpi.fb_buffer)); } } // namespace diff --git a/src/tokenizer.cc b/src/tokenizer.cc index f24655c75..54bf88c05 100644 --- a/src/tokenizer.cc +++ b/src/tokenizer.cc @@ -99,10 +99,15 @@ std::unique_ptr Tokenizer::NextToken() { data_.substr(current_position_, end_pos - current_position_); current_position_ = end_pos; + // Check for "NaN" explicitly. + bool is_nan = + (tok_str.size() == 3 && std::tolower(tok_str[0]) == 'n' && + std::tolower(tok_str[1]) == 'a' && std::tolower(tok_str[2]) == 'n'); + // Starts with an alpha is a string. - if (!std::isdigit(tok_str[0]) && - !(tok_str[0] == '-' && std::isdigit(tok_str[1])) && - !(tok_str[0] == '.' && std::isdigit(tok_str[1]))) { + if (!is_nan && !std::isdigit(tok_str[0]) && + !(tok_str[0] == '-' && tok_str.size() >= 2 && std::isdigit(tok_str[1])) && + !(tok_str[0] == '.' && tok_str.size() >= 2 && std::isdigit(tok_str[1]))) { // If we've got a continuation, skip over the end of line and get the next // token. if (tok_str == "\\") { @@ -126,17 +131,21 @@ std::unique_ptr Tokenizer::NextToken() { } // Handle hex strings - if (tok_str.size() > 2 && tok_str[0] == '0' && tok_str[1] == 'x') { + if (!is_nan && tok_str.size() > 2 && tok_str[0] == '0' && tok_str[1] == 'x') { auto tok = MakeUnique(TokenType::kHex); tok->SetStringValue(tok_str); return tok; } bool is_double = false; - for (const char ch : tok_str) { - if (ch == '.') { - is_double = true; - break; + if (is_nan) { + is_double = true; + } else { + for (const char ch : tok_str) { + if (ch == '.') { + is_double = true; + break; + } } } diff --git a/src/tokenizer_test.cc b/src/tokenizer_test.cc index d50ec467c..fd7dba48d 100644 --- a/src/tokenizer_test.cc +++ b/src/tokenizer_test.cc @@ -14,6 +14,7 @@ #include "src/tokenizer.h" +#include #include #include "gtest/gtest.h" @@ -77,6 +78,33 @@ TEST_F(TokenizerTest, ProcessDouble) { EXPECT_TRUE(next->IsEOS()); } +namespace { + +void TestNaN(const std::string& nan_str) { + Tokenizer t(nan_str); + auto next = t.NextToken(); + ASSERT_TRUE(next != nullptr); + EXPECT_TRUE(next->IsDouble()); + EXPECT_TRUE(std::isnan(next->AsDouble())); + + next = t.NextToken(); + ASSERT_TRUE(next != nullptr); + EXPECT_TRUE(next->IsEOS()); +} + +} // namespace + +TEST_F(TokenizerTest, ProcessNaN) { + TestNaN("nan"); + TestNaN("naN"); + TestNaN("nAn"); + TestNaN("nAN"); + TestNaN("Nan"); + TestNaN("NaN"); + TestNaN("NAn"); + TestNaN("NAN"); +} + TEST_F(TokenizerTest, ProcessNegativeDouble) { Tokenizer t("-123.456"); auto next = t.NextToken(); diff --git a/tests/cases/draw_rect_multiple_color_attachment.amber b/tests/cases/draw_rect_multiple_color_attachment.amber index 56fcd550d..31534f586 100644 --- a/tests/cases/draw_rect_multiple_color_attachment.amber +++ b/tests/cases/draw_rect_multiple_color_attachment.amber @@ -19,7 +19,7 @@ SHADER fragment frag_shader GLSL layout(location = 0) out vec4 color_out; layout(location = 1) out vec4 color_out1; void main() { - color_out = vec4(0.0, 0.5, 0.0, 0.8); + color_out = vec4(0.0, 0.499, 0.0, 0.8); color_out1 = vec4(1.0, 1.0, 1.0, 1.0); } END @@ -36,5 +36,5 @@ PIPELINE graphics my_pipeline END RUN my_pipeline DRAW_RECT POS 0 0 SIZE 800 600 -EXPECT framebuffer IDX 0 0 SIZE 800 600 EQ_RGBA 0 128 0 204 +EXPECT framebuffer IDX 0 0 SIZE 800 600 EQ_RGBA 0 127 0 204 EXPECT framebuffer1 IDX 0 0 SIZE 800 600 EQ_RGBA 255 255 255 255 diff --git a/tests/run_tests.py b/tests/run_tests.py index 18a35b5fc..7702efab4 100755 --- a/tests/run_tests.py +++ b/tests/run_tests.py @@ -126,8 +126,6 @@ "draw_triangle_list_in_r32g32b32a32_sfloat_color_frame.vkscript", "draw_triangle_list_in_r8g8b8a8_snorm_color_frame.vkscript", "draw_triangle_list_in_r8g8b8a8_srgb_color_frame.vkscript", - # Currently not working, an issue is created - "draw_rect_multiple_color_attachment.amber", # Currently not working, under investigation "draw_triangle_list_with_depth.vkscript", "non_default_entry_point.amber", diff --git a/third_party/CMakeLists.txt b/third_party/CMakeLists.txt index f23cdb095..db865ad8d 100644 --- a/third_party/CMakeLists.txt +++ b/third_party/CMakeLists.txt @@ -41,7 +41,7 @@ if (${AMBER_ENABLE_SHADERC}) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/shaderc) endif() -if (${AMBER_ENABLE_SAMPLES}) +if (${AMBER_ENABLE_SAMPLES} AND ${AMBER_ENABLE_LODEPNG}) # Lodepng set(LODEPNG_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/lodepng/lodepng.cpp) add_library(lodepng STATIC ${LODEPNG_SOURCES})