From a877f670d8701de6e3bc290df01680e260baef7a Mon Sep 17 00:00:00 2001
From: Jaswant Panchumarti <jaswant.panchumarti@kitware.com>
Date: Wed, 4 Dec 2024 10:01:05 -0500
Subject: [PATCH] Use render bundle per vtkRenderer

- this eliminates the link b/w a vtkActor with a render bundle.
- that link made it awkward when dealing with actors that are nested inside other actors. Ex: vtkAxesActor.
---
 Rendering/WebGPU/vtkWebGPUActor.cxx           |  89 ++++-
 Rendering/WebGPU/vtkWebGPUActor.h             |  33 +-
 .../WebGPU/vtkWebGPUBatchedPolyDataMapper.cxx |   4 +-
 Rendering/WebGPU/vtkWebGPUCamera.cxx          |   5 +-
 Rendering/WebGPU/vtkWebGPUClearDrawPass.cxx   |   1 -
 Rendering/WebGPU/vtkWebGPUPolyDataMapper.cxx  |  37 +--
 Rendering/WebGPU/vtkWebGPURenderer.cxx        | 309 +++---------------
 Rendering/WebGPU/vtkWebGPURenderer.h          |  42 +--
 .../wgsl/LineMiterJoinVertexShader.wgsl       |   2 +-
 .../wgsl/LineRoundJoinVertexShader.wgsl       |   2 +-
 Rendering/WebGPU/wgsl/PointShader.wgsl        |   2 +-
 Rendering/WebGPU/wgsl/SurfaceMeshShader.wgsl  |   2 +-
 12 files changed, 188 insertions(+), 340 deletions(-)

diff --git a/Rendering/WebGPU/vtkWebGPUActor.cxx b/Rendering/WebGPU/vtkWebGPUActor.cxx
index d83131ccc7d..372a767bdd7 100644
--- a/Rendering/WebGPU/vtkWebGPUActor.cxx
+++ b/Rendering/WebGPU/vtkWebGPUActor.cxx
@@ -3,6 +3,9 @@
 
 #include "vtkWebGPUActor.h"
 
+#include "Private/vtkWebGPUBindGroupInternals.h"
+#include "Private/vtkWebGPUBindGroupLayoutInternals.h"
+
 #include "vtkMapper.h"
 #include "vtkMatrix3x3.h"
 #include "vtkObjectFactory.h"
@@ -12,6 +15,7 @@
 #include "vtkTexture.h"
 #include "vtkTransform.h"
 #include "vtkWebGPUComputePointCloudMapper.h"
+#include "vtkWebGPURenderWindow.h"
 #include "vtkWebGPURenderer.h"
 #include "vtkWindow.h"
 
@@ -48,12 +52,14 @@ void vtkWebGPUActor::PrintSelf(ostream& os, vtkIndent indent)
   os << indent << "ModelTransformsBuildTimestamp: " << this->ModelTransformsBuildTimestamp << '\n';
   os << indent << "ShadingOptionsBuildTimestamp: " << this->ShadingOptionsBuildTimestamp << '\n';
   os << indent << "RenderOptionsBuildTimestamp: " << this->RenderOptionsBuildTimestamp << '\n';
-  os << indent << "BundleInvalidated: " << this->BundleInvalidated << '\n';
 }
 
 //------------------------------------------------------------------------------
 void vtkWebGPUActor::ReleaseGraphicsResources(vtkWindow* window)
 {
+  this->ActorBindGroupLayout = nullptr;
+  this->ActorBindGroup = nullptr;
+  this->ActorBuffer = nullptr;
   this->MapperHasOpaqueGeometry = {};
   this->MapperHasTranslucentPolygonalGeometry = {};
   this->Superclass::ReleaseGraphicsResources(window);
@@ -64,21 +70,46 @@ void vtkWebGPUActor::Render(vtkRenderer* renderer, vtkMapper* mapper)
 {
   if (auto* wgpuRenderer = vtkWebGPURenderer::SafeDownCast(renderer))
   {
+    auto* wgpuRenderWindow = vtkWebGPURenderWindow::SafeDownCast(wgpuRenderer->GetRenderWindow());
+    auto* wgpuConfiguration = wgpuRenderWindow->GetWGPUConfiguration();
     switch (wgpuRenderer->GetRenderStage())
     {
       case vtkWebGPURenderer::AwaitingPreparation:
         break;
       case vtkWebGPURenderer::UpdatingBuffers:
+      {
+        if (!(this->ActorBindGroup && this->ActorBindGroupLayout && this->ActorBuffer))
+        {
+          this->AllocateResources(wgpuConfiguration);
+        }
         // reset this flag because the `mapper->Render()` call shall invalidate the bundle if it
         // determines that the render bundle needs to be recorded once again.
-        this->BundleInvalidated = false;
         mapper->Render(renderer, this);
-        this->CacheActorRenderOptions();
-        this->CacheActorShadeOptions();
-        this->CacheActorTransforms();
+        bool updateBuffers = this->CacheActorRenderOptions();
+        updateBuffers |= this->CacheActorShadeOptions();
+        updateBuffers |= this->CacheActorTransforms();
+        if (updateBuffers)
+        {
+          wgpuConfiguration->WriteBuffer(this->ActorBuffer, 0, this->GetCachedActorInformation(),
+            this->GetCacheSizeBytes(), "ActorBufferUpdate");
+        }
         break;
+      }
       case vtkWebGPURenderer::RecordingCommands:
-        mapper->Render(renderer, this);
+        if (wgpuRenderer->GetUseRenderBundles() && this->SupportRenderBundles())
+        {
+          if (wgpuRenderer->GetBundleInvalidated())
+          {
+            wgpuRenderer->GetRenderBundleEncoder().SetBindGroup(1, this->ActorBindGroup);
+            mapper->Render(renderer, this);
+          }
+          // else, no need to record draw commands.
+        }
+        else
+        {
+          wgpuRenderer->GetRenderPassEncoder().SetBindGroup(1, this->ActorBindGroup);
+          mapper->Render(renderer, this);
+        }
         break;
       case vtkWebGPURenderer::Finished:
       default:
@@ -207,7 +238,7 @@ bool vtkWebGPUActor::UpdateKeyMatrices()
 }
 
 //------------------------------------------------------------------------------
-void vtkWebGPUActor::CacheActorTransforms()
+bool vtkWebGPUActor::CacheActorTransforms()
 {
   if (this->UpdateKeyMatrices())
   {
@@ -223,11 +254,13 @@ void vtkWebGPUActor::CacheActorTransforms()
         transform.Normal[i][j] = this->NormalMatrix->GetElement(i, j);
       }
     }
+    return true;
   }
+  return false;
 }
 
 //------------------------------------------------------------------------------
-void vtkWebGPUActor::CacheActorRenderOptions()
+bool vtkWebGPUActor::CacheActorRenderOptions()
 {
   auto* displayProperty = this->GetProperty();
   if (displayProperty->GetMTime() > this->RenderOptionsBuildTimestamp ||
@@ -246,11 +279,13 @@ void vtkWebGPUActor::CacheActorRenderOptions()
       (displayProperty->GetRenderLinesAsTubes() << 6) |
       (static_cast<int>(displayProperty->GetPoint2DShape()) << 7);
     this->RenderOptionsBuildTimestamp.Modified();
+    return true;
   }
+  return false;
 }
 
 //------------------------------------------------------------------------------
-void vtkWebGPUActor::CacheActorShadeOptions()
+bool vtkWebGPUActor::CacheActorShadeOptions()
 {
   auto* displayProperty = this->GetProperty();
   if (displayProperty->GetMTime() > this->ShadingOptionsBuildTimestamp ||
@@ -271,6 +306,42 @@ void vtkWebGPUActor::CacheActorShadeOptions()
       so.VertexColor[i] = displayProperty->GetVertexColor()[i];
     }
     this->ShadingOptionsBuildTimestamp.Modified();
+    return true;
   }
+  return false;
+}
+
+//------------------------------------------------------------------------------
+void vtkWebGPUActor::AllocateResources(vtkWebGPUConfiguration* wgpuConfiguration)
+{
+  const auto& device = wgpuConfiguration->GetDevice();
+
+  const auto actorDescription = this->GetObjectDescription();
+  const auto bufferLabel = "buffer@" + actorDescription;
+  const auto bufferSize = vtkWebGPUConfiguration::Align(vtkWebGPUActor::GetCacheSizeBytes(), 32);
+  this->ActorBuffer = wgpuConfiguration->CreateBuffer(bufferSize,
+    wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopyDst, false, bufferLabel.c_str());
+
+  this->ActorBindGroupLayout = vtkWebGPUBindGroupLayoutInternals::MakeBindGroupLayout(device,
+    {
+      // clang-format off
+      // ActorBlocks
+      { 0, wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::ReadOnlyStorage },
+      // clang-format on
+    },
+    "bgl@" + actorDescription);
+  this->ActorBindGroup =
+    vtkWebGPUBindGroupInternals::MakeBindGroup(device, this->ActorBindGroupLayout,
+      {
+        // clang-format off
+        { 0, this->ActorBuffer, 0, bufferSize },
+        // clang-format on
+      },
+      "bg@" + actorDescription);
+  // Reset timestamps because the previous buffer is now gone and contents of the buffer will need
+  // to be re-uploaded.
+  this->ModelTransformsBuildTimestamp = vtkTimeStamp();
+  this->ShadingOptionsBuildTimestamp = vtkTimeStamp();
+  this->RenderOptionsBuildTimestamp = vtkTimeStamp();
 }
 VTK_ABI_NAMESPACE_END
diff --git a/Rendering/WebGPU/vtkWebGPUActor.h b/Rendering/WebGPU/vtkWebGPUActor.h
index 57891f6371b..002ace5cf24 100644
--- a/Rendering/WebGPU/vtkWebGPUActor.h
+++ b/Rendering/WebGPU/vtkWebGPUActor.h
@@ -10,6 +10,7 @@
 
 VTK_ABI_NAMESPACE_BEGIN
 class vtkMatrix3x3;
+class vtkWebGPUConfiguration;
 class vtkWebGPURenderPipelineCache;
 
 class VTKRENDERINGWEBGPU_EXPORT vtkWebGPUActor : public vtkActor
@@ -19,6 +20,8 @@ public:
   vtkTypeMacro(vtkWebGPUActor, vtkActor);
   void PrintSelf(ostream& os, vtkIndent indent) override;
 
+  void ReleaseGraphicsResources(vtkWindow* window) override;
+
   inline const void* GetCachedActorInformation() { return &(this->CachedActorInfo); }
   static std::size_t GetCacheSizeBytes() { return sizeof(ActorBlock); }
 
@@ -35,6 +38,11 @@ public:
    */
   bool SupportRenderBundles();
 
+  inline void PopulateBindgroupLayouts(std::vector<wgpu::BindGroupLayout>& layouts)
+  {
+    layouts.emplace_back(this->ActorBindGroupLayout);
+  }
+
   virtual bool UpdateKeyMatrices();
 
   ///@{
@@ -55,26 +63,15 @@ public:
   vtkTypeBool HasTranslucentPolygonalGeometry() override;
   ///@}
 
-  /**
-   * Forces the renderer to re-record draw commands into a render bundle associated with this actor.
-   *
-   * @note This does not use vtkSetMacro because the actor MTime should not be affected when a
-   * render bundle is invalidated.
-   */
-  inline void SetBundleInvalidated(bool value) { this->BundleInvalidated = value; }
-
-  /**
-   * Get whether the render bundle associated with this actor must be reset by the renderer.
-   */
-  vtkGetMacro(BundleInvalidated, bool);
-
 protected:
   vtkWebGPUActor();
   ~vtkWebGPUActor() override;
 
-  void CacheActorTransforms();
-  void CacheActorRenderOptions();
-  void CacheActorShadeOptions();
+  bool CacheActorTransforms();
+  bool CacheActorRenderOptions();
+  bool CacheActorShadeOptions();
+
+  void AllocateResources(vtkWebGPUConfiguration* renderer);
 
   struct ActorBlock
   {
@@ -134,7 +131,9 @@ protected:
   vtkTimeStamp ShadingOptionsBuildTimestamp;
   vtkTimeStamp RenderOptionsBuildTimestamp;
 
-  bool BundleInvalidated = false;
+  wgpu::BindGroupLayout ActorBindGroupLayout;
+  wgpu::BindGroup ActorBindGroup;
+  wgpu::Buffer ActorBuffer;
 
   class MapperBooleanCache
   {
diff --git a/Rendering/WebGPU/vtkWebGPUBatchedPolyDataMapper.cxx b/Rendering/WebGPU/vtkWebGPUBatchedPolyDataMapper.cxx
index dbaf78d08e7..883e0e164f2 100644
--- a/Rendering/WebGPU/vtkWebGPUBatchedPolyDataMapper.cxx
+++ b/Rendering/WebGPU/vtkWebGPUBatchedPolyDataMapper.cxx
@@ -138,14 +138,14 @@ void vtkWebGPUBatchedPolyDataMapper::RenderPiece(vtkRenderer* renderer, vtkActor
   }
 
   auto* wgpuRenderWindow = vtkWebGPURenderWindow::SafeDownCast(renderer->GetRenderWindow());
-  auto* wgpuActor = vtkWebGPUActor::SafeDownCast(actor);
+  auto* wgpuRenderer = vtkWebGPURenderer::SafeDownCast(renderer);
   auto* wgpuConfiguration = wgpuRenderWindow->GetWGPUConfiguration();
   const auto& device = wgpuRenderWindow->GetDevice();
 
   auto& batchElement = *(this->VTKPolyDataToBatchElement.begin()->second);
   if (this->LastBlockVisibility != batchElement.Visibility)
   {
-    wgpuActor->SetBundleInvalidated(true);
+    wgpuRenderer->SetBundleInvalidated(true);
   }
   this->LastBlockVisibility = batchElement.Visibility;
 
diff --git a/Rendering/WebGPU/vtkWebGPUCamera.cxx b/Rendering/WebGPU/vtkWebGPUCamera.cxx
index aa36d2e8b92..7314901cc16 100644
--- a/Rendering/WebGPU/vtkWebGPUCamera.cxx
+++ b/Rendering/WebGPU/vtkWebGPUCamera.cxx
@@ -119,13 +119,12 @@ void vtkWebGPUCamera::UpdateViewport(vtkRenderer* renderer)
     rpassEncoder.SetScissorRect(static_cast<uint32_t>(this->ScissorRect.GetLeft()),
       static_cast<uint32_t>(this->ScissorRect.GetBottom()),
       static_cast<uint32_t>(this->ScissorRect.GetWidth()),
-      static_cast<uint32_t>(this->ScissorRect.GetWidth()));
+      static_cast<uint32_t>(this->ScissorRect.GetHeight()));
     this->UseScissor = false;
   }
   else
   {
-    rpassEncoder.SetScissorRect(
-      0u, 0u, static_cast<uint32_t>(width), static_cast<uint32_t>(height));
+    rpassEncoder.SetScissorRect(0, 0, static_cast<uint32_t>(width), static_cast<uint32_t>(height));
   }
 }
 
diff --git a/Rendering/WebGPU/vtkWebGPUClearDrawPass.cxx b/Rendering/WebGPU/vtkWebGPUClearDrawPass.cxx
index 33c33ac07cc..df3e159adcc 100644
--- a/Rendering/WebGPU/vtkWebGPUClearDrawPass.cxx
+++ b/Rendering/WebGPU/vtkWebGPUClearDrawPass.cxx
@@ -7,7 +7,6 @@
 #include "vtkRenderState.h"
 #include "vtkRenderer.h"
 #include "vtkWebGPURenderWindow.h"
-#include <chrono>
 
 VTK_ABI_NAMESPACE_BEGIN
 
diff --git a/Rendering/WebGPU/vtkWebGPUPolyDataMapper.cxx b/Rendering/WebGPU/vtkWebGPUPolyDataMapper.cxx
index ffc9151f01d..cfdc04fa74b 100644
--- a/Rendering/WebGPU/vtkWebGPUPolyDataMapper.cxx
+++ b/Rendering/WebGPU/vtkWebGPUPolyDataMapper.cxx
@@ -282,7 +282,6 @@ void vtkWebGPUPolyDataMapper::RenderPiece(vtkRenderer* renderer, vtkActor* actor
 
   const auto device = wgpuRenderWindow->GetDevice();
   auto* wgpuConfiguration = wgpuRenderWindow->GetWGPUConfiguration();
-  auto* wgpuActor = reinterpret_cast<vtkWebGPUActor*>(actor);
   auto* wgpuRenderer = vtkWebGPURenderer::SafeDownCast(renderer);
   auto* displayProperty = actor->GetProperty();
 
@@ -307,13 +306,13 @@ void vtkWebGPUPolyDataMapper::RenderPiece(vtkRenderer* renderer, vtkActor* actor
       if (this->GetNeedToRebuildGraphicsPipelines(actor))
       {
         // render bundle must reference new bind groups and/or pipelines
-        wgpuActor->SetBundleInvalidated(true);
+        wgpuRenderer->SetBundleInvalidated(true);
         this->SetupGraphicsPipelines(device, renderer, actor);
       }
       // invalidate render bundle when any of the cached properties of an actor have changed.
       if (this->CacheActorProperties(actor))
       {
-        wgpuActor->SetBundleInvalidated(true);
+        wgpuRenderer->SetBundleInvalidated(true);
       }
       break;
     }
@@ -1257,7 +1256,7 @@ void vtkWebGPUPolyDataMapper::UpdateMeshGeometryBuffers(vtkWebGPURenderWindow* w
 
   vtkCellData* cellData = this->CurrentInput->GetCellData();
   vtkDataArray* cellColors = nullptr;
-  vtkNew<vtkUnsignedCharArray> cellColorsFromFieldData;
+  vtkSmartPointer<vtkUnsignedCharArray> cellColorsFromFieldData;
   if (this->HasCellAttributes[CELL_COLORS])
   {
     // are we using a single color value replicated over all cells?
@@ -1265,6 +1264,7 @@ void vtkWebGPUPolyDataMapper::UpdateMeshGeometryBuffers(vtkWebGPURenderWindow* w
     {
       const vtkIdType numCells = this->CurrentInput->GetNumberOfCells();
       const int numComponents = this->Colors->GetNumberOfComponents();
+      cellColorsFromFieldData = vtk::TakeSmartPointer(vtkUnsignedCharArray::New());
       cellColorsFromFieldData->SetNumberOfComponents(numComponents);
       cellColorsFromFieldData->SetNumberOfTuples(numCells);
       for (int i = 0; i < numComponents; ++i)
@@ -1377,19 +1377,23 @@ void vtkWebGPUPolyDataMapper::UpdateMeshGeometryBuffers(vtkWebGPURenderWindow* w
     }
   }
 
-  const std::string meshAttrDescriptorLabel =
-    "MeshAttributeDescriptor-" + this->CurrentInput->GetObjectDescription();
-  if (this->AttributeDescriptorBuffer == nullptr)
-  {
-    this->AttributeDescriptorBuffer = wgpuConfiguration->CreateBuffer(sizeof(meshAttrDescriptor),
-      wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopyDst,
-      /*mappedAtCreation=*/false, meshAttrDescriptorLabel.c_str());
-  }
   // handle partial updates
   if (updatePointDescriptor || updateCellArrayDescriptor)
   {
+    const std::string meshAttrDescriptorLabel =
+      "MeshAttributeDescriptor-" + this->CurrentInput->GetObjectDescription();
+    if (this->AttributeDescriptorBuffer == nullptr)
+    {
+      this->AttributeDescriptorBuffer = wgpuConfiguration->CreateBuffer(sizeof(meshAttrDescriptor),
+        wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopyDst,
+        /*mappedAtCreation=*/false, meshAttrDescriptorLabel.c_str());
+    }
     wgpuConfiguration->WriteBuffer(this->AttributeDescriptorBuffer, 0, &meshAttrDescriptor,
       sizeof(meshAttrDescriptor), meshAttrDescriptorLabel.c_str());
+    // Create bind group for the point/cell attribute buffers.
+    this->MeshAttributeBindGroup =
+      this->CreateMeshAttributeBindGroup(wgpuConfiguration->GetDevice(), "MeshAttributeBindGroup");
+    this->RebuildGraphicsPipelines = true;
   }
 
   vtkDebugMacro(<< ((updatePointDescriptor || updateCellArrayDescriptor) ? "rebuilt" : "")
@@ -1399,13 +1403,6 @@ void vtkWebGPUPolyDataMapper::UpdateMeshGeometryBuffers(vtkWebGPURenderWindow* w
                 << ((updatePointDescriptor || updateCellArrayDescriptor)
                        ? " buffers"
                        : "reuse point and cell buffers"));
-  if (updatePointDescriptor || updateCellArrayDescriptor)
-  {
-    // Create bind group for the point/cell attribute buffers.
-    this->MeshAttributeBindGroup =
-      this->CreateMeshAttributeBindGroup(wgpuConfiguration->GetDevice(), "MeshAttributeBindGroup");
-    this->RebuildGraphicsPipelines = true;
-  }
 }
 
 //------------------------------------------------------------------------------
@@ -1833,6 +1830,7 @@ void vtkWebGPUPolyDataMapper::DispatchCellToPrimitiveComputePipeline(
 void vtkWebGPUPolyDataMapper::SetupGraphicsPipelines(
   const wgpu::Device& device, vtkRenderer* renderer, vtkActor* actor)
 {
+  auto* wgpuActor = vtkWebGPUActor::SafeDownCast(actor);
   auto* wgpuRenderWindow = vtkWebGPURenderWindow::SafeDownCast(renderer->GetRenderWindow());
   auto* wgpuRenderer = vtkWebGPURenderer::SafeDownCast(renderer);
   auto* wgpuPipelineCache = wgpuRenderWindow->GetWGPUPipelineCache();
@@ -1862,6 +1860,7 @@ void vtkWebGPUPolyDataMapper::SetupGraphicsPipelines(
 
   std::vector<wgpu::BindGroupLayout> bgls;
   wgpuRenderer->PopulateBindgroupLayouts(bgls);
+  wgpuActor->PopulateBindgroupLayouts(bgls);
   bgls.emplace_back(
     this->CreateMeshAttributeBindGroupLayout(device, "MeshAttributeBindGroupLayout"));
   bgls.emplace_back(this->CreateTopologyBindGroupLayout(device, "TopologyBindGroupLayout"));
diff --git a/Rendering/WebGPU/vtkWebGPURenderer.cxx b/Rendering/WebGPU/vtkWebGPURenderer.cxx
index 3221de34516..86409c94176 100644
--- a/Rendering/WebGPU/vtkWebGPURenderer.cxx
+++ b/Rendering/WebGPU/vtkWebGPURenderer.cxx
@@ -3,7 +3,6 @@
 #include "vtkWebGPURenderer.h"
 #include "Private/vtkWebGPUBindGroupInternals.h"
 #include "Private/vtkWebGPUBindGroupLayoutInternals.h"
-#include "Private/vtkWebGPUBufferInternals.h"
 #include "Private/vtkWebGPUComputePassInternals.h"
 #include "vtkAbstractMapper.h"
 #include "vtkFrameBufferObjectBase.h"
@@ -92,32 +91,6 @@ std::size_t vtkWebGPURenderer::WriteLightsBuffer(std::size_t offset /*=0*/)
   return wroteBytes;
 }
 
-//------------------------------------------------------------------------------
-std::size_t vtkWebGPURenderer::WriteActorBlocksBuffer(std::size_t offset /*=0*/)
-{
-  std::size_t wroteBytes = 0;
-  auto* wgpuRenderWindow = vtkWebGPURenderWindow::SafeDownCast(this->GetRenderWindow());
-  auto* wgpuConfiguration = wgpuRenderWindow->GetWGPUConfiguration();
-
-  const auto size = vtkWebGPUConfiguration::Align(vtkWebGPUActor::GetCacheSizeBytes(), 256);
-  std::vector<uint8_t> stage;
-  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<vtkWebGPUActor*>(aProp);
-    assert(wgpuActor != nullptr);
-
-    const auto data = wgpuActor->GetCachedActorInformation();
-    std::memcpy(&stage[wroteBytes], data, size);
-    wroteBytes += size;
-  }
-  wgpuConfiguration->WriteBuffer(
-    this->ActorBlocksBuffer, offset, stage.data(), wroteBytes, "ActorInformation");
-  return wroteBytes;
-}
-
 //------------------------------------------------------------------------------
 void vtkWebGPURenderer::CreateBuffers()
 {
@@ -128,15 +101,9 @@ void vtkWebGPURenderer::CreateBuffers()
     + this->LightIDs.size() * vtkWebGPULight::GetCacheSizeBytes();
   const auto lightSizePadded = vtkWebGPUConfiguration::Align(lightSize, 32);
 
-  // use padded for actor because dynamic offsets are used.
-  const auto actorBlkSize = vtkMath::Max<std::size_t>(this->Props->GetNumberOfItems() *
-      vtkWebGPUConfiguration::Align(vtkWebGPUActor::GetCacheSizeBytes(), 256),
-    256);
-
   auto* wgpuRenderWindow = vtkWebGPURenderWindow::SafeDownCast(this->GetRenderWindow());
   auto* wgpuConfiguration = wgpuRenderWindow->GetWGPUConfiguration();
   bool createSceneBindGroup = false;
-  bool createActorBindGroup = false;
 
   if (this->SceneTransformBuffer == nullptr)
   {
@@ -154,46 +121,10 @@ void vtkWebGPURenderer::CreateBuffers()
     createSceneBindGroup = true;
   }
 
-  if (actorBlkSize != this->LastActorBufferSize)
-  {
-    if (this->ActorBlocksBuffer != nullptr)
-    {
-      this->ActorBlocksBuffer.Destroy();
-      this->ActorBlocksBuffer = nullptr;
-    }
-  }
-  if (this->ActorBlocksBuffer == nullptr)
-  {
-    const std::string label = "ActorInformation-" + this->GetObjectDescription();
-    this->ActorBlocksBuffer = wgpuConfiguration->CreateBuffer(
-      actorBlkSize, wgpu::BufferUsage::Uniform | wgpu::BufferUsage::CopyDst, false, label.c_str());
-    this->LastActorBufferSize = actorBlkSize;
-    createActorBindGroup = true;
-  }
-
   if (createSceneBindGroup)
   {
     this->SetupSceneBindGroup();
   }
-
-  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 = vtkWebGPUConfiguration::Align(vtkWebGPUActor::GetCacheSizeBytes(), 256);
-    int i = 0;
-    for (this->Props->InitTraversal(piter); (aProp = this->Props->GetNextProp(piter)); i++)
-    {
-      vtkWGPUPropItem item;
-      item.Bundle = nullptr;
-      item.DynamicOffset = i * size;
-      this->PropWGPUItems.emplace(aProp, item);
-    }
-  }
 }
 
 //------------------------------------------------------------------------------
@@ -228,12 +159,14 @@ void vtkWebGPURenderer::UpdateBuffers()
   this->UpdateCamera(); // brings the camera's transform matrices up-to-date.
   this->UpdateLightGeometry();
   this->UpdateLights();
+
+  // if any mapper's commands need rebundling, that mapper will set this flag.
+  this->BundleInvalidated = false;
   this->UpdateGeometry(); // mappers prepare geometry SSBO and pipeline layout.
 
   this->CreateBuffers();
   this->WriteSceneTransformsBuffer();
   this->WriteLightsBuffer();
-  this->WriteActorBlocksBuffer();
 }
 
 //------------------------------------------------------------------------------
@@ -349,14 +282,7 @@ int vtkWebGPURenderer::UpdateOpaquePolygonalGeometry()
     {
       for (int i = 0; i < this->PropArrayCount; i++)
       {
-        auto wgpuActor = reinterpret_cast<vtkWebGPUActor*>(this->PropArray[i]);
-        wgpuActor->RenderOpaqueGeometry(this);
-        if (wgpuActor->GetBundleInvalidated())
-        {
-          // mapper's buffers and bind points have changed. bundle is outdated.
-          auto& wgpuPropItem = this->PropWGPUItems[this->PropArray[i]];
-          wgpuPropItem.Bundle = nullptr;
-        }
+        this->PropArray[i]->RenderOpaqueGeometry(this);
       }
       result += this->PropArrayCount;
     }
@@ -365,69 +291,12 @@ int vtkWebGPURenderer::UpdateOpaquePolygonalGeometry()
     {
       for (int i = 0; i < this->PropArrayCount; i++)
       {
-        if (auto* wgpuActor = vtkWebGPUActor::SafeDownCast(this->PropArray[i]))
-        {
-          int rendered = 0;
-          auto& wgpuPropItem = this->PropWGPUItems[this->PropArray[i]];
-          if (wgpuActor->SupportRenderBundles() && this->UseRenderBundles)
-          {
-            this->BundleCacheStats.TotalRequests++;
-            if (wgpuPropItem.Bundle == nullptr)
-            {
-              const std::string label = this->GetObjectDescription();
-              auto wgpuRenderWindow = vtkWebGPURenderWindow::SafeDownCast(this->GetRenderWindow());
-              const auto colorFormat = wgpuRenderWindow->GetPreferredSurfaceTextureFormat();
-              const int sampleCount =
-                wgpuRenderWindow->GetMultiSamples() ? wgpuRenderWindow->GetMultiSamples() : 1;
-              wgpu::RenderBundleEncoderDescriptor bundleEncDesc;
-              bundleEncDesc.colorFormatCount = 1;
-              bundleEncDesc.colorFormats = &colorFormat;
-              bundleEncDesc.depthStencilFormat = wgpuRenderWindow->GetDepthStencilFormat();
-              bundleEncDesc.sampleCount = sampleCount;
-              bundleEncDesc.depthReadOnly = false;
-              bundleEncDesc.stencilReadOnly = false;
-              bundleEncDesc.label = label.c_str();
-              bundleEncDesc.nextInChain = nullptr;
-              this->WGPUBundleEncoder = wgpuRenderWindow->NewRenderBundleEncoder(bundleEncDesc);
-              this->WGPUBundleEncoder.SetBindGroup(0, this->SceneBindGroup);
-              this->WGPUBundleEncoder.SetBindGroup(
-                1, this->ActorBindGroup, 1, &wgpuPropItem.DynamicOffset);
-              rendered = wgpuActor->RenderOpaqueGeometry(this);
-              auto bundle = this->WGPUBundleEncoder.Finish();
-              this->WGPUBundleEncoder = nullptr;
-              if (rendered > 0)
-              {
-                wgpuPropItem.Bundle = bundle;
-                this->Bundles.emplace_back(wgpuPropItem.Bundle);
-              }
-              this->BundleCacheStats.Misses++;
-            }
-            else
-            {
-              // bundle gets reused for this prop.
-              rendered = 1;
-              this->Bundles.emplace_back(wgpuPropItem.Bundle);
-              this->BundleCacheStats.Hits++;
-            }
-          }
-          else
-          {
-            this->WGPURenderEncoder.SetBindGroup(
-              1, this->ActorBindGroup, 1, &wgpuPropItem.DynamicOffset);
-            rendered = wgpuActor->RenderOpaqueGeometry(this);
-          }
-          if (rendered > 0)
-          {
-            result += rendered;
-
-            this->NumberOfPropsRendered += rendered;
-            this->PropsRendered.insert(wgpuActor);
-          }
-        }
-        else
+        const int rendered = this->PropArray[i]->RenderOpaqueGeometry(this);
+        if (rendered > 0)
         {
-          vtkWarningMacro(<< "Prop " << this->PropArray[i]->GetObjectDescription()
-                          << " is unrecognized in the vtkWebGPURenderer.");
+          result += rendered;
+          this->NumberOfPropsRendered += rendered;
+          this->PropsRendered.insert(this->PropArray[i]);
         }
       }
     }
@@ -449,14 +318,7 @@ int vtkWebGPURenderer::UpdateTranslucentPolygonalGeometry()
     {
       for (int i = 0; i < this->PropArrayCount; i++)
       {
-        auto wgpuActor = reinterpret_cast<vtkWebGPUActor*>(this->PropArray[i]);
-        wgpuActor->RenderTranslucentPolygonalGeometry(this);
-        if (wgpuActor->GetBundleInvalidated())
-        {
-          // mapper's buffers and bind points have changed. bundle is outdated.
-          auto& wgpuPropItem = this->PropWGPUItems[this->PropArray[i]];
-          wgpuPropItem.Bundle = nullptr;
-        }
+        this->PropArray[i]->RenderTranslucentPolygonalGeometry(this);
       }
       result += this->PropArrayCount;
     }
@@ -465,69 +327,12 @@ int vtkWebGPURenderer::UpdateTranslucentPolygonalGeometry()
     {
       for (int i = 0; i < this->PropArrayCount; i++)
       {
-        if (auto* wgpuActor = vtkWebGPUActor::SafeDownCast(this->PropArray[i]))
+        const int rendered = this->PropArray[i]->RenderTranslucentPolygonalGeometry(this);
+        if (rendered > 0)
         {
-          int rendered = 0;
-          auto& wgpuPropItem = this->PropWGPUItems[this->PropArray[i]];
-          if (wgpuActor->SupportRenderBundles() && this->UseRenderBundles)
-          {
-            this->BundleCacheStats.TotalRequests++;
-            if (wgpuPropItem.Bundle == nullptr)
-            {
-              const std::string label = this->GetObjectDescription();
-              auto wgpuRenderWindow = vtkWebGPURenderWindow::SafeDownCast(this->GetRenderWindow());
-              const auto colorFormat = wgpuRenderWindow->GetPreferredSurfaceTextureFormat();
-              const int sampleCount =
-                wgpuRenderWindow->GetMultiSamples() ? wgpuRenderWindow->GetMultiSamples() : 1;
-              wgpu::RenderBundleEncoderDescriptor bundleEncDesc;
-              bundleEncDesc.colorFormatCount = 1;
-              bundleEncDesc.colorFormats = &colorFormat;
-              bundleEncDesc.depthStencilFormat = wgpuRenderWindow->GetDepthStencilFormat();
-              bundleEncDesc.sampleCount = sampleCount;
-              bundleEncDesc.depthReadOnly = false;
-              bundleEncDesc.stencilReadOnly = false;
-              bundleEncDesc.label = label.c_str();
-              bundleEncDesc.nextInChain = nullptr;
-              this->WGPUBundleEncoder = wgpuRenderWindow->NewRenderBundleEncoder(bundleEncDesc);
-              this->WGPUBundleEncoder.SetBindGroup(0, this->SceneBindGroup);
-              this->WGPUBundleEncoder.SetBindGroup(
-                1, this->ActorBindGroup, 1, &wgpuPropItem.DynamicOffset);
-              rendered = wgpuActor->RenderTranslucentPolygonalGeometry(this);
-              auto bundle = this->WGPUBundleEncoder.Finish();
-              this->WGPUBundleEncoder = nullptr;
-              if (rendered > 0)
-              {
-                wgpuPropItem.Bundle = bundle;
-                this->Bundles.emplace_back(wgpuPropItem.Bundle);
-              }
-              this->BundleCacheStats.Misses++;
-            }
-            else
-            {
-              // bundle gets reused for this prop.
-              rendered = 1;
-              this->Bundles.emplace_back(wgpuPropItem.Bundle);
-              this->BundleCacheStats.Hits++;
-            }
-          }
-          else
-          {
-            this->WGPURenderEncoder.SetBindGroup(
-              1, this->ActorBindGroup, 1, &wgpuPropItem.DynamicOffset);
-            rendered = wgpuActor->RenderTranslucentPolygonalGeometry(this);
-          }
-          if (rendered > 0)
-          {
-            result += rendered;
-
-            this->NumberOfPropsRendered += rendered;
-            this->PropsRendered.insert(wgpuActor);
-          }
-        }
-        else
-        {
-          vtkWarningMacro(<< "Prop " << this->PropArray[i]->GetObjectDescription()
-                          << " is unrecognized in the vtkWebGPURenderer.");
+          result += rendered;
+          this->NumberOfPropsRendered += rendered;
+          this->PropsRendered.insert(this->PropArray[i]);
         }
       }
     }
@@ -803,17 +608,14 @@ void vtkWebGPURenderer::SetEnvironmentTexture(vtkTexture*, bool vtkNotUsed(isSRG
 void vtkWebGPURenderer::ReleaseGraphicsResources(vtkWindow* w)
 {
   this->Superclass::ReleaseGraphicsResources(w);
-  this->Bundles.clear();
-  this->PropWGPUItems.clear();
+  this->Bundle = nullptr;
+  this->WGPUBundleEncoder = nullptr;
   this->WGPURenderEncoder = nullptr;
   this->SceneTransformBuffer = nullptr;
   this->SceneLightsBuffer = nullptr;
-  this->ActorBlocksBuffer = nullptr;
   this->LastActorBufferSize = 0;
   this->SceneBindGroup = nullptr;
   this->SceneBindGroupLayout = nullptr;
-  this->ActorBindGroup = nullptr;
-  this->ActorBindGroupLayout = nullptr;
 }
 
 //------------------------------------------------------------------------------
@@ -893,11 +695,32 @@ void vtkWebGPURenderer::BeginRecording()
   this->WGPURenderEncoder.PushDebugGroup("Renderer start encoding");
 #endif
   this->WGPURenderEncoder.SetBindGroup(0, this->SceneBindGroup);
-
-  this->Bundles.clear();
-  this->BundleCacheStats.TotalRequests = 0;
-  this->BundleCacheStats.Hits = 0;
-  this->BundleCacheStats.Misses = 0;
+  if (this->BundleInvalidated)
+  {
+    // destroy previous bundle.
+    this->Bundle = nullptr;
+    // create a new bundle encoder.
+    const std::string label = this->GetObjectDescription();
+    auto wgpuRenderWindow = vtkWebGPURenderWindow::SafeDownCast(this->GetRenderWindow());
+    const auto colorFormat = wgpuRenderWindow->GetPreferredSurfaceTextureFormat();
+    const int sampleCount =
+      wgpuRenderWindow->GetMultiSamples() ? wgpuRenderWindow->GetMultiSamples() : 1;
+    wgpu::RenderBundleEncoderDescriptor bundleEncDesc;
+    bundleEncDesc.colorFormatCount = 1;
+    bundleEncDesc.colorFormats = &colorFormat;
+    bundleEncDesc.depthStencilFormat = wgpuRenderWindow->GetDepthStencilFormat();
+    bundleEncDesc.sampleCount = sampleCount;
+    bundleEncDesc.depthReadOnly = false;
+    bundleEncDesc.stencilReadOnly = false;
+    bundleEncDesc.label = label.c_str();
+    bundleEncDesc.nextInChain = nullptr;
+    this->WGPUBundleEncoder = wgpuRenderWindow->NewRenderBundleEncoder(bundleEncDesc);
+    this->WGPUBundleEncoder.SetBindGroup(0, this->SceneBindGroup);
+  }
+  else
+  {
+    this->WGPUBundleEncoder = nullptr;
+  }
 }
 
 //------------------------------------------------------------------------------
@@ -919,19 +742,6 @@ void vtkWebGPURenderer::SetupBindGroupLayouts()
 
     this->SceneBindGroupLayout.SetLabel("SceneBindGroupLayout");
   }
-
-  if (this->ActorBindGroupLayout.Get() == nullptr)
-  {
-    this->ActorBindGroupLayout = vtkWebGPUBindGroupLayoutInternals::MakeBindGroupLayout(device,
-      {
-        // clang-format off
-      // ActorBlocks
-      { 0, wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::Uniform, /*hasDynamicOffsets=*/true },
-        // clang-format on
-      });
-
-    this->ActorBindGroupLayout.SetLabel("ActorBindGroupLayout");
-  }
 }
 
 //------------------------------------------------------------------------------
@@ -951,21 +761,6 @@ void vtkWebGPURenderer::SetupSceneBindGroup()
   this->SceneBindGroup.SetLabel("SceneBindGroup");
 }
 
-//------------------------------------------------------------------------------
-void vtkWebGPURenderer::SetupActorBindGroup()
-{
-  auto wgpuRenderWindow = vtkWebGPURenderWindow::SafeDownCast(this->GetRenderWindow());
-  wgpu::Device device = wgpuRenderWindow->GetDevice();
-  this->ActorBindGroup =
-    vtkWebGPUBindGroupInternals::MakeBindGroup(device, this->ActorBindGroupLayout,
-      {
-        // clang-format off
-        { 0, this->ActorBlocksBuffer, 0, vtkWebGPUConfiguration::Align(vtkWebGPUActor::GetCacheSizeBytes(), 256) },
-        // clang-format on
-      });
-  this->ActorBindGroup.SetLabel("ActorBindGroup");
-}
-
 //------------------------------------------------------------------------------
 void vtkWebGPURenderer::EndRecording()
 {
@@ -973,19 +768,13 @@ void vtkWebGPURenderer::EndRecording()
   this->RenderStage = RenderStageEnum::Finished;
   if (this->UseRenderBundles)
   {
-    if (!this->Bundles.empty() && this->BundleCacheStats.TotalRequests > 0)
+    if (this->WGPUBundleEncoder)
+    {
+      this->Bundle = this->WGPUBundleEncoder.Finish();
+    }
+    if (this->Bundle != nullptr)
     {
-      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->WGPURenderEncoder.ExecuteBundles(1, &this->Bundle);
     }
   }
 #ifndef NDEBUG
diff --git a/Rendering/WebGPU/vtkWebGPURenderer.h b/Rendering/WebGPU/vtkWebGPURenderer.h
index 69b3b90bb33..c7333743f84 100644
--- a/Rendering/WebGPU/vtkWebGPURenderer.h
+++ b/Rendering/WebGPU/vtkWebGPURenderer.h
@@ -110,13 +110,11 @@ public:
 
   inline wgpu::RenderPassEncoder GetRenderPassEncoder() { return this->WGPURenderEncoder; }
   inline wgpu::RenderBundleEncoder GetRenderBundleEncoder() { return this->WGPUBundleEncoder; }
-  inline wgpu::BindGroup GetActorBindGroup() { return this->ActorBindGroup; }
   inline wgpu::BindGroup GetSceneBindGroup() { return this->SceneBindGroup; }
 
   inline void PopulateBindgroupLayouts(std::vector<wgpu::BindGroupLayout>& layouts)
   {
     layouts.emplace_back(this->SceneBindGroupLayout);
-    layouts.emplace_back(this->ActorBindGroupLayout);
   }
 
   /// @{
@@ -165,6 +163,19 @@ public:
    */
   vtkGetEnumMacro(RenderStage, RenderStageEnum);
 
+  /**
+   * Forces the renderer to re-record draw commands into a render bundle.
+   *
+   * @note This does not use vtkSetMacro because the actor MTime should not be affected when a
+   * render bundle is invalidated.
+   */
+  inline void SetBundleInvalidated(bool value) { this->BundleInvalidated = value; }
+
+  /**
+   * Get whether the render bundle associated with this actor must be reset by the renderer.
+   */
+  vtkGetMacro(BundleInvalidated, bool);
+
 protected:
   vtkWebGPURenderer();
   ~vtkWebGPURenderer() override;
@@ -193,35 +204,23 @@ protected:
 
   std::size_t WriteLightsBuffer(std::size_t offset = 0);
   std::size_t WriteSceneTransformsBuffer(std::size_t offset = 0);
-  std::size_t WriteActorBlocksBuffer(std::size_t offset = 0);
 
   wgpu::RenderPassEncoder WGPURenderEncoder;
   wgpu::RenderBundleEncoder WGPUBundleEncoder;
   wgpu::Buffer SceneTransformBuffer;
   wgpu::Buffer SceneLightsBuffer;
-  wgpu::Buffer ActorBlocksBuffer;
   std::size_t LastActorBufferSize = 0;
   wgpu::BindGroup SceneBindGroup;
   wgpu::BindGroupLayout SceneBindGroupLayout;
 
-  wgpu::BindGroup ActorBindGroup;
-  wgpu::BindGroupLayout ActorBindGroupLayout;
-
 #ifdef __EMSCRIPTEN__
   bool UseRenderBundles = true;
 #else
-  bool UseRenderBundles = false;
+  bool UseRenderBundles = true;
 #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<wgpu::RenderBundle> Bundles;
-  struct vtkWGPUPropItem
-  {
-    wgpu::RenderBundle Bundle = nullptr;
-    vtkTypeUInt32 DynamicOffset = 0;
-  };
-  std::unordered_map<vtkProp*, vtkWGPUPropItem> PropWGPUItems;
+  bool BundleInvalidated = false;
+  // the commands in bundle get reused every frame.
+  wgpu::RenderBundle Bundle;
 
   int LightingComplexity = 0;
   std::size_t NumberOfLightsUsed = 0;
@@ -230,13 +229,6 @@ protected:
   vtkMTimeType LightingUpdateTime;
   vtkTimeStamp LightingUploadTimestamp;
 
-  struct
-  {
-    uint32_t Hits = 0;
-    uint32_t Misses = 0;
-    uint32_t TotalRequests = 0;
-  } BundleCacheStats;
-
   /**
    * Optional user transform for lights
    */
diff --git a/Rendering/WebGPU/wgsl/LineMiterJoinVertexShader.wgsl b/Rendering/WebGPU/wgsl/LineMiterJoinVertexShader.wgsl
index e25057be618..f29e73cbe1a 100644
--- a/Rendering/WebGPU/wgsl/LineMiterJoinVertexShader.wgsl
+++ b/Rendering/WebGPU/wgsl/LineMiterJoinVertexShader.wgsl
@@ -85,7 +85,7 @@ struct Topology {
 ///-------------------------------------------------------------------
 // Everything shader needs from the vtkActor and it's vtkProperty
 ///-------------------------------------------------------------------
-@group(1) @binding(0) var<uniform> actor: ActorBlock;
+@group(1) @binding(0) var<storage, read> actor: ActorBlock;
 
 ///-----------------------------------------------------------------///
 // Mesh attributes.
diff --git a/Rendering/WebGPU/wgsl/LineRoundJoinVertexShader.wgsl b/Rendering/WebGPU/wgsl/LineRoundJoinVertexShader.wgsl
index 9e81b90a26d..a0773d1693f 100644
--- a/Rendering/WebGPU/wgsl/LineRoundJoinVertexShader.wgsl
+++ b/Rendering/WebGPU/wgsl/LineRoundJoinVertexShader.wgsl
@@ -138,7 +138,7 @@ struct Topology {
 ///-------------------------------------------------------------------
 // Everything shader needs from the vtkActor and it's vtkProperty
 ///-------------------------------------------------------------------
-@group(1) @binding(0) var<uniform> actor: ActorBlock;
+@group(1) @binding(0) var<storage, read> actor: ActorBlock;
 
 ///-----------------------------------------------------------------///
 // Mesh attributes.
diff --git a/Rendering/WebGPU/wgsl/PointShader.wgsl b/Rendering/WebGPU/wgsl/PointShader.wgsl
index 33f217c7c99..193197ff5f9 100644
--- a/Rendering/WebGPU/wgsl/PointShader.wgsl
+++ b/Rendering/WebGPU/wgsl/PointShader.wgsl
@@ -89,7 +89,7 @@ struct Topology {
 ///-------------------------------------------------------------------
 // Everything shader needs from the vtkActor and it's vtkProperty
 ///-------------------------------------------------------------------
-@group(1) @binding(0) var<uniform> actor: ActorBlock;
+@group(1) @binding(0) var<storage, read> actor: ActorBlock;
 
 ///-----------------------------------------------------------------///
 // Mesh attributes.
diff --git a/Rendering/WebGPU/wgsl/SurfaceMeshShader.wgsl b/Rendering/WebGPU/wgsl/SurfaceMeshShader.wgsl
index c55ba3bfed2..b9186314859 100644
--- a/Rendering/WebGPU/wgsl/SurfaceMeshShader.wgsl
+++ b/Rendering/WebGPU/wgsl/SurfaceMeshShader.wgsl
@@ -70,7 +70,7 @@ struct Topology {
 ///-------------------------------------------------------------------
 // Everything shader needs from the vtkActor and it's vtkProperty
 ///-------------------------------------------------------------------
-@group(1) @binding(0) var<uniform> actor: ActorBlock;
+@group(1) @binding(0) var<storage, read> actor: ActorBlock;
 
 ///-----------------------------------------------------------------///
 // Mesh attributes.
-- 
GitLab