diff --git a/Examples/Emscripten/Cxx/ConeWebGPU/ConeWebGPU.cxx b/Examples/Emscripten/Cxx/ConeWebGPU/ConeWebGPU.cxx index eccf3f4b729766618e2139cf219b7a591d3e1b62..027b5998b08e5f52f7f4b052f950f634f52c9ccc 100644 --- a/Examples/Emscripten/Cxx/ConeWebGPU/ConeWebGPU.cxx +++ b/Examples/Emscripten/Cxx/ConeWebGPU/ConeWebGPU.cxx @@ -37,20 +37,19 @@ int main(int argc, char* argv[]) iren->SetInteractorStyle(style); style->SetDefaultRenderer(renderer); - for (int i = 0; i < 16; ++i) + for (int i = 0; i < 100; ++i) { - for (int j = 0; j < 16; ++j) + for (int j = 0; j < 100; ++j) { vtkNew cone; cone->SetCenter(i, j, 0); vtkNew mapper; mapper->SetInputConnection(cone->GetOutputPort()); mapper->Update(); - mapper->SetStatic(1); + // mapper->SetStatic(1); vtkNew actor; actor->SetMapper(mapper); - // actor->GetProperty()->SetPointSize(8); - // actor->GetProperty()->SetRepresentationToPoints(); + actor->GetProperty()->EdgeVisibilityOn(); renderer->AddActor(actor); } } diff --git a/Rendering/WebGPU/README.md b/Rendering/WebGPU/README.md index aa0f4e9066f15276437f35852118787449fa170f..5c13f4053f54fe4d746e3a5874ed3c026eb85d9e 100644 --- a/Rendering/WebGPU/README.md +++ b/Rendering/WebGPU/README.md @@ -1,5 +1,8 @@ +# Description + This module contains the WebGPU native backend for `RenderingCore`. At the moment, only polygonal geometry can be rendered in different representations with point/cell scalar mapped colors. +## Available features Here is a list of currently implemented features: 1. Polygonal geometry rendering with point, line and triangle primitives. 2. Point scalar mapped coloring of surfaces. @@ -11,12 +14,36 @@ Here is a list of currently implemented features: 8. `vtkSDL2WebGPURenderWindow` is a reference implementation of `vtkWebGPURenderWindow` that works on WebAssembly and desktop. 9. Depth testing. +## Future work Since WebGPU is already an abstraction over graphics APIs, this module doesn't create another level of abstraction. It uses WebGPU's C++ flavor for it's object-oriented API and RAII. There are helper classes in the `vtkWebGPUInternals...` files for convenience and to make the bind group initialization code look clean. A lot of work remains to be done. Selections, volume mappers, textures, dual-depth peeling, fancy lights, platform native render windows are few that come to mind. +## References +Here are some very interesting references to learn WebGPU from examples if you prefer code over spec. +1. https://toji.github.io/webgpu-gltf-case-study/ + A case-study that slowly builds up an efficient gltf renderer in WebGPU using javascript. The author describes downfalls in + certain methods and proposes alternative ways when applicable. +2. https://github.com/samdauwe/webgpu-native-examples + A curated list of single file examples if you want to see how to do X with Y like constraints using WebGPU C API. +3. https://eliemichel.github.io/LearnWebGPU/index.html + Similar to LearnOpenGL or the vulka-tutorial.com. Walks you through getting a window, triangle, buffers, textures and 3D rendering. + This tutorial has good coverage and the author provides a simple to use WebGPU C++ distribution. +4. https://sotrh.github.io/learn-wgpu/ + A very nice coverage of the beginner concepts of webgpu. This tutorial uses wgpu.rs +5. https://alain.xyz/blog/raw-webgpu + Another small tutorial that lets you break the ice with WebGPU and get comfy with the concepts. This tutorial targets javascript API. +6. https://carmencincotti.com/2022-12-19/how-to-render-a-webgpu-triangle-series-part-three-video/ + A detailed, yet fun to read explaination of the swapchain and image presentation process. The author has several other + targeted posts on WebGPU concepts. +7. https://webgpu.rocks/ + You want to look at the WebGPU API, but are afraid of reading the spec and do not want to read C headers. This website + presents the WebGPU API and WGSL summary in a fancy way with syntax highlights. + +Finally, for wgsl, the spec does a good job https://www.w3.org/TR/WGSL/ + ## How to build VTK with Dawn (Highly experimental) diff --git a/Rendering/WebGPU/vtkWebGPUActor.cxx b/Rendering/WebGPU/vtkWebGPUActor.cxx index 46e1c6105a31ce8b05f1cc82dfa1ada301998025..880096531d01f0e6910b01160b5125ab97635f15 100644 --- a/Rendering/WebGPU/vtkWebGPUActor.cxx +++ b/Rendering/WebGPU/vtkWebGPUActor.cxx @@ -117,7 +117,7 @@ wgpu::RenderBundle vtkWebGPUActor::RenderToBundle(vtkRenderer* ren, vtkMapper* m this->CurrentMapperRenderType = MapperRenderType::None; auto bundle = this->CurrentBundler.Finish(); - this->CurrentBundler.Release(); + this->CurrentBundler = nullptr; return bundle; } diff --git a/Rendering/WebGPU/vtkWebGPUActor.h b/Rendering/WebGPU/vtkWebGPUActor.h index 8bf2289c75e7966205e621ef6a5544c09c2122a7..b455c0ab86445fd17a97bd1f924ffd057e23103b 100644 --- a/Rendering/WebGPU/vtkWebGPUActor.h +++ b/Rendering/WebGPU/vtkWebGPUActor.h @@ -104,7 +104,10 @@ public: inline MapperRenderType GetMapperRenderType() { return this->CurrentMapperRenderType; } inline wgpu::RenderBundleEncoder GetRenderBundleEncoder() { return this->CurrentBundler; } - inline void SetDynamicOffsets(vtkTypeUInt32Array* offsets) { this->DynamicOffsets = offsets; } + inline void SetDynamicOffsets(vtkSmartPointer offsets) + { + this->DynamicOffsets = offsets; + } protected: vtkWebGPUActor(); diff --git a/Rendering/WebGPU/vtkWebGPUPolyDataMapper.cxx b/Rendering/WebGPU/vtkWebGPUPolyDataMapper.cxx index 9bfa4894bb3be1e7a568da440fb8c7f4642bc0dc..41b6ae7f7a9405aef4109865e5da9268ddd2f40e 100644 --- a/Rendering/WebGPU/vtkWebGPUPolyDataMapper.cxx +++ b/Rendering/WebGPU/vtkWebGPUPolyDataMapper.cxx @@ -969,6 +969,7 @@ void vtkWebGPUPolyDataMapper::SetupGraphicsPipeline( if (!(shaderModule = wgpuRenderer->HasShaderCache(PolyData))) { shaderModule = vtkWebGPUInternalsShaderModule::CreateFromWGSL(device, PolyData); + wgpuRenderer->InsertShader(PolyData, shaderModule); } vtkWebGPUInternalsRenderPipelineDescriptor descriptor; diff --git a/Rendering/WebGPU/vtkWebGPURenderPass.cxx b/Rendering/WebGPU/vtkWebGPURenderPass.cxx index efc7fe7d445a54df5dcf2552202132a913420426..686139b4fb3da6df6d39f2955b3db1db709bf368 100644 --- a/Rendering/WebGPU/vtkWebGPURenderPass.cxx +++ b/Rendering/WebGPU/vtkWebGPURenderPass.cxx @@ -34,7 +34,7 @@ void vtkWebGPURenderPass::PrintSelf(ostream& os, vtkIndent indent) void vtkWebGPURenderPass::End(const vtkRenderState*, wgpu::RenderPassEncoder&& pass) { pass.End(); - pass.Release(); + pass = nullptr; } //------------------------------------------------------------------------------ diff --git a/Rendering/WebGPU/vtkWebGPURenderWindow.cxx b/Rendering/WebGPU/vtkWebGPURenderWindow.cxx index 5f21b2486c90eb0c173c21d35942e552c881381a..5ced9b2ba77ef78e220b4565fe8ca99cdaf1b72e 100644 --- a/Rendering/WebGPU/vtkWebGPURenderWindow.cxx +++ b/Rendering/WebGPU/vtkWebGPURenderWindow.cxx @@ -173,7 +173,8 @@ void vtkWebGPURenderWindow::WGPUFinalize() vtkDebugMacro(<< __func__ << " WGPUInitialized=" << this->WGPUInitialized); this->DestroyDepthStencilTexture(); this->DestroySwapChain(); - this->Device.Release(); + this->Device.SetDeviceLostCallback(nullptr, nullptr); + this->Device = nullptr; } //------------------------------------------------------------------------------ @@ -215,7 +216,7 @@ void vtkWebGPURenderWindow::CreateSwapChain() void vtkWebGPURenderWindow::DestroySwapChain() { vtkDebugMacro(<< __func__); - this->SwapChain.Instance.Release(); + this->SwapChain.Instance = nullptr; } //------------------------------------------------------------------------------ @@ -256,8 +257,8 @@ void vtkWebGPURenderWindow::CreateDepthStencilTexture() void vtkWebGPURenderWindow::DestroyDepthStencilTexture() { vtkDebugMacro(<< __func__); - this->DepthStencil.View.Release(); - this->DepthStencil.Texture.Release(); + this->DepthStencil.View = nullptr; + this->DepthStencil.Texture = nullptr; } //------------------------------------------------------------------------------ @@ -311,9 +312,9 @@ void vtkWebGPURenderWindow::CreateOffscreenColorAttachments() void vtkWebGPURenderWindow::DestroyOffscreenColorAttachments() { this->ColorAttachment.OffscreenBuffer.Destroy(); - this->ColorAttachment.OffscreenBuffer.Release(); - this->ColorAttachment.View.Release(); - this->ColorAttachment.Texture.Release(); + this->ColorAttachment.OffscreenBuffer = nullptr; + this->ColorAttachment.View = nullptr; + this->ColorAttachment.Texture = nullptr; } //------------------------------------------------------------------------------ @@ -391,8 +392,8 @@ void vtkWebGPURenderWindow::CreateFSQGraphicsPipeline() //------------------------------------------------------------------------------ void vtkWebGPURenderWindow::DestroyFSQGraphicsPipeline() { - this->FSQ.BindGroup.Release(); - this->FSQ.Pipeline.Release(); + this->FSQ.BindGroup = nullptr; + this->FSQ.Pipeline = nullptr; } //------------------------------------------------------------------------------ @@ -524,20 +525,20 @@ void vtkWebGPURenderWindow::Frame() wgpu::CommandBufferDescriptor cmdBufDesc = {}; wgpu::CommandBuffer cmdBuffer = this->CommandEncoder.Finish(&cmdBufDesc); - this->CommandEncoder.Release(); + this->CommandEncoder = nullptr; this->FlushCommandBuffers(1, &cmdBuffer); // On web, html5 `requestAnimateFrame` takes care of presentation. #ifndef __EMSCRIPTEN__ this->SwapChain.Instance.Present(); #endif - this->SwapChain.Framebuffer.Release(); + this->SwapChain.Framebuffer = nullptr; // Clean up staging buffer for SetPixelData. if (this->StagingPixelData.Buffer.Get() != nullptr) { this->StagingPixelData.Buffer.Destroy(); - this->StagingPixelData.Buffer.Release(); + this->StagingPixelData.Buffer = nullptr; } #ifndef NDEBUG diff --git a/Rendering/WebGPU/vtkWebGPURenderer.cxx b/Rendering/WebGPU/vtkWebGPURenderer.cxx index ddb2f0ccb142ba65a5ce7edcae3f53c5304c3056..c1aa9c413090d503af77838f652655008a40dc70 100644 --- a/Rendering/WebGPU/vtkWebGPURenderer.cxx +++ b/Rendering/WebGPU/vtkWebGPURenderer.cxx @@ -111,10 +111,12 @@ std::size_t vtkWebGPURenderer::WriteActorBlocksBuffer(std::size_t offset /*=0*/) const auto size = vtkWGPUContext::Align(vtkWebGPUActor::GetCacheSizeBytes(), 256); std::vector stage; - stage.resize(this->PropArrayCount * size); - for (int i = 0; i < this->PropArrayCount; ++i) + stage.resize(this->Props->GetNumberOfItems() * size); + vtkProp* aProp = nullptr; + vtkCollectionSimpleIterator piter; + for (this->Props->InitTraversal(piter); (aProp = this->Props->GetNextProp(piter));) { - vtkWebGPUActor* wgpuActor = reinterpret_cast(this->PropArray[i]); + vtkWebGPUActor* wgpuActor = reinterpret_cast(aProp); assert(wgpuActor != nullptr); const auto data = wgpuActor->GetCachedActorInformation(); @@ -136,8 +138,9 @@ void vtkWebGPURenderer::CreateBuffers() const auto lightSizePadded = vtkWGPUContext::Align(lightSize, 32); // use padded for actor because dynamic offsets are used. - const auto actorBlkSize = - this->PropArrayCount * vtkWGPUContext::Align(vtkWebGPUActor::GetCacheSizeBytes(), 256); + const auto actorBlkSize = vtkMath::Max(this->Props->GetNumberOfItems() * + vtkWGPUContext::Align(vtkWebGPUActor::GetCacheSizeBytes(), 256), + 256); auto wgpuRenWin = vtkWebGPURenderWindow::SafeDownCast(this->GetRenderWindow()); const wgpu::Device& device = wgpuRenWin->GetDevice(); @@ -190,7 +193,22 @@ void vtkWebGPURenderer::CreateBuffers() if (createActorBindGroup) { + // delete outdated info. + this->PropWGPUItems.clear(); this->SetupActorBindGroup(); + // build new cache for bundles and dynamic offsets. + vtkProp* aProp = nullptr; + vtkCollectionSimpleIterator piter; + const auto size = vtkWGPUContext::Align(vtkWebGPUActor::GetCacheSizeBytes(), 256); + int i = 0; + for (this->Props->InitTraversal(piter); (aProp = this->Props->GetNextProp(piter)); i++) + { + vtkWGPUPropItem item; + item.Bundle = nullptr; + item.DynamicOffsets = vtk::TakeSmartPointer(vtkTypeUInt32Array::New()); + item.DynamicOffsets->InsertNextValue(i * size); + this->PropWGPUItems.emplace(aProp, item); + } } } @@ -220,16 +238,6 @@ void vtkWebGPURenderer::DeviceRender() this->BeginEncoding(); // all pipelines execute in single render pass, for now. this->ActiveCamera->UpdateViewport(this); - ///@{ FIXME: Leaks memory when we recreate them. - if (this->PropArrayCount != static_cast(this->Bundles.size())) - { - // FIXME: Re-use bundles that don't need to be re-built. - this->Bundles.clear(); - // All props must re-bundle their commands. - std::fill(this->ReBundleProps.begin(), this->ReBundleProps.end(), 1); - } - ///@} - if (!this->UseRenderBundles) { this->WGPURenderEncoder.SetBindGroup(0, this->SceneBindGroup); @@ -240,26 +248,22 @@ void vtkWebGPURenderer::DeviceRender() this->BundleCacheStats.TotalRequests = 0; this->BundleCacheStats.Hits = 0; this->BundleCacheStats.Misses = 0; - if (this->Bundles.empty()) - { - this->Bundles.resize(this->ReBundleProps.size()); - this->RenderGeometry(); - } - else + this->RenderGeometry(); + if (!this->Bundles.empty()) { - this->RenderGeometry(); + vtkDebugMacro(<< "Bundle cache summary:\n" + << "Total requests: " << this->BundleCacheStats.TotalRequests << "\n" + << "Hit ratio: " + << (this->BundleCacheStats.Hits / this->BundleCacheStats.TotalRequests) * 100 + << "%\n" + << "Miss ratio: " + << (this->BundleCacheStats.Misses / this->BundleCacheStats.TotalRequests) * 100 + << "%\n" + << "Hit: " << this->BundleCacheStats.Hits << "\n" + << "Miss: " << this->BundleCacheStats.Misses << "\n"); + this->WGPURenderEncoder.ExecuteBundles(this->Bundles.size(), this->Bundles.data()); } - vtkDebugMacro(<< "Bundle cache summary:\n" - << "Total requests: " << this->BundleCacheStats.TotalRequests << "\n" - << "Hit ratio: " - << (this->BundleCacheStats.Hits / this->BundleCacheStats.TotalRequests) * 100 - << "%\n" - << "Miss ratio: " - << (this->BundleCacheStats.Misses / this->BundleCacheStats.TotalRequests) * 100 - << "%\n" - << "Hit: " << this->BundleCacheStats.Hits << "\n" - << "Miss: " << this->BundleCacheStats.Misses << "\n"); - this->WGPURenderEncoder.ExecuteBundles(this->Bundles.size(), this->Bundles.data()); + this->Bundles.clear(); } this->EndEncoding(); } @@ -293,7 +297,6 @@ int vtkWebGPURenderer::UpdateGeometry(vtkFrameBufferObjectBase* vtkNotUsed(fbo) { return 0; } - this->ReBundleProps.resize(this->PropArrayCount, 0); return this->UpdateOpaquePolygonalGeometry(); } @@ -307,7 +310,12 @@ int vtkWebGPURenderer::UpdateOpaquePolygonalGeometry() wgpuActor->CacheActorRenderOptions(); wgpuActor->CacheActorShadeOptions(); wgpuActor->CacheActorTransforms(); - this->ReBundleProps[i] = wgpuActor->Update(this, wgpuActor->GetMapper()); + if (wgpuActor->Update(this, wgpuActor->GetMapper())) + { + // mapper's buffers and bind points have changed. bundle is outdated. + auto& wgpuPropItem = this->PropWGPUItems[this->PropArray[i]]; + wgpuPropItem.Bundle = nullptr; + } } this->NumberOfPropsUpdated += result; return result; @@ -317,36 +325,31 @@ int vtkWebGPURenderer::UpdateOpaquePolygonalGeometry() void vtkWebGPURenderer::DeviceRenderOpaqueGeometry(vtkFrameBufferObjectBase* vtkNotUsed(fbo)) { int result = 0; - vtkNew offsets; - // currently supports only bind group with dynamic offsets. (ActorBindGroup) - offsets->SetNumberOfValues(1); - - std::size_t uboOffset = vtkWGPUContext::Align(vtkWebGPUActor::GetCacheSizeBytes(), 256); for (int i = 0; i < this->PropArrayCount; i++) { + auto& wgpuPropItem = this->PropWGPUItems[this->PropArray[i]]; + auto wgpuActor = reinterpret_cast(this->PropArray[i]); if (this->UseRenderBundles) { this->BundleCacheStats.TotalRequests++; - if (this->ReBundleProps[i]) + if (wgpuPropItem.Bundle == nullptr) { - auto wgpuActor = reinterpret_cast(this->PropArray[i]); - offsets->SetValue(0, i * uboOffset); - wgpuActor->SetDynamicOffsets(offsets); - this->Bundles[i] = wgpuActor->RenderToBundle(this, wgpuActor->GetMapper()); + wgpuActor->SetDynamicOffsets(wgpuPropItem.DynamicOffsets); + wgpuPropItem.Bundle = wgpuActor->RenderToBundle(this, wgpuActor->GetMapper()); + this->Bundles.emplace_back(wgpuPropItem.Bundle); this->BundleCacheStats.Misses++; } else { - // do nothing. reuse bundle for this prop. + // bundle gets reused for this prop. + this->Bundles.emplace_back(wgpuPropItem.Bundle); this->BundleCacheStats.Hits++; } } else { - auto wgpuActor = reinterpret_cast(this->PropArray[i]); - offsets->SetValue(0, i * uboOffset); - wgpuActor->SetDynamicOffsets(offsets); + wgpuActor->SetDynamicOffsets(wgpuPropItem.DynamicOffsets); wgpuActor->Render(this, wgpuActor->GetMapper()); } result += 1; @@ -475,6 +478,7 @@ void vtkWebGPURenderer::SetEnvironmentTexture(vtkTexture*, bool vtkNotUsed(isSRG //------------------------------------------------------------------------------ void vtkWebGPURenderer::ReleaseGraphicsResources(vtkWindow* vtkNotUsed(w)) { + this->PropWGPUItems.clear(); this->Bundles.clear(); } @@ -566,7 +570,7 @@ void vtkWebGPURenderer::EndEncoding() this->WGPURenderEncoder.PopDebugGroup(); #endif this->WGPURenderEncoder.End(); - this->WGPURenderEncoder.Release(); + this->WGPURenderEncoder = nullptr; this->Pass->Delete(); this->Pass = nullptr; } diff --git a/Rendering/WebGPU/vtkWebGPURenderer.h b/Rendering/WebGPU/vtkWebGPURenderer.h index 17bf03cf6ac6ebaf3294ab313599fc03d2351cc4..02cc50ae9777fd160b2feae37e9091a5058ff0a1 100644 --- a/Rendering/WebGPU/vtkWebGPURenderer.h +++ b/Rendering/WebGPU/vtkWebGPURenderer.h @@ -19,6 +19,7 @@ #include "vtkRenderingWebGPUModule.h" // for export macro #include "vtkSmartPointer.h" // for ivar +#include "vtkTypeUInt32Array.h" // for ivar #include "vtk_wgpu.h" // for webgpu #include // for ivar @@ -158,12 +159,22 @@ protected: wgpu::BindGroup ActorBindGroup; wgpu::BindGroupLayout ActorBindGroupLayout; +#ifdef __EMSCRIPTEN__ + bool UseRenderBundles = true; +#else bool UseRenderBundles = false; +#endif // one bundle per actor. bundle gets reused every frame. // these bundles can be built in parallel with vtkSMPTools. holding off because not // sure how to get emscripten to thread. std::vector Bundles; - std::vector ReBundleProps; + struct vtkWGPUPropItem + { + wgpu::RenderBundle Bundle = nullptr; + vtkSmartPointer DynamicOffsets; + }; + std::unordered_map PropWGPUItems; + std::unordered_map ShaderCache; std::size_t NumberOfPropsUpdated = 0; int LightingComplexity = 0;