diff --git a/Rendering/WebGPU/CMakeLists.txt b/Rendering/WebGPU/CMakeLists.txt index e9e8432c82fbd053f1141513d6402334c13833b1..8c2ca90d67defecd3051a5041cf18253b16fba1d 100644 --- a/Rendering/WebGPU/CMakeLists.txt +++ b/Rendering/WebGPU/CMakeLists.txt @@ -112,6 +112,7 @@ set(private_shader_files wgsl/ActorColorOptions.wgsl wgsl/ActorRenderOptions.wgsl wgsl/ActorTransform.wgsl + wgsl/CopyDepthTextureToBuffer.wgsl wgsl/FrustumCullingShader.wgsl wgsl/LineFragmentShader.wgsl wgsl/LineGlyphShader.wgsl diff --git a/Rendering/WebGPU/vtkWebGPURenderPipelineCache.cxx b/Rendering/WebGPU/vtkWebGPURenderPipelineCache.cxx index b772be0d9c86631a6b6d83a953dcd0cf08c5687d..f80c4734ecec2a92c8acd34645b9bb5f5c000f45 100644 --- a/Rendering/WebGPU/vtkWebGPURenderPipelineCache.cxx +++ b/Rendering/WebGPU/vtkWebGPURenderPipelineCache.cxx @@ -137,7 +137,13 @@ void vtkWebGPURenderPipelineCache::CreateRenderPipeline(wgpu::RenderPipelineDesc vtkWebGPURenderer* wgpuRenderer, const char* shaderSource) { auto* wgpuRenderWindow = vtkWebGPURenderWindow::SafeDownCast(wgpuRenderer->GetRenderWindow()); + this->CreateRenderPipeline(descriptor, wgpuRenderWindow, shaderSource); +} +//------------------------------------------------------------------------------ +void vtkWebGPURenderPipelineCache::CreateRenderPipeline(wgpu::RenderPipelineDescriptor* descriptor, + vtkWebGPURenderWindow* wgpuRenderWindow, const char* shaderSource) +{ // apply all shader source replacements. const auto source = wgpuRenderWindow->PreprocessShaderSource(shaderSource); diff --git a/Rendering/WebGPU/vtkWebGPURenderPipelineCache.h b/Rendering/WebGPU/vtkWebGPURenderPipelineCache.h index 8d2723e56957b3a959b4782171cbd3110b66ce3a..5fd9e028f32a33d828134bb8de90164cba472b36 100644 --- a/Rendering/WebGPU/vtkWebGPURenderPipelineCache.h +++ b/Rendering/WebGPU/vtkWebGPURenderPipelineCache.h @@ -34,6 +34,7 @@ VTK_ABI_NAMESPACE_BEGIN class vtkWebGPURenderer; +class vtkWebGPURenderWindow; class vtkWindow; class VTKRENDERINGWEBGPU_EXPORT vtkWebGPURenderPipelineCache : public vtkObject @@ -65,6 +66,9 @@ public: void CreateRenderPipeline(wgpu::RenderPipelineDescriptor* descriptor, vtkWebGPURenderer* wgpuRenderer, const char* shaderSource); + void CreateRenderPipeline(wgpu::RenderPipelineDescriptor* descriptor, + vtkWebGPURenderWindow* wgpuRenderWindow, const char* shaderSource); + /** * Destroy the render pipeline associated with the given hash. */ diff --git a/Rendering/WebGPU/vtkWebGPURenderWindow.cxx b/Rendering/WebGPU/vtkWebGPURenderWindow.cxx index 5d1c1caec5585f17b6eb6a7c4b7027ba8fc980d0..b9492f48cb58884ba5db1ff5a2d6005216fa1dc0 100644 --- a/Rendering/WebGPU/vtkWebGPURenderWindow.cxx +++ b/Rendering/WebGPU/vtkWebGPURenderWindow.cxx @@ -17,14 +17,14 @@ #include "vtkObjectFactory.h" #include "vtkRect.h" #include "vtkRendererCollection.h" -#include "vtkTypeUInt8Array.h" #include "vtkUnsignedCharArray.h" #include "vtkWebGPUConfiguration.h" #include "vtkWebGPURenderer.h" #include "vtksys/SystemTools.hxx" -#include <exception> +#include "CopyDepthTextureToBuffer.h" + #include <sstream> #if defined(__EMSCRIPTEN__) @@ -59,51 +59,24 @@ VTK_ABI_NAMESPACE_BEGIN namespace { -struct PixelReadDescriptor -{ - vtkRecti Rect; - int NumColorComponents = 0; - int NumBytesPerRow = 0; - int NumRows = 0; -}; - -PixelReadDescriptor GetPixelReadDesriptor( - const wgpu::Texture& colorTexture, const int x, const int y, const int x2, const int y2) +struct InternalMapTextureAsyncData { - PixelReadDescriptor desc; - desc.NumColorComponents = 4; - desc.NumBytesPerRow = vtkWebGPUConfiguration::Align(colorTexture.GetWidth() * 4, 256); - desc.NumRows = colorTexture.GetHeight(); - - int y_low, y_hi; - int x_low, x_hi; - - if (y < y2) - { - y_low = y; - y_hi = y2; - } - else - { - y_low = y2; - y_hi = y; - } - - if (x < x2) - { - x_low = x; - x_hi = x2; - } - else - { - x_low = x2; - x_hi = x; - } + // Buffer currently being mapped + wgpu::Buffer buffer; + // Label of the buffer currently being mapped. Used for printing errors + std::string bufferLabel; + // Size of the buffer being mapped in bytes + vtkIdType byteSize; - desc.Rect.Set(x, y, (x_hi - x_low) + 1, (y_hi - y_low) + 1); - return desc; -} + // Userdata passed to userCallback. This is typically the structure that contains the CPU-side + // buffer into which the data of the mapped buffer will be copied + void* userData; + // Bytes per row of the padded buffer that contains the mapped texture data + int bytesPerRow; + // Callback given by the user + vtkWebGPURenderWindow::TextureMapCallback userCallback; +}; } //------------------------------------------------------------------------------ @@ -150,8 +123,9 @@ void vtkWebGPURenderWindow::Initialize() } this->ConfigureSurface(); - this->CreateOffscreenColorAttachments(); + this->CreateOffscreenColorAttachment(); this->CreateDepthStencilTexture(); + this->CreateDepthStencilAttachment(); this->CreateFSQGraphicsPipeline(); this->InitializeRendererComputePipelines(); @@ -225,19 +199,19 @@ wgpu::TextureView vtkWebGPURenderWindow::GetOffscreenColorAttachmentView() //------------------------------------------------------------------------------ wgpu::TextureView vtkWebGPURenderWindow::GetDepthStencilView() { - return this->DepthStencil.View; + return this->DepthStencilAttachment.View; } //------------------------------------------------------------------------------ wgpu::TextureFormat vtkWebGPURenderWindow::GetDepthStencilFormat() { - return this->DepthStencil.Format; + return this->DepthStencilAttachment.Format; } //------------------------------------------------------------------------------ bool vtkWebGPURenderWindow::HasStencil() { - return this->DepthStencil.HasStencil; + return this->DepthStencilAttachment.HasStencil; } //------------------------------------------------------------------------------ @@ -283,7 +257,7 @@ vtkWebGPURenderWindow::AcquireDepthBufferRenderTexture() texture->SetAspect(vtkWebGPUComputeTextureView::TextureViewAspect::ASPECT_DEPTH); texture->SetLabel("Depth buffer render texture"); texture->SetType(vtkWebGPUComputeRenderTexture::RenderTextureType::DEPTH_BUFFER); - texture->SetWebGPUTexture(this->DepthStencil.Texture); + texture->SetWebGPUTexture(this->DepthStencilAttachment.Texture); texture->SetFormat(vtkWebGPUComputeTexture::TextureFormat::DEPTH_24_PLUS_8_STENCIL); this->ComputeRenderTextures.push_back(texture); @@ -392,7 +366,7 @@ void vtkWebGPURenderWindow::UnconfigureSurface() } //------------------------------------------------------------------------------ -void vtkWebGPURenderWindow::CreateDepthStencilTexture() +void vtkWebGPURenderWindow::CreateDepthStencilAttachment() { vtkDebugMacro(<< __func__ << '(' << this->SurfaceConfiguredSize[0] << ',' << this->SurfaceConfiguredSize[1] << ')'); @@ -406,7 +380,7 @@ void vtkWebGPURenderWindow::CreateDepthStencilTexture() // TODO: // setup basic depth attachment // todo: verify device supports this depth and stencil format in feature set - this->DepthStencil.HasStencil = true; + this->DepthStencilAttachment.HasStencil = true; const std::string textureLabel = "DepthStencil-" + this->GetObjectDescription(); wgpu::TextureDescriptor textureDesc; @@ -435,11 +409,11 @@ void vtkWebGPURenderWindow::CreateDepthStencilTexture() if (auto texture = this->WGPUConfiguration->CreateTexture(textureDesc)) { - this->DepthStencil.Texture = texture; + this->DepthStencilAttachment.Texture = texture; if (auto view = this->WGPUConfiguration->CreateView(texture, textureViewDesc)) { - this->DepthStencil.View = view; - this->DepthStencil.Format = textureDesc.format; + this->DepthStencilAttachment.View = view; + this->DepthStencilAttachment.Format = textureDesc.format; } else { @@ -455,15 +429,15 @@ void vtkWebGPURenderWindow::CreateDepthStencilTexture() } //------------------------------------------------------------------------------ -void vtkWebGPURenderWindow::DestroyDepthStencilTexture() +void vtkWebGPURenderWindow::DestroyDepthStencilAttachment() { vtkDebugMacro(<< __func__); - this->DepthStencil.View = nullptr; - this->DepthStencil.Texture = nullptr; + this->DepthStencilAttachment.View = nullptr; + this->DepthStencilAttachment.Texture = nullptr; } //------------------------------------------------------------------------------ -void vtkWebGPURenderWindow::CreateOffscreenColorAttachments() +void vtkWebGPURenderWindow::CreateOffscreenColorAttachment() { vtkWebGPUCheckUnconfigured(this); auto device = this->WGPUConfiguration->GetDevice(); @@ -509,26 +483,6 @@ void vtkWebGPURenderWindow::CreateOffscreenColorAttachments() { this->ColorAttachment.View = view; this->ColorAttachment.Format = textureDesc.format; - - // color attachment texture can be read into this buffer and then mapped into a CPU side - // buffer. - const auto alignedWidth = - vtkWebGPUConfiguration::Align(4 * this->ColorAttachment.Texture.GetWidth(), 256); - const std::string label = "OffscreenBuffer-" + this->GetObjectDescription(); - wgpu::BufferDescriptor buffDesc; - buffDesc.label = label.c_str(); - buffDesc.mappedAtCreation = false; - buffDesc.size = this->ColorAttachment.Texture.GetHeight() * alignedWidth; - buffDesc.usage = wgpu::BufferUsage::MapRead | wgpu::BufferUsage::CopyDst; - if (auto buffer = this->WGPUConfiguration->CreateBuffer(buffDesc)) - { - this->ColorAttachment.OffscreenBuffer = buffer; - } - else - { - vtkErrorMacro(<< "Failed to create a buffer for offscreen color attachment using device " - << device.Get()); - } } else { @@ -545,19 +499,14 @@ void vtkWebGPURenderWindow::CreateOffscreenColorAttachments() } //------------------------------------------------------------------------------ -void vtkWebGPURenderWindow::DestroyOffscreenColorAttachments() +void vtkWebGPURenderWindow::DestroyOffscreenColorAttachment() { - if (this->ColorAttachment.OffscreenBuffer.Get() != nullptr) - { - this->ColorAttachment.OffscreenBuffer.Destroy(); - this->ColorAttachment.OffscreenBuffer = nullptr; - } this->ColorAttachment.View = nullptr; this->ColorAttachment.Texture = nullptr; } //------------------------------------------------------------------------------ -void vtkWebGPURenderWindow::CreateFSQGraphicsPipeline() +void vtkWebGPURenderWindow::CreateColorCopyPipeline() { vtkWebGPUCheckUnconfigured(this); auto device = this->WGPUConfiguration->GetDevice(); @@ -572,21 +521,22 @@ void vtkWebGPURenderWindow::CreateFSQGraphicsPipeline() // clang-format off { 0, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float, wgpu::TextureViewDimension::e2D, /*multiSampled=*/false } // clang-format on - }); - bgl.SetLabel("FSQ bind group layout"); + }, + std::string("ColorCopy-bgl@") + this->GetObjectDescription()); wgpu::PipelineLayout pipelineLayout = vtkWebGPUPipelineLayoutInternals::MakeBasicPipelineLayout(device, &bgl); - pipelineLayout.SetLabel("FSQ graphics pipeline layout"); + pipelineLayout.SetLabel("FSQ Color Copy pipeline layout"); - this->FSQ.BindGroup = vtkWebGPUBindGroupInternals::MakeBindGroup(device, bgl, + this->ColorCopyRenderPipeline.BindGroup = vtkWebGPUBindGroupInternals::MakeBindGroup(device, bgl, { // clang-formt off { 0, this->ColorAttachment.View } // clang-format on - }); + }, + std::string("ColorCopy-bindgroup@") + this->GetObjectDescription()); - wgpu::ShaderModule shaderModule = vtkWebGPUShaderModuleInternals::CreateFromWGSL(device, R"( + const char* shaderSource = R"( struct VertexOutput { @builtin(position) position: vec4<f32>, } @@ -617,32 +567,24 @@ void vtkWebGPURenderWindow::CreateFSQGraphicsPipeline() let color = textureLoad(fsqTexture, texCoord, 0); return vec4<f32>(color); } - )"); - if (shaderModule == nullptr) - { - vtkErrorMacro(<< "Failed to create shader module for full-screen-quad graphics pipeline."); - return; - } + )"; + const std::string pipelineLabel = "ColorCopy-pipeline@" + this->GetObjectDescription(); vtkWebGPURenderPipelineDescriptorInternals pipelineDesc; - pipelineDesc.label = "FSQ Graphics pipeline description"; + pipelineDesc.label = pipelineLabel.c_str(); pipelineDesc.layout = pipelineLayout; - pipelineDesc.vertex.module = shaderModule; pipelineDesc.vertex.entryPoint = "vertexMain"; pipelineDesc.vertex.bufferCount = 0; - pipelineDesc.cFragment.module = shaderModule; pipelineDesc.cFragment.entryPoint = "fragmentMain"; pipelineDesc.cTargets[0].format = this->GetPreferredSurfaceTextureFormat(); pipelineDesc.DisableDepthStencil(); pipelineDesc.primitive.topology = wgpu::PrimitiveTopology::TriangleStrip; - if (auto pipeline = device.CreateRenderPipeline(&pipelineDesc)) - { - this->FSQ.Pipeline = pipeline; - } - else + const auto pipelineKey = this->WGPUPipelineCache->GetPipelineKey(&pipelineDesc, shaderSource); + if (this->ColorCopyRenderPipeline.Key != pipelineKey) { - vtkErrorMacro(<< "Failed to create the full-screen-quad render pipeline."); + this->WGPUPipelineCache->CreateRenderPipeline(&pipelineDesc, this, shaderSource); + this->ColorCopyRenderPipeline.Key = pipelineKey; } } @@ -663,7 +605,7 @@ void vtkWebGPURenderWindow::RecreateComputeRenderTextures() switch (renderTexture->GetType()) { case vtkWebGPUComputeRenderTexture::RenderTextureType::DEPTH_BUFFER: - renderTexture->SetWebGPUTexture(this->DepthStencil.Texture); + renderTexture->SetWebGPUTexture(this->DepthStencilAttachment.Texture); break; case vtkWebGPUComputeRenderTexture::RenderTextureType::COLOR_BUFFER: @@ -695,10 +637,11 @@ void vtkWebGPURenderWindow::RecreateComputeRenderTextures() } //------------------------------------------------------------------------------ -void vtkWebGPURenderWindow::DestroyFSQGraphicsPipeline() +void vtkWebGPURenderWindow::DestroyColorCopyPipeline() { - this->FSQ.BindGroup = nullptr; - this->FSQ.Pipeline = nullptr; + this->ColorCopyRenderPipeline.BindGroup = nullptr; + this->WGPUPipelineCache->DestroyRenderPipeline(this->ColorCopyRenderPipeline.Key); + this->ColorCopyRenderPipeline.Key.clear(); } //------------------------------------------------------------------------------ @@ -735,6 +678,124 @@ void vtkWebGPURenderWindow::PostRasterizationRender() } } +//------------------------------------------------------------------------------ +void vtkWebGPURenderWindow::ReadTextureFromGPU(wgpu::Texture& wgpuTexture, + wgpu::TextureFormat format, std::size_t mipLevel, wgpu::TextureAspect aspect, + wgpu::Origin3D offsets, wgpu::Extent3D extents, TextureMapCallback callback, void* userData) +{ + int bytesPerPixel = 0; + switch (format) + { + case wgpu::TextureFormat::RGBA8Unorm: + case wgpu::TextureFormat::BGRA8Unorm: + bytesPerPixel = 4; + break; + case wgpu::TextureFormat::RGBA32Uint: + bytesPerPixel = 16; + break; + case wgpu::TextureFormat::Depth24Plus: + bytesPerPixel = 3; + break; + case wgpu::TextureFormat::Depth24PlusStencil8: + bytesPerPixel = 4; + break; + case wgpu::TextureFormat::R32Uint: + bytesPerPixel = 4; + break; + default: + vtkErrorMacro(<< "Unhandled texture format in vtkWebGPUTexture::GetBytesPerPixel: " + << int(format)); + } + + // Bytes needs to be a multiple of 256 + vtkIdType bytesPerRow = vtkWebGPUConfiguration::Align(extents.width * bytesPerPixel, 256); + + // Creating the buffer that will hold the data of the texture + wgpu::BufferDescriptor bufferDescriptor; + bufferDescriptor.label = "Buffer descriptor for mapping texture"; + bufferDescriptor.mappedAtCreation = false; + bufferDescriptor.nextInChain = nullptr; + bufferDescriptor.size = bytesPerRow * extents.height; + bufferDescriptor.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::MapRead; + + wgpu::Buffer buffer = this->WGPUConfiguration->CreateBuffer(bufferDescriptor); + + // Parameters for copying the texture + wgpu::ImageCopyTexture imageCopyTexture; + imageCopyTexture.mipLevel = mipLevel; + imageCopyTexture.origin = offsets; + imageCopyTexture.texture = wgpuTexture; + imageCopyTexture.aspect = aspect; + + // Parameters for copying the buffer + unsigned int mipLevelWidth = std::floor(extents.width / std::pow(2, mipLevel)); + unsigned int mipLevelHeight = std::floor(extents.height / std::pow(2, mipLevel)); + wgpu::ImageCopyBuffer imageCopyBuffer; + imageCopyBuffer.buffer = buffer; + imageCopyBuffer.layout.nextInChain = nullptr; + imageCopyBuffer.layout.offset = 0; + imageCopyBuffer.layout.rowsPerImage = mipLevelHeight; + imageCopyBuffer.layout.bytesPerRow = bytesPerRow; + + // Copying the texture to the buffer + wgpu::CommandEncoder commandEncoder = this->WGPUConfiguration->GetDevice().CreateCommandEncoder(); + wgpu::Extent3D copySize = { mipLevelWidth, mipLevelHeight, extents.depthOrArrayLayers }; + commandEncoder.CopyTextureToBuffer(&imageCopyTexture, &imageCopyBuffer, ©Size); + + // Submitting the comand + wgpu::CommandBuffer commandBuffer = commandEncoder.Finish(); + this->WGPUConfiguration->GetDevice().GetQueue().Submit(1, &commandBuffer); + + auto bufferMapCallback = [](WGPUBufferMapAsyncStatus status, void* userData2) + { + InternalMapTextureAsyncData* mapData = + reinterpret_cast<InternalMapTextureAsyncData*>(userData2); + + if (status == WGPUBufferMapAsyncStatus_Success) + { + const void* mappedRange = mapData->buffer.GetConstMappedRange(0, mapData->byteSize); + mapData->userCallback(mappedRange, mapData->bytesPerRow, mapData->userData); + + mapData->buffer.Unmap(); + // Freeing the callbackData structure as it was dynamically allocated + delete mapData; + } + else + { + vtkLogF(WARNING, "Could not map texture '%s' with error status: %d", + mapData->bufferLabel.empty() ? "(nolabel)" : mapData->bufferLabel.c_str(), status); + + // Freeing the callbackData structure as it was dynamically allocated + delete mapData; + } + }; + + // Now mapping the buffer that contains the texture data to the CPU + // Dynamically allocating here because we callbackData to stay alive even after exiting this + // function (because buffer.MapAsync is asynchronous). buffer.MapAsync() also takes a raw pointer + // so we cannot use smart pointers here + InternalMapTextureAsyncData* callbackData = new InternalMapTextureAsyncData; + callbackData->buffer = buffer; + callbackData->bufferLabel = "ReadTextureFromGPU map buffer"; + callbackData->byteSize = bufferDescriptor.size; + callbackData->bytesPerRow = bytesPerRow; + callbackData->userCallback = callback; + callbackData->userData = userData; + + buffer.MapAsync(wgpu::MapMode::Read, 0, bufferDescriptor.size, bufferMapCallback, callbackData); +} + +//------------------------------------------------------------------------------ +void vtkWebGPURenderWindow::ReadTextureFromGPU(wgpu::Texture& wgpuTexture, + wgpu::TextureFormat format, std::size_t mipLevel, wgpu::TextureAspect aspect, + vtkWebGPURenderWindow::TextureMapCallback callback, void* userData) +{ + return this->ReadTextureFromGPU(wgpuTexture, format, mipLevel, aspect, wgpu::Origin3D{ 0, 0, 0 }, + wgpu::Extent3D{ + wgpuTexture.GetWidth(), wgpuTexture.GetHeight(), wgpuTexture.GetDepthOrArrayLayers() }, + callback, userData); +} + //------------------------------------------------------------------------------ void vtkWebGPURenderWindow::RenderOffscreenTexture() { @@ -790,13 +851,13 @@ void vtkWebGPURenderWindow::RenderOffscreenTexture() "texture is null!"); return; } - if (this->FSQ.Pipeline == nullptr) + if (this->ColorCopyRenderPipeline.Key.empty()) { vtkErrorMacro(<< "Cannot render offscreen texture because the full-screen-quad render " - "pipeline is null!"); + "pipeline is not ready!"); return; } - if (this->FSQ.BindGroup == nullptr) + if (this->ColorCopyRenderPipeline.BindGroup == nullptr) { vtkErrorMacro(<< "Cannot render offscreen texture because the full-screen-quad render bind " "group is null!"); @@ -829,9 +890,11 @@ void vtkWebGPURenderWindow::RenderOffscreenTexture() #ifndef NDEBUG encoder.PushDebugGroup("FSQ Render"); #endif - encoder.SetPipeline(this->FSQ.Pipeline); + const auto pipeline = + this->WGPUPipelineCache->GetRenderPipeline(this->ColorCopyRenderPipeline.Key); + encoder.SetPipeline(pipeline); // bind fsq group - encoder.SetBindGroup(0, this->FSQ.BindGroup); + encoder.SetBindGroup(0, this->ColorCopyRenderPipeline.BindGroup); // draw triangle strip encoder.Draw(4); #ifndef NDEBUG @@ -897,14 +960,14 @@ void vtkWebGPURenderWindow::Start() this->Size[1] != this->SurfaceConfiguredSize[1]) { // Window's size changed, need to recreate the swap chain, textures, ... - this->DestroyFSQGraphicsPipeline(); - this->DestroyDepthStencilTexture(); - this->DestroyOffscreenColorAttachments(); + this->DestroyColorCopyPipeline(); + this->DestroyDepthStencilAttachment(); + this->DestroyOffscreenColorAttachment(); this->UnconfigureSurface(); this->ConfigureSurface(); - this->CreateOffscreenColorAttachments(); - this->CreateDepthStencilTexture(); - this->CreateFSQGraphicsPipeline(); + this->CreateOffscreenColorAttachment(); + this->CreateDepthStencilAttachment(); + this->CreateColorCopyPipeline(); this->RecreateComputeRenderTextures(); } @@ -916,11 +979,6 @@ void vtkWebGPURenderWindow::Frame() { vtkDebugMacro(<< __func__); vtkWebGPUCheckUnconfigured(this); - if (this->CommandEncoder == nullptr) - { - vtkErrorMacro(<< "Cannot render frame because the command encoder is null!"); - return; - } if (this->Surface == nullptr) { vtkErrorMacro(<< "Cannot render frame because the surface is null!"); @@ -928,12 +986,16 @@ void vtkWebGPURenderWindow::Frame() } this->Superclass::Frame(); - // Flushing the commands for the props to be rendered wgpu::CommandBufferDescriptor cmdBufDesc = {}; - wgpu::CommandBuffer cmdBuffer = this->CommandEncoder.Finish(&cmdBufDesc); + wgpu::CommandBuffer cmdBuffer; + // Flushing the commands for the props to be rendered + if (this->CommandEncoder != nullptr) + { + cmdBuffer = this->CommandEncoder.Finish(&cmdBufDesc); - this->CommandEncoder = nullptr; - this->FlushCommandBuffers(1, &cmdBuffer); + this->CommandEncoder = nullptr; + this->FlushCommandBuffers(1, &cmdBuffer); + } this->PostRenderComputePipelines(); this->PostRasterizationRender(); @@ -960,6 +1022,8 @@ void vtkWebGPURenderWindow::Frame() this->StagingPixelData.Buffer = nullptr; } + this->ReleaseRGBAPixelData(nullptr); + #ifndef NDEBUG // This lets the implementation execute all callbacks so that validation errors are output in // the console. @@ -1013,224 +1077,98 @@ const char* vtkWebGPURenderWindow::GetRenderingBackend() return ""; } -//------------------------------------------------------------------------------ -void vtkWebGPURenderWindow::ReadPixels() -{ - vtkWebGPUCheckUnconfigured(this); - - this->CopyFramebufferToOffscreenBuffer(); - - if (this->ColorAttachment.OffscreenBuffer == nullptr) - { - vtkErrorMacro(<< "Cannot read pixels from texture because the color attachment's offscreen " - "buffer is null!"); - return; - } - if (this->CachedPixelBytes->GetNumberOfValues() > 0) - { - // use cache - return; - } - this->BufferMapReadContext.src = this->ColorAttachment.OffscreenBuffer; - this->BufferMapReadContext.size = this->ColorAttachment.OffscreenBuffer.GetSize(); - this->BufferMapReadContext.dst = this->CachedPixelBytes; - this->BufferMapReadContext.window = this; - - auto onBufferMapped = [](WGPUBufferMapAsyncStatus status, void* userdata) - { - auto ctx = reinterpret_cast<MappingContext*>(userdata); - if (ctx == nullptr) - { - vtkErrorWithObjectMacro(nullptr, << "Unexpected user data from buffer mapped callback in " - "vtkWebGPURenderWindow::ReadPixels"); - return; - } - if (!ctx->window) - { - vtkErrorWithObjectMacro(nullptr, - << "Mapping context in vtkWebGPURenderWindow::ReadPixels is missing render window!"); - return; - } - if (!ctx->dst) - { - vtkErrorWithObjectMacro( - ctx->window, << "Mapping context in vtkWebGPURenderWindow::ReadPixels is missing " - "destination vtkDataArray object!"); - return; - } - if (!ctx->src) - { - vtkErrorWithObjectMacro( - ctx->window, << "Mapping context in vtkWebGPURenderWindow::ReadPixels is missing " - "source WGPUbuffer object!"); - return; - } - switch (status) - { - case WGPUBufferMapAsyncStatus_ValidationError: - vtkErrorWithObjectMacro(ctx->window, << "Validation error occurred"); - break; - case WGPUBufferMapAsyncStatus_Unknown: - vtkErrorWithObjectMacro(ctx->window, << "Unknown error occurred"); - break; - case WGPUBufferMapAsyncStatus_DeviceLost: - vtkErrorWithObjectMacro(ctx->window, << "Device lost!"); - break; - case WGPUBufferMapAsyncStatus_DestroyedBeforeCallback: - vtkErrorWithObjectMacro(ctx->window, << "Buffer destroyed before callback"); - break; - case WGPUBufferMapAsyncStatus_UnmappedBeforeCallback: - vtkErrorWithObjectMacro(ctx->window, << "Buffer unmapped before callback"); - break; - case WGPUBufferMapAsyncStatus_MappingAlreadyPending: - vtkErrorWithObjectMacro(ctx->window, << "Buffer already has a mapping pending completion"); - break; - case WGPUBufferMapAsyncStatus_OffsetOutOfRange: - vtkErrorWithObjectMacro(ctx->window, << "Buffer offset out of range"); - break; - case WGPUBufferMapAsyncStatus_SizeOutOfRange: - vtkErrorWithObjectMacro(ctx->window, << "Buffer size out of range"); - break; - case WGPUBufferMapAsyncStatus_Success: - { - // acquire a const mapped range since OffscreenBuffer is assigned a `MapRead` usage. - auto mapped = - reinterpret_cast<const vtkTypeUInt8*>(ctx->src.GetConstMappedRange(0, ctx->size)); - if (mapped == nullptr) - { - vtkErrorWithObjectMacro(ctx->window, << "Mapped range returned null!"); - break; - } - else - { - // allocate sufficient space on host. - ctx->dst->SetNumberOfValues(ctx->size); - // These are plain bytes. GetABCDPixelData() functions know how to interpret them. - std::copy(mapped, mapped + ctx->size, ctx->dst->GetPointer(0)); - } - } - break; - default: - break; - } - ctx->src.Unmap(); - }; - this->ColorAttachment.OffscreenBuffer.MapAsync(wgpu::MapMode::Read, 0, - this->BufferMapReadContext.size, onBufferMapped, &this->BufferMapReadContext); - this->WaitForCompletion(); -} - -//------------------------------------------------------------------------------ -void vtkWebGPURenderWindow::CopyFramebufferToOffscreenBuffer() -{ - if (this->ColorAttachment.OffscreenBuffer == nullptr) - { - vtkErrorMacro(<< "Cannot copy offscreen texture into offscreen buffer because the destination " - "buffer is null!"); - return; - } - - // Now copy the contents of the color attachment texture into the offscreen buffer. - // Both source and destination are on the GPU. - // Later, when we really need the pixels on the CPU, the `ReadPixels` method will map - // the contents of the offscreen buffer into CPU memory. - wgpu::Origin3D srcOrigin; - srcOrigin.x = 0; - srcOrigin.y = 0; - srcOrigin.y = 0; - - wgpu::Extent3D srcExtent; - srcExtent.width = this->ColorAttachment.Texture.GetWidth(); - srcExtent.height = this->ColorAttachment.Texture.GetHeight(); - srcExtent.depthOrArrayLayers = 1; - - wgpu::ImageCopyTexture copySrc; - copySrc.texture = this->ColorAttachment.Texture; - copySrc.mipLevel = 0; - copySrc.origin = srcOrigin; - copySrc.aspect = wgpu::TextureAspect::All; - - wgpu::TextureDataLayout textureDataLayout; - textureDataLayout.offset = 0; - textureDataLayout.bytesPerRow = - vtkWebGPUConfiguration::Align(4 * this->ColorAttachment.Texture.GetWidth(), 256); - textureDataLayout.rowsPerImage = this->ColorAttachment.Texture.GetHeight(); - - wgpu::ImageCopyBuffer copyDst; - copyDst.buffer = this->ColorAttachment.OffscreenBuffer; - copyDst.layout = textureDataLayout; - - this->CreateCommandEncoder(); -#ifndef NDEBUG - this->CommandEncoder.PushDebugGroup("Copy color attachment to offscreen buffer"); -#endif - this->CommandEncoder.CopyTextureToBuffer(©Src, ©Dst, &srcExtent); -#ifndef NDEBUG - this->CommandEncoder.PopDebugGroup(); -#endif - - wgpu::CommandBufferDescriptor cmdBufDesc = {}; - wgpu::CommandBuffer cmdBuffer = this->CommandEncoder.Finish(&cmdBufDesc); - - this->CommandEncoder = nullptr; - this->FlushCommandBuffers(1, &cmdBuffer); -} - //------------------------------------------------------------------------------ unsigned char* vtkWebGPURenderWindow::GetPixelData( - int x, int y, int x2, int y2, int front, int right) + int x1, int y1, int x2, int y2, int front, int right) { (void)front; (void)right; - this->ReadPixels(); - PixelReadDescriptor desc = ::GetPixelReadDesriptor(this->ColorAttachment.Texture, x, y, x2, y2); - unsigned char* pixels = new unsigned char[desc.Rect.GetWidth() * desc.Rect.GetHeight() * 3]; - int componentMap[3] = {}; + int width = x2 - x1 + 1; + int height = y2 - y1 + 1; + const int outNumberOfComponents = 3; + int inNumberOfComponents = 0; + struct CallbackData + { + unsigned char* outputValues; + int xMin; + int xMax; + int yMin; + int yMax; + int componentMap[3] = {}; + }; + CallbackData callbackData; + callbackData.outputValues = new unsigned char[width * height * outNumberOfComponents]; + callbackData.xMin = x1; + callbackData.xMax = x2; + callbackData.yMin = y1; + callbackData.yMax = y2; if (this->ColorAttachment.Format == wgpu::TextureFormat::BGRA8Unorm) { - componentMap[0] = 2; - componentMap[1] = 1; - componentMap[2] = 0; + callbackData.componentMap[0] = 2; + callbackData.componentMap[1] = 1; + callbackData.componentMap[2] = 0; + inNumberOfComponents = 4; } else if (this->ColorAttachment.Format == wgpu::TextureFormat::RGBA8Unorm) { - componentMap[0] = 0; - componentMap[1] = 1; - componentMap[2] = 2; + callbackData.componentMap[0] = 0; + callbackData.componentMap[1] = 1; + callbackData.componentMap[2] = 2; + inNumberOfComponents = 4; } else { // TODO: Handle other formats. vtkErrorMacro(<< "Unsupported offscreen texture format!"); + return callbackData.outputValues; } - vtkIdType dstIdx = 0; - for (int j = desc.Rect.GetY(); j < desc.Rect.GetTop(); ++j) + auto onTextureMapped = [inNumberOfComponents]( + const void* mappedData, int bytesPerRow, void* userData) { - for (int i = desc.Rect.GetX(); i < desc.Rect.GetRight(); ++i) + CallbackData* callbackDataPtr = reinterpret_cast<CallbackData*>(userData); + unsigned char* outputValues = callbackDataPtr->outputValues; + const unsigned char* mappedDataChar = reinterpret_cast<const unsigned char*>(mappedData); + + // Copying the RGB channels of each pixel + vtkIdType dstIdx = 0; + for (int y = callbackDataPtr->yMin; y <= callbackDataPtr->yMax; y++) { - for (auto& comp : componentMap) + for (int x = callbackDataPtr->xMin; x <= callbackDataPtr->xMax; x++) { - pixels[dstIdx++] = this->CachedPixelBytes->GetValue( - j * desc.NumBytesPerRow + i * desc.NumColorComponents + comp); + // Dividing by inNumberOfComponents * sizeof(SampleType) here because we want to multiply Y + // by the 'width' which is in number of pixels (ex: for RGBA=4, for RGB=3) + const int mappedIndex = + x + y * (bytesPerRow / (inNumberOfComponents * sizeof(unsigned char))); + // Copying the RGB channels of each pixel + for (auto& comp : callbackDataPtr->componentMap) + { + outputValues[dstIdx++] = mappedDataChar[mappedIndex * inNumberOfComponents + comp]; + } } } - } - return pixels; + }; + + this->ReadTextureFromGPU(this->ColorAttachment.Texture, this->ColorAttachment.Format, 0, + wgpu::TextureAspect::All, onTextureMapped, &callbackData); + this->WaitForCompletion(); + return callbackData.outputValues; } //------------------------------------------------------------------------------ int vtkWebGPURenderWindow::GetPixelData( - int x, int y, int x2, int y2, int front, vtkUnsignedCharArray* data, int right) -{ - PixelReadDescriptor desc = ::GetPixelReadDesriptor(this->ColorAttachment.Texture, x, y, x2, y2); - data->SetNumberOfComponents(3); - data->SetNumberOfTuples(desc.Rect.GetWidth() * desc.Rect.GetHeight()); - unsigned char* pixels = this->GetPixelData(x, y, x2, y2, front, right); + int x1, int y1, int x2, int y2, int front, vtkUnsignedCharArray* data, int right) +{ + int width = x2 - x1 + 1; + int height = y2 - y1 + 1; + const int numberOfComponents = 3; + data->SetNumberOfComponents(numberOfComponents); + data->SetNumberOfTuples(width * height); + unsigned char* pixels = this->GetPixelData(x1, y1, x2, y2, front, right); // take ownership of pixels - data->SetArray(pixels, desc.Rect.GetWidth() * desc.Rect.GetHeight() * 3, 0); + data->SetArray(pixels, width * height * numberOfComponents, 0); return data->GetNumberOfValues(); } @@ -1328,62 +1266,98 @@ int vtkWebGPURenderWindow::SetPixelData( //------------------------------------------------------------------------------ float* vtkWebGPURenderWindow::GetRGBAPixelData( - int x, int y, int x2, int y2, int front, int right /*=0*/) + int x1, int y1, int x2, int y2, int front, int right /*=0*/) { (void)front; (void)right; - this->ReadPixels(); - PixelReadDescriptor desc = ::GetPixelReadDesriptor(this->ColorAttachment.Texture, x, y, x2, y2); - float* pixels = new float[desc.Rect.GetWidth() * desc.Rect.GetHeight() * 4]; - int componentMap[4] = {}; + int width = x2 - x1 + 1; + int height = y2 - y1 + 1; + const int outNumberOfComponents = 4; + int inNumberOfComponents = 0; + struct CallbackData + { + float* outputValues; + int xMin; + int xMax; + int yMin; + int yMax; + int componentMap[4] = {}; + }; + CallbackData callbackData; + callbackData.outputValues = new float[width * height * outNumberOfComponents]; + callbackData.xMin = x1; + callbackData.xMax = x2; + callbackData.yMin = y1; + callbackData.yMax = y2; if (this->ColorAttachment.Format == wgpu::TextureFormat::BGRA8Unorm) { - componentMap[0] = 2; - componentMap[1] = 1; - componentMap[2] = 0; - componentMap[3] = 3; + callbackData.componentMap[0] = 2; + callbackData.componentMap[1] = 1; + callbackData.componentMap[2] = 0; + callbackData.componentMap[3] = 3; + inNumberOfComponents = 4; } else if (this->ColorAttachment.Format == wgpu::TextureFormat::RGBA8Unorm) { - componentMap[0] = 0; - componentMap[1] = 1; - componentMap[2] = 2; - componentMap[3] = 3; + callbackData.componentMap[0] = 0; + callbackData.componentMap[1] = 1; + callbackData.componentMap[2] = 2; + callbackData.componentMap[3] = 3; + inNumberOfComponents = 4; } else { // TODO: Handle other formats. vtkErrorMacro(<< "Unsupported offscreen texture format!"); + return callbackData.outputValues; } - vtkIdType dstIdx = 0; - for (int j = desc.Rect.GetY(); j < desc.Rect.GetTop(); ++j) + auto onTextureMapped = [inNumberOfComponents]( + const void* mappedData, int bytesPerRow, void* userData) { - for (int i = desc.Rect.GetX(); i < desc.Rect.GetRight(); ++i) + CallbackData* callbackDataPtr = reinterpret_cast<CallbackData*>(userData); + float* outputValues = callbackDataPtr->outputValues; + const unsigned char* mappedDataChar = reinterpret_cast<const unsigned char*>(mappedData); + + vtkIdType dstIdx = 0; + for (int y = callbackDataPtr->yMin; y <= callbackDataPtr->yMax; y++) { - for (auto& comp : componentMap) + for (int x = callbackDataPtr->xMin; x <= callbackDataPtr->xMax; x++) { - pixels[dstIdx++] = this->CachedPixelBytes->GetValue( - j * desc.NumBytesPerRow + i * desc.NumColorComponents + comp) / - 255.0; + // Dividing by inNumberOfComponents * sizeof(SampleType) here because we want to multiply Y + // by the 'width' which is in number of pixels (ex: for RGBA=4, for RGB=3) + const int mappedIndex = + x + y * (bytesPerRow / (inNumberOfComponents * sizeof(unsigned char))); + // Copying the RGBA channels of each pixel + for (auto& comp : callbackDataPtr->componentMap) + { + outputValues[dstIdx++] = + mappedDataChar[mappedIndex * inNumberOfComponents + comp] / 255.0f; + } } } - } - return pixels; + }; + + this->ReadTextureFromGPU(this->ColorAttachment.Texture, this->ColorAttachment.Format, 0, + wgpu::TextureAspect::All, onTextureMapped, &callbackData); + this->WaitForCompletion(); + return callbackData.outputValues; } //------------------------------------------------------------------------------ int vtkWebGPURenderWindow::GetRGBAPixelData( - int x, int y, int x2, int y2, int front, vtkFloatArray* data, int right /*=0*/) -{ - PixelReadDescriptor desc = ::GetPixelReadDesriptor(this->ColorAttachment.Texture, x, y, x2, y2); - data->SetNumberOfComponents(4); - data->SetNumberOfTuples(desc.Rect.GetWidth() * desc.Rect.GetHeight()); - float* pixels = this->GetRGBAPixelData(x, y, x2, y2, front, right); + int x1, int y1, int x2, int y2, int front, vtkFloatArray* data, int right /*=0*/) +{ + int width = x2 - x1 + 1; + int height = y2 - y1 + 1; + const int numberOfComponents = 4; + data->SetNumberOfComponents(numberOfComponents); + data->SetNumberOfTuples(width * height); + float* pixels = this->GetRGBAPixelData(x1, y1, x2, y2, front, right); // take ownership of pixels - data->SetArray(pixels, desc.Rect.GetWidth() * desc.Rect.GetHeight() * 4, 0); + data->SetArray(pixels, width * height * numberOfComponents, 0); return data->GetNumberOfValues(); } @@ -1488,67 +1462,102 @@ int vtkWebGPURenderWindow::SetRGBAPixelData( //------------------------------------------------------------------------------ void vtkWebGPURenderWindow::ReleaseRGBAPixelData(float* data) { - (void)data; - // reset cache - this->CachedPixelBytes->SetNumberOfValues(0); + delete[] data; } unsigned char* vtkWebGPURenderWindow::GetRGBACharPixelData( - int x, int y, int x2, int y2, int front, int right /*=0*/) + int x1, int y1, int x2, int y2, int front, int right /*=0*/) { (void)front; (void)right; - this->ReadPixels(); - PixelReadDescriptor desc = ::GetPixelReadDesriptor(this->ColorAttachment.Texture, x, y, x2, y2); - unsigned char* pixels = new unsigned char[desc.Rect.GetWidth() * desc.Rect.GetHeight() * 4]; - int componentMap[4] = {}; + int width = x2 - x1 + 1; + int height = y2 - y1 + 1; + const int outNumberOfComponents = 4; + int inNumberOfComponents = 0; + struct CallbackData + { + unsigned char* outputValues; + int xMin; + int xMax; + int yMin; + int yMax; + int componentMap[4] = {}; + }; + CallbackData callbackData; + callbackData.outputValues = new unsigned char[width * height * outNumberOfComponents]; + callbackData.xMin = x1; + callbackData.xMax = x2; + callbackData.yMin = y1; + callbackData.yMax = y2; if (this->ColorAttachment.Format == wgpu::TextureFormat::BGRA8Unorm) { - componentMap[0] = 2; - componentMap[1] = 1; - componentMap[2] = 0; - componentMap[3] = 3; + callbackData.componentMap[0] = 2; + callbackData.componentMap[1] = 1; + callbackData.componentMap[2] = 0; + callbackData.componentMap[3] = 3; + inNumberOfComponents = 4; } else if (this->ColorAttachment.Format == wgpu::TextureFormat::RGBA8Unorm) { - componentMap[0] = 0; - componentMap[1] = 1; - componentMap[2] = 2; - componentMap[3] = 3; + callbackData.componentMap[0] = 0; + callbackData.componentMap[1] = 1; + callbackData.componentMap[2] = 2; + callbackData.componentMap[3] = 3; + inNumberOfComponents = 4; } else { // TODO: Handle other formats. vtkErrorMacro(<< "Unsupported offscreen texture format!"); + return callbackData.outputValues; } - vtkIdType dstIdx = 0; - for (int j = desc.Rect.GetY(); j < desc.Rect.GetTop(); ++j) + auto onTextureMapped = [inNumberOfComponents]( + const void* mappedData, int bytesPerRow, void* userData) { - for (int i = desc.Rect.GetX(); i < desc.Rect.GetRight(); ++i) + CallbackData* callbackDataPtr = reinterpret_cast<CallbackData*>(userData); + unsigned char* outputValues = callbackDataPtr->outputValues; + const unsigned char* mappedDataChar = reinterpret_cast<const unsigned char*>(mappedData); + + // Copying the RGB channels of each pixel + vtkIdType dstIdx = 0; + for (int y = callbackDataPtr->yMin; y <= callbackDataPtr->yMax; y++) { - for (auto& comp : componentMap) + for (int x = callbackDataPtr->xMin; x <= callbackDataPtr->xMax; x++) { - pixels[dstIdx++] = this->CachedPixelBytes->GetValue( - j * desc.NumBytesPerRow + i * desc.NumColorComponents + comp); + // Dividing by inNumberOfComponents * sizeof(SampleType) here because we want to multiply Y + // by the 'width' which is in number of pixels (ex: for RGBA=4, for RGB=3) + const int mappedIndex = + x + y * (bytesPerRow / (inNumberOfComponents * sizeof(unsigned char))); + // Copying the RGBA channels of each pixel + for (auto& comp : callbackDataPtr->componentMap) + { + outputValues[dstIdx++] = mappedDataChar[mappedIndex * inNumberOfComponents + comp]; + } } } - } - return pixels; + }; + + this->ReadTextureFromGPU(this->ColorAttachment.Texture, this->ColorAttachment.Format, 0, + wgpu::TextureAspect::All, onTextureMapped, &callbackData); + this->WaitForCompletion(); + return callbackData.outputValues; } //------------------------------------------------------------------------------ int vtkWebGPURenderWindow::GetRGBACharPixelData( - int x, int y, int x2, int y2, int front, vtkUnsignedCharArray* data, int right /*=0*/) -{ - PixelReadDescriptor desc = ::GetPixelReadDesriptor(this->ColorAttachment.Texture, x, y, x2, y2); - data->SetNumberOfComponents(4); - data->SetNumberOfTuples(desc.Rect.GetWidth() * desc.Rect.GetHeight()); - unsigned char* pixels = this->GetRGBACharPixelData(x, y, x2, y2, front, right); + int x1, int y1, int x2, int y2, int front, vtkUnsignedCharArray* data, int right /*=0*/) +{ + int width = x2 - x1 + 1; + int height = y2 - y1 + 1; + const int numberOfComponents = 4; + data->SetNumberOfComponents(numberOfComponents); + data->SetNumberOfTuples(width * height); + unsigned char* pixels = this->GetRGBACharPixelData(x1, y1, x2, y2, front, right); // take ownership of pixels - data->SetArray(pixels, desc.Rect.GetWidth() * desc.Rect.GetHeight() * 4, 0); + data->SetArray(pixels, width * height * numberOfComponents, 0); return data->GetNumberOfValues(); } @@ -1586,7 +1595,7 @@ int vtkWebGPURenderWindow::SetRGBACharPixelData( << device.Get()); return 0; } - auto mapped = + auto* mapped = reinterpret_cast<unsigned char*>(this->StagingPixelData.Buffer.GetMappedRange(0, size)); if (mapped == nullptr) { @@ -1656,21 +1665,124 @@ int vtkWebGPURenderWindow::SetRGBACharPixelData(int x, int y, int x2, int y2, } //------------------------------------------------------------------------------ -float* vtkWebGPURenderWindow::GetZbufferData(int, int, int, int) +float* vtkWebGPURenderWindow::GetZbufferData(int x1, int y1, int x2, int y2) { - return nullptr; + int width = x2 - x1 + 1; + int height = y2 - y1 + 1; + float* zValues = new float[width * height]; + this->GetZbufferData(x1, y1, x2, y2, zValues); + return zValues; } //------------------------------------------------------------------------------ -int vtkWebGPURenderWindow::GetZbufferData(int, int, int, int, float*) +int vtkWebGPURenderWindow::GetZbufferData(int x1, int y1, int x2, int y2, float* zValues) { - return 0; + // Create a compute pipeline + if (this->DepthCopyPipeline == nullptr) + { + this->DepthCopyPipeline = vtk::TakeSmartPointer(vtkWebGPUComputePipeline::New()); + this->DepthCopyPipeline->SetLabel("DepthCopy-computepipeline@" + this->GetObjectDescription()); + this->DepthCopyPipeline->SetWGPUConfiguration(this->WGPUConfiguration); + } + unsigned int textureWidth = 0; + + // Create a compute pass which copies the depth texture values into a wgpu::Buffer + if (this->DepthCopyPass == nullptr) + { + this->DepthCopyPass = this->DepthCopyPipeline->CreateComputePass(); + this->DepthCopyPass->SetLabel("DepthCopy-computepass@" + this->GetObjectDescription()); + vtkSmartPointer<vtkWebGPUComputeRenderTexture> depthTexture; + depthTexture = this->AcquireDepthBufferRenderTexture(); + textureWidth = depthTexture->GetWidth(); + + depthTexture->SetLabel("DepthCopy-texture@" + this->GetObjectDescription()); + this->DepthCopyPass->SetShaderSource(CopyDepthTextureToBuffer); + this->DepthCopyPass->SetShaderEntryPoint("computeMain"); + this->DepthCopyTextureIndex = this->DepthCopyPass->AddRenderTexture(depthTexture); + + auto depthTextureView = this->DepthCopyPass->CreateTextureView(this->DepthCopyTextureIndex); + depthTextureView->SetGroup(0); + depthTextureView->SetBinding(0); + depthTextureView->SetLabel("DepthCopy-textureview@" + this->GetObjectDescription()); + depthTextureView->SetMode(vtkWebGPUTextureView::TextureViewMode::READ_ONLY); + depthTextureView->SetAspect(vtkWebGPUTextureView::TextureViewAspect::ASPECT_DEPTH); + depthTextureView->SetFormat(vtkWebGPUTexture::TextureFormat::DEPTH_24_PLUS); + this->DepthCopyPass->AddTextureView(depthTextureView); + + vtkNew<vtkWebGPUComputeBuffer> buffer; + buffer->SetGroup(0); + buffer->SetBinding(1); + buffer->SetLabel("DepthCopy-buffer@" + this->GetObjectDescription()); + buffer->SetMode(vtkWebGPUComputeBuffer::BufferMode::READ_WRITE_MAP_COMPUTE_STORAGE); + buffer->SetByteSize( + depthTexture->GetBytesPerPixel() * textureWidth * depthTexture->GetHeight()); + + this->DepthCopyBufferIndex = this->DepthCopyPass->AddBuffer(buffer); + } + else + { + // Resize depth buffer if needed. + auto depthTexture = this->DepthCopyPass->GetComputeTexture(this->DepthCopyTextureIndex); + textureWidth = depthTexture->GetWidth(); + + const auto byteSize = + depthTexture->GetBytesPerPixel() * textureWidth * depthTexture->GetHeight(); + if (this->DepthCopyPass->GetBufferByteSize(this->DepthCopyBufferIndex) != byteSize) + { + this->DepthCopyPass->ResizeBuffer(this->DepthCopyBufferIndex, byteSize); + } + } + + int nbGroupsX = std::ceil(this->SurfaceConfiguredSize[0] / 8.0f); + int nbGroupsY = std::ceil(this->SurfaceConfiguredSize[1] / 8.0f); + this->DepthCopyPass->SetWorkgroups(nbGroupsX, nbGroupsY, 1); + + this->DepthCopyPass->Dispatch(); + + struct CallbackData + { + float* outputValues; + int xMin; + int xMax; + int yMin; + int yMax; + unsigned int width; + }; + auto onBufferMapped = [](const void* mappedData, void* userData) + { + CallbackData* callbackData = reinterpret_cast<CallbackData*>(userData); + float* outputValues = callbackData->outputValues; + const float* mappedDataAsF32 = reinterpret_cast<const float*>(mappedData); + vtkIdType dstIdx = 0; + for (int y = callbackData->yMin; y <= callbackData->yMax; y++) + { + for (int x = callbackData->xMin; x <= callbackData->xMax; x++) + { + const int mappedIndex = x + y * callbackData->width; + outputValues[dstIdx++] = mappedDataAsF32[mappedIndex]; + } + } + }; + CallbackData callbackData; + callbackData.xMin = x1; + callbackData.xMax = x2; + callbackData.yMin = y1; + callbackData.yMax = y2; + callbackData.outputValues = zValues; + callbackData.width = textureWidth; + this->DepthCopyPass->ReadBufferFromGPU(this->DepthCopyBufferIndex, onBufferMapped, &callbackData); + this->DepthCopyPipeline->Update(); + return VTK_OK; } //------------------------------------------------------------------------------ -int vtkWebGPURenderWindow::GetZbufferData(int, int, int, int, vtkFloatArray*) +int vtkWebGPURenderWindow::GetZbufferData(int x1, int y1, int x2, int y2, vtkFloatArray* buffer) { - return 0; + int width = x2 - x1 + 1; + int height = y2 - y1 + 1; + buffer->SetNumberOfComponents(1); + buffer->SetNumberOfTuples(width * height); + return this->GetZbufferData(x1, y1, x2, y2, buffer->GetPointer(0)); } //------------------------------------------------------------------------------ @@ -1716,7 +1828,7 @@ void vtkWebGPURenderWindow::WaitForCompletion() // removed unused signalValue argument from 3.1.54 onwards. 0u, #endif - [](WGPUQueueWorkDoneStatus, void* userdata) { *static_cast<bool*>(userdata) = true; }, &done); + [](WGPUQueueWorkDoneStatus, void* userData) { *static_cast<bool*>(userData) = true; }, &done); while (!done) { this->WGPUConfiguration->ProcessEvents(); @@ -1763,12 +1875,15 @@ void vtkWebGPURenderWindow::ReleaseGraphicsResources(vtkWindow* w) { renderer->ReleaseGraphicsResources(this); } + + this->DepthCopyPass = nullptr; + this->DepthCopyPipeline = nullptr; + this->WGPUPipelineCache->ReleaseGraphicsResources(w); - this->DestroyFSQGraphicsPipeline(); - this->DestroyDepthStencilTexture(); - this->DestroyOffscreenColorAttachments(); + this->DestroyColorCopyPipeline(); + this->DestroyDepthStencilAttachment(); + this->DestroyOffscreenColorAttachment(); this->UnconfigureSurface(); - this->BufferMapReadContext.src = nullptr; } //------------------------------------------------------------------------------ diff --git a/Rendering/WebGPU/vtkWebGPURenderWindow.h b/Rendering/WebGPU/vtkWebGPURenderWindow.h index 891e035b5a637d60be28e5804179fc11aadb9351..17414a5ee1b03e9b1830334419b372bc13f7477f 100644 --- a/Rendering/WebGPU/vtkWebGPURenderWindow.h +++ b/Rendering/WebGPU/vtkWebGPURenderWindow.h @@ -85,11 +85,6 @@ public: const char* GetRenderingBackend() override; - /** - * Reads pixels into the `CachedPixelBytes` variable. - */ - void ReadPixels(); - ///@{ /** * Set/Get the pixel data of an image, transmitted as RGBRGB... @@ -269,6 +264,9 @@ public: vtkSmartPointer<vtkWebGPUComputeRenderTexture> AcquireFramebufferRenderTexture(); ///@} + using TextureMapCallback = + std::function<void(const void* mappedData, int bytesPerRow, void* userdata)>; + protected: vtkWebGPURenderWindow(); ~vtkWebGPURenderWindow() override; @@ -289,14 +287,14 @@ protected: void ConfigureSurface(); void UnconfigureSurface(); - void CreateOffscreenColorAttachments(); - void DestroyOffscreenColorAttachments(); + void CreateOffscreenColorAttachment(); + void DestroyOffscreenColorAttachment(); - void CreateDepthStencilTexture(); - void DestroyDepthStencilTexture(); + void CreateDepthStencilAttachment(); + void DestroyDepthStencilAttachment(); - void CreateFSQGraphicsPipeline(); - void DestroyFSQGraphicsPipeline(); + void CreateColorCopyPipeline(); + void DestroyColorCopyPipeline(); void RecreateComputeRenderTextures(); @@ -309,23 +307,22 @@ protected: int SurfaceConfiguredSize[2]; wgpu::TextureFormat PreferredSurfaceTextureFormat = wgpu::TextureFormat::BGRA8Unorm; - struct vtkWGPUDeptStencil + struct vtkWGPUDepthStencil { wgpu::Texture Texture; wgpu::TextureView View; wgpu::TextureFormat Format; bool HasStencil; }; - vtkWGPUDeptStencil DepthStencil; + vtkWGPUDepthStencil DepthStencilAttachment; - struct vtkWGPUColorAttachment + struct vtkWGPUAttachment { wgpu::Texture Texture; wgpu::TextureView View; wgpu::TextureFormat Format; - wgpu::Buffer OffscreenBuffer; }; - vtkWGPUColorAttachment ColorAttachment; + vtkWGPUAttachment ColorAttachment; struct vtkWGPUUserStagingPixelData { @@ -338,25 +335,20 @@ protected: struct vtkWGPUFullScreenQuad { - wgpu::RenderPipeline Pipeline; + std::string Key; wgpu::BindGroup BindGroup; }; + vtkWGPUFullScreenQuad ColorCopyRenderPipeline; - vtkWGPUFullScreenQuad FSQ; - - struct MappingContext - { - vtkSmartPointer<vtkTypeUInt8Array> dst; - wgpu::Buffer src; - unsigned long size; - vtkWebGPURenderWindow* window; - } BufferMapReadContext; - - vtkNew<vtkTypeUInt8Array> CachedPixelBytes; vtkSmartPointer<vtkWebGPUConfiguration> WGPUConfiguration; vtkNew<vtkWebGPUShaderDatabase> WGPUShaderDatabase; vtkNew<vtkWebGPURenderPipelineCache> WGPUPipelineCache; + vtkSmartPointer<vtkWebGPUComputePipeline> DepthCopyPipeline; + vtkSmartPointer<vtkWebGPUComputePass> DepthCopyPass; + int DepthCopyBufferIndex = 0; + int DepthCopyTextureIndex = 0; + int ScreenSize[2]; private: @@ -389,11 +381,12 @@ private: */ void PostRasterizationRender(); - /** - * Copies the current framebuffer to the offscreen buffer (used for screenshotting the render - * window for example) - */ - void CopyFramebufferToOffscreenBuffer(); + void ReadTextureFromGPU(wgpu::Texture& wgpuTexture, wgpu::TextureFormat format, + std::size_t mipLevel, wgpu::TextureAspect aspect, wgpu::Origin3D offsets, + wgpu::Extent3D extents, TextureMapCallback callback, void* userData); + + void ReadTextureFromGPU(wgpu::Texture& wgpuTexture, wgpu::TextureFormat format, + std::size_t mipLevel, wgpu::TextureAspect aspect, TextureMapCallback callback, void* userData); // Render textures acquired by the user on this render window. They are kept here in case the // render window is resized, in which case, we'll need to resize the render textures --> We need diff --git a/Rendering/WebGPU/wgsl/CopyDepthTextureToBuffer.wgsl b/Rendering/WebGPU/wgsl/CopyDepthTextureToBuffer.wgsl new file mode 100644 index 0000000000000000000000000000000000000000..8b873584576c8d72ee3caf63106f0f002efa1146 --- /dev/null +++ b/Rendering/WebGPU/wgsl/CopyDepthTextureToBuffer.wgsl @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen +// SPDX-License-Identifier: BSD-3-Clause + +@group(0) @binding(0) var depthTexture: texture_depth_2d; +@group(0) @binding(1) var<storage, read_write> outBuffer: array<f32>; + +@compute +@workgroup_size(8, 8, 1) +fn computeMain(@builtin(global_invocation_id) id: vec3<u32>) +{ + let dims = textureDimensions(depthTexture); + if (id.x >= dims.x || id.y >= dims.y) + { + return; + } + outBuffer[id.x + dims.x * id.y] = textureLoad(depthTexture, id.xy, 0); + // textureStore(outTexture, id.xy, vec4f(vec3f(textureLoad(depthTexture, id.xy, 0)), 1.0f)); +}