// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
#include "vtkWebGPURenderWindow.h"
#include "Private/vtkWebGPUBindGroupInternals.h"
#include "Private/vtkWebGPUBindGroupLayoutInternals.h"
#include "Private/vtkWebGPUComputePassInternals.h"
#include "Private/vtkWebGPUPipelineLayoutInternals.h"
#include "Private/vtkWebGPURenderPassDescriptorInternals.h"
#include "Private/vtkWebGPURenderPipelineDescriptorInternals.h"
#include "vtkCollectionRange.h"
#include "vtkFloatArray.h"
#include "vtkHardwareWindow.h"
#include "vtkImageData.h"
#include "vtkObjectFactory.h"
#include "vtkOverrideAttribute.h"
#include "vtkPointData.h"
#include "vtkRendererCollection.h"
#include "vtkSmartPointer.h"
#include "vtkTypeUInt32Array.h"
#include "vtkUnsignedCharArray.h"
#include "vtkWebGPUCommandEncoderDebugGroup.h"
#include "vtkWebGPUConfiguration.h"
#include "vtkWebGPUHelpers.h"
#include "vtkWebGPURenderer.h"
#if !defined(__EMSCRIPTEN__)
#ifdef _WIN32
#include "vtkWin32HardwareWindow.h"
#elif __APPLE__
#include "vtkCocoaHardwareWindow.h"
#elif VTK_USE_Wayland
#include "vtkWaylandHardwareWindow.h"
#elif VTK_USE_X
#include "vtkXlibHardwareWindow.h"
#include <unistd.h> // for close()
#endif              // _WIN32, __APPLE__, VTK_USE_Wayland, Xlib
#else               // __EMSCRIPTEN__
#include "vtkWebAssemblyHardwareWindow.h"
#endif // !__EMSCRIPTEN__

#include "vtksys/SystemTools.hxx"

#include "CopyDepthTextureToBuffer.h"

#include <sstream>
#include <webgpu/webgpu_cpp.h>

#if defined(__EMSCRIPTEN__)
#include "emscripten/version.h"
#endif

VTK_ABI_NAMESPACE_BEGIN

#define vtkWebGPUCheckUnconfiguredWithReturn(renderWindow, retVal)                                 \
  do                                                                                               \
  {                                                                                                \
    if (renderWindow->WGPUConfiguration == nullptr)                                                \
    {                                                                                              \
      vtkErrorWithObjectMacro(                                                                     \
        renderWindow, << "This render window is not configured to use webgpu. Please call "        \
                         "vtkWebGPURenderWindow::SetWGPUConfiguration().");                        \
      return retVal;                                                                               \
    }                                                                                              \
  } while (0)

#define vtkWebGPUCheckUnconfigured(renderWindow)                                                   \
  do                                                                                               \
  {                                                                                                \
    if (renderWindow->WGPUConfiguration == nullptr)                                                \
    {                                                                                              \
      vtkErrorWithObjectMacro(                                                                     \
        renderWindow, << "This render window is not configured to use webgpu. Please call "        \
                         "vtkWebGPURenderWindow::SetWGPUConfiguration().");                        \
      return;                                                                                      \
    }                                                                                              \
  } while (0)

namespace
{
struct InternalMapTextureAsyncData
{
  // 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;

  // 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;
};
}

vtkStandardNewMacro(vtkWebGPURenderWindow);
//------------------------------------------------------------------------------
vtkWebGPURenderWindow::vtkWebGPURenderWindow()
{
  this->ScreenSize[0] = 0;
  this->ScreenSize[1] = 0;
  this->Size[0] = 300;
  this->Size[1] = 300;
  this->WGPUConfiguration = vtk::TakeSmartPointer(vtkWebGPUConfiguration::New());
}

//------------------------------------------------------------------------------
vtkWebGPURenderWindow::~vtkWebGPURenderWindow()
{
  this->Finalize();
  this->ReleaseGraphicsResources(this);

  vtkRenderer* renderer;
  vtkCollectionSimpleIterator rit;
  this->Renderers->InitTraversal(rit);
  while ((renderer = this->Renderers->GetNextRenderer(rit)))
  {
    renderer->SetRenderWindow(nullptr);
  }
  this->SetHardwareWindow(nullptr);
  this->WGPUConfiguration = nullptr;
}

//------------------------------------------------------------------------------
vtkOverrideAttribute* vtkWebGPURenderWindow::CreateOverrideAttributes()
{
  auto* renderingBackendAttribute =
    vtkOverrideAttribute::CreateAttributeChain("RenderingBackend", "WebGPU", nullptr);
  return renderingBackendAttribute;
}

//------------------------------------------------------------------------------
void vtkWebGPURenderWindow::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
}

//------------------------------------------------------------------------------
bool vtkWebGPURenderWindow::WGPUInit()
{
  vtkDebugMacro(<< __func__ << " Initialized=" << this->Initialized);
  vtkWebGPUCheckUnconfiguredWithReturn(this, false);
  if (!this->WGPUConfiguration->Initialize())
  {
    return false;
  }
  if (this->WindowName == std::string("Visualization Toolkit"))
  {
    std::string windowNameWithBackend = this->MakeDefaultWindowNameWithBackend();
    this->SetWindowName(windowNameWithBackend.c_str());
  }
  return true;
}

//------------------------------------------------------------------------------
void vtkWebGPURenderWindow::CreateSurface()
{
  if (!this->HardwareWindow)
  {
    // vtkErrorMacro(<< "Cannot create surface without a hardware window.");
    return;
  }

#ifdef __EMSCRIPTEN__
  if (auto* wasmhw = vtkWebAssemblyHardwareWindow::SafeDownCast(this->HardwareWindow))
  {
    wgpu::EmscriptenSurfaceSourceCanvasHTMLSelector htmlSurfDesc;
    htmlSurfDesc.selector = wasmhw->GetCanvasSelector();
    wgpu::SurfaceDescriptor surfDesc = {};
    surfDesc.label = "VTK HTML5 surface";
    surfDesc.nextInChain = &htmlSurfDesc;
    this->Surface = this->WGPUConfiguration->GetInstance().CreateSurface(&surfDesc);
  }
#elif _WIN32
  if (auto* win32hw = vtkWin32HardwareWindow::SafeDownCast(this->HardwareWindow))
  {
    wgpu::SurfaceSourceWindowsHWND winSurfDesc;
    winSurfDesc.hwnd = win32hw->GetWindowId();
    winSurfDesc.hinstance = win32hw->GetApplicationInstance();
    wgpu::SurfaceDescriptor surfDesc = {};
    surfDesc.label = "VTK Win32 surface";
    surfDesc.nextInChain = &winSurfDesc;
    this->Surface = this->WGPUConfiguration->GetInstance().CreateSurface(&surfDesc);
  }
#elif defined VTK_USE_Wayland
  if (auto* waylandhw = vtkWaylandHardwareWindow::SafeDownCast(this->HardwareWindow))
  {
    wgpu::SurfaceSourceWaylandSurface waylandSurfDesc;
    waylandSurfDesc.surface = waylandhw->GetWindowId();
    waylandSurfDesc.display = waylandhw->GetDisplayId();
    wgpu::SurfaceDescriptor surfDesc = {};
    surfDesc.label = "VTK Wayland surface";
    surfDesc.nextInChain = &waylandSurfDesc;
    this->Surface = this->WGPUConfiguration->GetInstance().CreateSurface(&surfDesc);
  }
#elif defined __APPLE__
  if (auto* cocoahw = vtkCocoaHardwareWindow::SafeDownCast(this->HardwareWindow))
  {
    wgpu::SurfaceSourceMetalLayer metalSurfDesc;
    metalSurfDesc.layer = cocoahw->GetMetalLayer();
    wgpu::SurfaceDescriptor surfDesc = {};
    surfDesc.label = "VTK Cocoa surface";
    surfDesc.nextInChain = &metalSurfDesc;
    this->Surface = this->WGPUConfiguration->GetInstance().CreateSurface(&surfDesc);
  }
#else // Xlib
  if (auto* xlibhw = vtkXlibHardwareWindow::SafeDownCast(this->HardwareWindow))
  {
    xlibhw->SetWindowName("VTK Xlib window");
    wgpu::SurfaceSourceXlibWindow xlibSurfDesc;
    xlibSurfDesc.display = xlibhw->GetDisplayId();
    xlibSurfDesc.window = xlibhw->GetWindowId();
    wgpu::SurfaceDescriptor surfDesc = {};
    surfDesc.label = "VTK Xlib surface";
    surfDesc.nextInChain = &xlibSurfDesc;
    this->Surface = this->WGPUConfiguration->GetInstance().CreateSurface(&surfDesc);
  }
#endif
}

//------------------------------------------------------------------------------
void vtkWebGPURenderWindow::Initialize()
{
  this->CreateAWindow();
  if (this->WGPUInit())
  {
    this->CreateSurface();
  }
  this->ConfigureSurface();
  this->CreateOffscreenColorAttachment();
  this->CreateIdsAttachment();
  this->CreateDepthStencilAttachment();
  this->CreateColorCopyPipeline();
  this->InitializeRendererComputePipelines();

  this->Initialized = true;
}

//------------------------------------------------------------------------------
void vtkWebGPURenderWindow::Finalize()
{
  this->WGPUFinalize();
}

//------------------------------------------------------------------------------
void vtkWebGPURenderWindow::WGPUFinalize()
{
  vtkDebugMacro(<< __func__ << " Initialized=" << this->Initialized);
  vtkWebGPUCheckUnconfigured(this);
  if (!this->Initialized)
  {
    return;
  }
  this->ReleaseGraphicsResources(this);

  // x11 display cleanup ordering
  //
  // NVIDIA's Vulkan driver defers GLX extension initialization until device
  // destruction time. During this deferred initialization, the driver modifies
  // X11 Display state (e.g., registering close_display handlers). If the Display
  // is already closed before Vulkan finalization, the driver attempts to access
  // freed memory, causing a segmentation fault in XCloseDisplay().
  //
  // Minimal repro example shared with NVIDIA: https://github.com/sankhesh/nvidia-vulkan-reproducer
  //
  // The correct cleanup sequence on X11 is:
  //   1. DestroyWindow()    — destroy the X11 window (keep Display open)
  //   2. FinalizeDevice()   — destroy Vulkan device/adapter; instance stays alive
  //                           so the ICD shared library remains loaded
  //   3. CloseDisplay()     — call XCloseDisplay() while the ICD is still in memory
  //                           (its close_display handlers are therefore still valid)
  //   4. Finalize()         — destroy the Vulkan instance; the Vulkan loader
  //                           dlclose()s the ICD — Display already closed, safe
  //
  // We prevent DestroyWindow() from also closing the Display (it would, because
  // vtkXlibHardwareWindow::Destroy() always calls CloseDisplay() at its end) by
  // temporarily setting OwnDisplay=false before the call and restoring it after.
  // The destructor's guard (WindowId != 0) would skip CloseDisplay() anyway since
  // DestroyWindow() zeroes WindowId, so we must call CloseDisplay() explicitly.

#if defined(VTK_USE_X)
  bool savedOwnDisplay = false;
  bool isNVIDIA = false; // queried now, before the adapter is destroyed
  vtkXlibHardwareWindow* xlibWindowPtr = nullptr;
  if (auto xlibWindow = vtkXlibHardwareWindow::SafeDownCast(this->HardwareWindow))
  {
    xlibWindowPtr = xlibWindow;
    savedOwnDisplay = xlibWindow->GetOwnDisplay();
    isNVIDIA = this->WGPUConfiguration->IsNVIDIAGPUInUse();
    // Prevent DestroyWindow() → Destroy() → CloseDisplay() from firing.
    xlibWindow->SetOwnDisplay(false);
  }
#endif // VTK_USE_X

  // Destroy the X11 window (Display connection is still open).
  this->DestroyWindow();

  // Destroy Vulkan device/adapter; keep the instance alive so the ICD
  // library (and its close_display handlers) remain loaded in memory.
  this->WGPUConfiguration->FinalizeDevice();

  // Close the Display.
  //
  // === Begin workaround ===
  // Instead of XCloseDisplay(), close the socket file descriptor
  // directly. The X server sees the EOF and immediately releases the client
  // slot (freeing the server-side slot and the per-process OS FD), but Xlib
  // never iterates its close_display handler list — so the NVIDIA handler is
  // never called. We clear DisplayId to prevent any later Xlib use.
  //
  // When the issue is fixed in the nvidia driver, remove the if/else below and
  // replace with the unconditional call that is already used for non-NVIDIA:
  //
  //   if (xlibWindowPtr && savedOwnDisplay)
  //   {
  //     xlibWindowPtr->SetOwnDisplay(true);
  //     xlibWindowPtr->CloseDisplay();
  //   }
  //
  // Also remove the isNVIDIA variable above and the <unistd.h> include.
#if defined(VTK_USE_X)
  if (xlibWindowPtr && savedOwnDisplay)
  {
    if (isNVIDIA)
    {
      Display* dpy = xlibWindowPtr->GetDisplayId();
      if (dpy)
      {
        close(ConnectionNumber(dpy));
        xlibWindowPtr->SetDisplayId(static_cast<void*>(nullptr));
      }
    }
    else
    {
      xlibWindowPtr->SetOwnDisplay(true);
      xlibWindowPtr->CloseDisplay();
    }
  }
  // === End workaround ===
#endif // VTK_USE_X

  // Finally, release the Vulkan instance — triggers vkDestroyInstance() and
  // unloads the ICD. Display is already closed so no dangling handler fires.
  this->WGPUConfiguration->Finalize();

  this->Initialized = false;
}

//------------------------------------------------------------------------------
void vtkWebGPURenderWindow::Render()
{
  vtkDebugMacro(<< __func__);
  if (this->HardwareWindow != nullptr)
  {
    this->SyncWithHardware();
    this->HardwareWindow->Render();
  }
  this->Superclass::Render();
}

//------------------------------------------------------------------------------
wgpu::RenderPassEncoder vtkWebGPURenderWindow::NewRenderPass(wgpu::RenderPassDescriptor& descriptor)
{
  if (this->CommandEncoder)
  {
    return this->CommandEncoder.BeginRenderPass(&descriptor);
  }
  else
  {
    vtkErrorMacro(<< "Cannot create a new render pass because CommandEncoder is null!");
    return nullptr;
  }
}

//------------------------------------------------------------------------------
wgpu::RenderBundleEncoder vtkWebGPURenderWindow::NewRenderBundleEncoder(
  wgpu::RenderBundleEncoderDescriptor& descriptor)
{
  vtkWebGPUCheckUnconfiguredWithReturn(this, nullptr);
  if (auto device = this->WGPUConfiguration->GetDevice())
  {
    return device.CreateRenderBundleEncoder(&descriptor);
  }
  else
  {
    vtkErrorMacro(<< "Cannot create a render bundle encoder because WebGPU device is not ready!");
    return nullptr;
  }
}

//------------------------------------------------------------------------------
wgpu::CommandEncoder vtkWebGPURenderWindow::GetCommandEncoder()
{
  return this->CommandEncoder;
}

//------------------------------------------------------------------------------
wgpu::TextureView vtkWebGPURenderWindow::GetOffscreenColorAttachmentView()
{
  return this->ColorAttachment.View;
}

//------------------------------------------------------------------------------
wgpu::TextureView vtkWebGPURenderWindow::GetHardwareSelectorAttachmentView()
{
  return this->IdsAttachment.View;
}

//------------------------------------------------------------------------------
wgpu::TextureView vtkWebGPURenderWindow::GetDepthStencilView()
{
  return this->DepthStencilAttachment.View;
}

//------------------------------------------------------------------------------
wgpu::TextureFormat vtkWebGPURenderWindow::GetDepthStencilFormat()
{
  return this->DepthStencilAttachment.Format;
}

//------------------------------------------------------------------------------
bool vtkWebGPURenderWindow::HasStencil()
{
  return this->DepthStencilAttachment.HasStencil;
}

//------------------------------------------------------------------------------
wgpu::Device vtkWebGPURenderWindow::GetDevice()
{
  vtkWebGPUCheckUnconfiguredWithReturn(this, nullptr);
  return this->WGPUConfiguration->GetDevice();
}

//------------------------------------------------------------------------------
wgpu::Adapter vtkWebGPURenderWindow::GetAdapter()
{
  vtkWebGPUCheckUnconfiguredWithReturn(this, nullptr);
  return this->WGPUConfiguration->GetAdapter();
}

//------------------------------------------------------------------------------
wgpu::TextureFormat vtkWebGPURenderWindow::GetPreferredSurfaceTextureFormat()
{
  return this->PreferredSurfaceTextureFormat;
}

//------------------------------------------------------------------------------
wgpu::TextureFormat vtkWebGPURenderWindow::GetPreferredSelectorIdsTextureFormat()
{
  return this->PreferredSelectorIdsTextureFormat;
}

//------------------------------------------------------------------------------
vtkSmartPointer<vtkWebGPUComputeRenderTexture>
vtkWebGPURenderWindow::AcquireDepthBufferRenderTexture()
{
  if (!this->Initialized)
  {
    vtkLog(ERROR,
      "You must call vtkRenderWindow::Initialize() before acquiring a render texture from the "
      "RenderWindow.");
    return nullptr;
  }

  vtkSmartPointer<vtkWebGPUComputeRenderTexture> texture =
    vtkSmartPointer<vtkWebGPUComputeRenderTexture>::New();

  int* dims = this->GetSize();

  texture->SetSize(dims[0], dims[1]);
  texture->SetMode(vtkWebGPUComputeTexture::TextureMode::READ_ONLY);
  texture->SetSampleType(vtkWebGPUComputeTexture::TextureSampleType::DEPTH);
  texture->SetAspect(vtkWebGPUComputeTextureView::TextureViewAspect::ASPECT_DEPTH);
  texture->SetLabel("Depth buffer render texture");
  texture->SetType(vtkWebGPUComputeRenderTexture::RenderTextureType::DEPTH_BUFFER);
  texture->SetWebGPUTexture(this->DepthStencilAttachment.Texture);
  texture->SetFormat(vtkWebGPUComputeTexture::TextureFormat::DEPTH_24_PLUS_8_STENCIL);

  this->ComputeRenderTextures.push_back(texture);

  return texture;
}

//------------------------------------------------------------------------------
vtkSmartPointer<vtkWebGPUComputeRenderTexture>
vtkWebGPURenderWindow::AcquireFramebufferRenderTexture()
{
  vtkSmartPointer<vtkWebGPUComputeRenderTexture> texture =
    vtkSmartPointer<vtkWebGPUComputeRenderTexture>::New();

  int* dims = this->GetSize();

  texture->SetSize(dims[0], dims[1]);
  texture->SetFormat(vtkWebGPUTextureDeviceResource::TextureFormat::BGRA8_UNORM);
  texture->SetSampleType(vtkWebGPUComputeTexture::TextureSampleType::FLOAT);
  texture->SetLabel("Framebuffer render texture");
  texture->SetType(vtkWebGPUComputeRenderTexture::RenderTextureType::COLOR_BUFFER);
  texture->SetMode(vtkWebGPUComputeRenderTexture::TextureMode::WRITE_ONLY_STORAGE);
  texture->SetWebGPUTexture(this->ColorAttachment.Texture);
  texture->SetAspect(vtkWebGPUComputeTextureView::TextureViewAspect::ASPECT_ALL);

  this->ComputeRenderTextures.push_back(texture);

  return texture;
}

//------------------------------------------------------------------------------
void vtkWebGPURenderWindow::CreateCommandEncoder()
{
  vtkWebGPUCheckUnconfigured(this);
  wgpu::CommandEncoderDescriptor encDesc = {};
  encDesc.label = "vtkWebGPURenderWindow::CommandEncoder";
  if (auto device = this->WGPUConfiguration->GetDevice())
  {
    this->CommandEncoder = device.CreateCommandEncoder(&encDesc);
  }
  else
  {
    vtkErrorMacro(
      << "Cannot create a command encoder because a WebGPU device has not been initialized!");
  }
}

//------------------------------------------------------------------------------
void vtkWebGPURenderWindow::InitializeRendererComputePipelines()
{
  for (auto renderer : vtk::Range(this->Renderers))
  {
    if (vtkWebGPURenderer* wgpuRenderer = vtkWebGPURenderer::SafeDownCast(renderer))
    {
      wgpuRenderer->ConfigureComputePipelines();
    }
    else
    {
      vtkWarningMacro(<< "Renderer is not a vtkWebGPURenderer. Cannot initialize compute "
                         "pipelines. Please ensure object factory preferences include "
                         "'RenderingBackend=WebGPU'.");
    }
  }
}

//------------------------------------------------------------------------------
void vtkWebGPURenderWindow::SubmitCommandBuffer(int count, wgpu::CommandBuffer* commandBuffer)
{
  this->FlushCommandBuffers(count, commandBuffer);
}

//------------------------------------------------------------------------------
void vtkWebGPURenderWindow::ConfigureSurface()
{
  vtkDebugMacro(<< __func__ << '(' << this->Size[0] << ',' << this->Size[1] << ')');
  vtkWebGPUCheckUnconfigured(this);
  // Configure the surface.
  wgpu::SurfaceCapabilities capabilities;
  this->Surface.GetCapabilities(this->GetAdapter(), &capabilities);
  if (capabilities.formatCount > 0)
  {
    wgpu::SurfaceConfiguration config = {};
    config.usage = wgpu::TextureUsage::RenderAttachment;
    config.device = this->GetDevice();
    config.width = this->Size[0];
    config.height = this->Size[1];
    config.presentMode =
      wgpu::PresentMode::Fifo; // fallback. this stalls GetCurrentTexture to VSync interval
    for (std::size_t i = 0; i < capabilities.presentModeCount; ++i)
    {
      if (capabilities.presentModes[i] == wgpu::PresentMode::Mailbox)
      {
        config.presentMode = wgpu::PresentMode::Mailbox;
        break;
      }
    }
    for (std::size_t i = 0; i < capabilities.formatCount; ++i)
    {
      config.format = capabilities.formats[i];
      // prefer BGRA8Unorm
      if (config.format == wgpu::TextureFormat::BGRA8Unorm)
      {
        break;
      }
    }
    this->Surface.Configure(&config);
    this->PreferredSurfaceTextureFormat = config.format;
    this->SurfaceConfiguredSize[0] = this->Size[0];
    this->SurfaceConfiguredSize[1] = this->Size[1];
  }
}

//------------------------------------------------------------------------------
void vtkWebGPURenderWindow::UnconfigureSurface()
{
  vtkDebugMacro(<< __func__);
  if (this->Surface == nullptr)
  {
    return;
  }
  this->Surface.Unconfigure();
}

//------------------------------------------------------------------------------
void vtkWebGPURenderWindow::CreateDepthStencilAttachment()
{
  vtkDebugMacro(<< __func__ << '(' << this->SurfaceConfiguredSize[0] << ','
                << this->SurfaceConfiguredSize[1] << ')');
  vtkWebGPUCheckUnconfigured(this);
  auto device = this->WGPUConfiguration->GetDevice();
  if (device == nullptr)
  {
    vtkErrorMacro(<< "Cannot create a depth stencil texture because WebGPU device is not ready!");
    return;
  }
  // TODO:
  // setup basic depth attachment
  // todo: verify device supports this depth and stencil format in feature set
  this->DepthStencilAttachment.HasStencil = true;

  const std::string textureLabel = "DepthStencil-" + this->GetObjectDescription();
  wgpu::TextureDescriptor textureDesc;
  textureDesc.label = textureLabel.c_str();
  textureDesc.dimension = wgpu::TextureDimension::e2D;
  textureDesc.size.width = this->SurfaceConfiguredSize[0];
  textureDesc.size.height = this->SurfaceConfiguredSize[1];
  textureDesc.size.depthOrArrayLayers = 1;
  textureDesc.sampleCount = 1;
  textureDesc.format = wgpu::TextureFormat::Depth24PlusStencil8;
  textureDesc.mipLevelCount = 1;
  // TextureBinding here because we may want to use the depth buffer as a vtkWebComputeRenderTexture
  // (which will be bound to a compute shader)
  textureDesc.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding;

  // view
  wgpu::TextureViewDescriptor textureViewDesc;
  textureViewDesc.dimension = wgpu::TextureViewDimension::e2D;
  textureViewDesc.format = textureDesc.format;
  textureViewDesc.baseMipLevel = 0;
  textureViewDesc.mipLevelCount = 1;
  textureViewDesc.baseArrayLayer = 0;
  textureViewDesc.arrayLayerCount = 1;
  // To be able to access the depth part of the depth-stencil buffer in a compute pipeline
  textureViewDesc.aspect = wgpu::TextureAspect::All;

  if (auto texture = this->WGPUConfiguration->CreateTexture(textureDesc))
  {
    this->DepthStencilAttachment.Texture = texture;
    if (auto view = this->WGPUConfiguration->CreateView(texture, textureViewDesc))
    {
      this->DepthStencilAttachment.View = view;
      this->DepthStencilAttachment.Format = textureDesc.format;
    }
    else
    {
      vtkErrorMacro(<< "Failed to create a texture view for depth stencil attachment using texture "
                    << texture.Get());
    }
  }
  else
  {
    vtkErrorMacro(<< "Failed to create a texture for depth stencil attachment using device "
                  << device.Get());
  }
}

//------------------------------------------------------------------------------
void vtkWebGPURenderWindow::DestroyDepthStencilAttachment()
{
  vtkDebugMacro(<< __func__);
  this->DepthStencilAttachment.View = nullptr;
  this->DepthStencilAttachment.Texture = nullptr;
}

//------------------------------------------------------------------------------
void vtkWebGPURenderWindow::CreateOffscreenColorAttachment()
{
  vtkWebGPUCheckUnconfigured(this);
  auto device = this->WGPUConfiguration->GetDevice();
  if (device == nullptr)
  {
    vtkErrorMacro(
      << "Cannot create offscreen color attachments because WebGPU device is not ready!");
    return;
  }
  // must match swapchain's dimensions as we'll eventually sample from this.
  wgpu::Extent3D textureExtent;
  textureExtent.depthOrArrayLayers = 1;
  textureExtent.width = this->SurfaceConfiguredSize[0];
  textureExtent.height = this->SurfaceConfiguredSize[1];

  // Color attachment
  const std::string textureLabel = "OffscreenColor-" + this->GetObjectDescription();
  wgpu::TextureDescriptor textureDesc;
  textureDesc.label = textureLabel.c_str();
  textureDesc.size = textureExtent;
  textureDesc.mipLevelCount = 1;
  textureDesc.sampleCount = 1;
  textureDesc.dimension = wgpu::TextureDimension::e2D;
  textureDesc.format = this->GetPreferredSurfaceTextureFormat();
  textureDesc.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding |
    wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::StorageBinding;
  textureDesc.viewFormatCount = 0;
  textureDesc.viewFormats = nullptr;

  // view
  wgpu::TextureViewDescriptor textureViewDesc;
  textureViewDesc.dimension = wgpu::TextureViewDimension::e2D;
  textureViewDesc.format = textureDesc.format;
  textureViewDesc.baseMipLevel = 0;
  textureViewDesc.mipLevelCount = 1;
  textureViewDesc.baseArrayLayer = 0;
  textureViewDesc.arrayLayerCount = 1;

  if (auto texture = this->WGPUConfiguration->CreateTexture(textureDesc))
  {
    this->ColorAttachment.Texture = texture;
    if (auto view = this->WGPUConfiguration->CreateView(texture, textureViewDesc))
    {
      this->ColorAttachment.View = view;
      this->ColorAttachment.Format = textureDesc.format;
    }
    else
    {
      vtkErrorMacro(<< "Failed to create a texture view for color attachment using texture "
                    << texture.Get());
    }
  }
  else
  {
    vtkErrorMacro(<< "Failed to create a texture for color attachment using device "
                  << device.Get());
    return;
  }
}

//------------------------------------------------------------------------------
void vtkWebGPURenderWindow::DestroyOffscreenColorAttachment()
{
  this->ColorAttachment.View = nullptr;
  this->ColorAttachment.Texture = nullptr;
}

//------------------------------------------------------------------------------
void vtkWebGPURenderWindow::CreateIdsAttachment()
{
  vtkWebGPUCheckUnconfigured(this);
  auto device = this->WGPUConfiguration->GetDevice();
  if (device == nullptr)
  {
    vtkErrorMacro(
      << "Cannot create offscreen color attachments because WebGPU device is not ready!");
    return;
  }
  // must match swapchain's dimensions as we'll eventually sample from this.
  wgpu::Extent3D textureExtent;
  textureExtent.depthOrArrayLayers = 1;
  textureExtent.width = this->SurfaceConfiguredSize[0];
  textureExtent.height = this->SurfaceConfiguredSize[1];

  // selector attachment for cell id
  const std::string textureLabel = "HardwareSelector-" + this->GetObjectDescription();
  wgpu::TextureDescriptor textureDesc;
  textureDesc.label = textureLabel.c_str();
  textureDesc.size = textureExtent;
  textureDesc.mipLevelCount = 1;
  textureDesc.sampleCount = 1;
  textureDesc.dimension = wgpu::TextureDimension::e2D;
  textureDesc.format = this->PreferredSelectorIdsTextureFormat;
  textureDesc.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc;
  textureDesc.viewFormatCount = 0;
  textureDesc.viewFormats = nullptr;

  // view
  wgpu::TextureViewDescriptor textureViewDesc;
  textureViewDesc.dimension = wgpu::TextureViewDimension::e2D;
  textureViewDesc.format = textureDesc.format;
  textureViewDesc.baseMipLevel = 0;
  textureViewDesc.mipLevelCount = 1;
  textureViewDesc.baseArrayLayer = 0;
  textureViewDesc.arrayLayerCount = 1;

  if (auto texture = this->WGPUConfiguration->CreateTexture(textureDesc))
  {
    this->IdsAttachment.Texture = texture;
    if (auto view = this->WGPUConfiguration->CreateView(texture, textureViewDesc))
    {
      this->IdsAttachment.View = view;
      this->IdsAttachment.Format = textureDesc.format;
    }
    else
    {
      vtkErrorMacro(<< "Failed to create a texture view for color attachment using texture "
                    << texture.Get());
    }
  }
  else
  {
    vtkErrorMacro(<< "Failed to create a texture for color attachment using device "
                  << device.Get());
    return;
  }
}

//------------------------------------------------------------------------------
void vtkWebGPURenderWindow::DestroyIdsAttachment()
{
  this->IdsAttachment.View = nullptr;
  this->IdsAttachment.Texture = nullptr;
}

//------------------------------------------------------------------------------
void vtkWebGPURenderWindow::CreateColorCopyPipeline()
{
  vtkWebGPUCheckUnconfigured(this);
  auto device = this->WGPUConfiguration->GetDevice();
  if (device == nullptr)
  {
    vtkErrorMacro(<< "Cannot create full-screen-quad graphics pipeline because WebGPU device "
                     "is not ready!");
    return;
  }
  wgpu::BindGroupLayout bgl = vtkWebGPUBindGroupLayoutInternals::MakeBindGroupLayout(device,
    {
      // clang-format off
      { 0, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float, wgpu::TextureViewDimension::e2D, /*multiSampled=*/false }
      // clang-format on
    },
    std::string("ColorCopy-") + this->GetObjectDescription());

  wgpu::PipelineLayout pipelineLayout =
    vtkWebGPUPipelineLayoutInternals::MakeBasicPipelineLayout(device, &bgl);
  pipelineLayout.SetLabel("FSQ Color Copy pipeline layout");

  this->ColorCopyRenderPipeline.BindGroup = vtkWebGPUBindGroupInternals::MakeBindGroup(device, bgl,
    {
      // clang-formt off
      { 0, this->ColorAttachment.View }
      // clang-format on
    },
    std::string("ColorCopy-") + this->GetObjectDescription());

  const char* shaderSource = R"(
    struct VertexOutput {
      @builtin(position) position: vec4<f32>,
    }

    @vertex
    fn vertexMain(@builtin(vertex_index) vertex_id: u32) -> VertexOutput {
      var output: VertexOutput;
      var coords: array<vec2<f32>, 4> = array<vec2<f32>, 4>(
        vec2<f32>(-1, -1), // bottom-left
        vec2<f32>(-1,  1), // top-left
        vec2<f32>( 1, -1), // bottom-right
        vec2<f32>( 1,  1)  // top-right
      );
      output.position = vec4<f32>(coords[vertex_id].xy, 1.0, 1.0);
      return output;
    }

    struct FragmentInput {
      @builtin(position) position: vec4<f32>,
    }

    @group(0) @binding(0) var fsqTexture: texture_2d<f32>;

    @fragment
    fn fragmentMain(fragment: FragmentInput) -> @location(0) vec4<f32> {
      let dims = textureDimensions(fsqTexture);
      let texCoord = vec2(u32(fragment.position.x), u32(fragment.position.y));
      let color = textureLoad(fsqTexture, texCoord, 0);
      return vec4<f32>(color);
    }
  )";

  const std::string pipelineLabel = "ColorCopy-" + this->GetObjectDescription();
  vtkWebGPURenderPipelineDescriptorInternals pipelineDesc;
  pipelineDesc.label = pipelineLabel.c_str();
  pipelineDesc.layout = pipelineLayout;
  pipelineDesc.vertex.entryPoint = "vertexMain";
  pipelineDesc.vertex.bufferCount = 0;
  pipelineDesc.cFragment.entryPoint = "fragmentMain";
  pipelineDesc.cTargets[0].format = this->GetPreferredSurfaceTextureFormat();
  pipelineDesc.DisableDepthStencil();
  pipelineDesc.primitive.topology = wgpu::PrimitiveTopology::TriangleStrip;

  const auto pipelineKey = this->WGPUPipelineCache->GetPipelineKey(&pipelineDesc, shaderSource);
  if (this->ColorCopyRenderPipeline.Key != pipelineKey)
  {
    this->WGPUPipelineCache->CreateRenderPipeline(&pipelineDesc, this, shaderSource);
    this->ColorCopyRenderPipeline.Key = pipelineKey;
  }
}

//------------------------------------------------------------------------------
void vtkWebGPURenderWindow::RecreateComputeRenderTextures()
{
  vtkWebGPUCheckUnconfigured(this);

  for (const auto& renderTexture : this->ComputeRenderTextures)
  {
    int* dims = this->GetSize();

    // Updating the size of the texture
    renderTexture->SetSize(dims[0], dims[1]);

    // Updating the WebGPU texture used by the render texture since it has been recreated by the
    // window resize
    switch (renderTexture->GetType())
    {
      case vtkWebGPUComputeRenderTexture::RenderTextureType::DEPTH_BUFFER:
        renderTexture->SetWebGPUTexture(this->DepthStencilAttachment.Texture);
        break;

      case vtkWebGPUComputeRenderTexture::RenderTextureType::COLOR_BUFFER:
        renderTexture->SetWebGPUTexture(this->ColorAttachment.Texture);
        break;

      default:
        vtkLog(ERROR,
          "Unhandled ComputeRenderTexture type in "
          "vtkWebGPURenderWindow::RecreateComputeRenderTextures. This is an internal error.");
        break;
    }

    vtkWeakPointer<vtkWebGPUComputePass> associatedComputePass =
      renderTexture->GetAssociatedComputePass();

    if (associatedComputePass == nullptr)
    {
      vtkLog(WARNING,
        "The render texture with label \"" << renderTexture->GetLabel()
                                           << "\" didn't have an associated compute pass. Did "
                                              "you forget to add the render texture "
                                              "to a compute pass?");

      continue;
    }
    associatedComputePass->Internals->RecreateRenderTexture(renderTexture);
  }
}

//------------------------------------------------------------------------------
void vtkWebGPURenderWindow::DestroyColorCopyPipeline()
{
  this->ColorCopyRenderPipeline.BindGroup = nullptr;
  this->WGPUPipelineCache->DestroyRenderPipeline(this->ColorCopyRenderPipeline.Key);
  this->ColorCopyRenderPipeline.Key.clear();
}

//------------------------------------------------------------------------------
void vtkWebGPURenderWindow::PostRenderComputePipelines()
{
  for (auto renderer : vtk::Range(this->Renderers))
  {
    vtkWebGPURenderer* wgpuRenderer = vtkWebGPURenderer::SafeDownCast(renderer);
    if (wgpuRenderer == nullptr)
    {
      // Probably not a wgpuRenderer

      continue;
    }

    wgpuRenderer->PostRenderComputePipelines();
  }
}

//------------------------------------------------------------------------------
void vtkWebGPURenderWindow::PostRasterizationRender()
{
  for (auto renderer : vtk::Range(this->Renderers))
  {
    vtkWebGPURenderer* wgpuRenderer = vtkWebGPURenderer::SafeDownCast(renderer);
    if (wgpuRenderer == nullptr)
    {
      // Probably not a wgpuRenderer

      continue;
    }

    wgpuRenderer->PostRasterizationRender();
  }
}

//------------------------------------------------------------------------------
vtkWebGPURenderWindow::ComponentMapping vtkWebGPURenderWindow::GetComponentMapping(
  wgpu::TextureFormat format, int desiredOutComponents)
{
  switch (format)
  {
    case wgpu::TextureFormat::RGBA8Unorm:
      return { { 0, 1, 2, 3 }, 4, desiredOutComponents, true };
    case wgpu::TextureFormat::RGBA32Uint:
      return { { 0, 1, 2, 3 }, 4, desiredOutComponents, true };
    case wgpu::TextureFormat::BGRA8Unorm:
      return { { 2, 1, 0, 3 }, 4, desiredOutComponents, true };
    default:
      vtkGenericWarningMacro(
        "GetComponentMapping: Unsupported texture format " << static_cast<int>(format) << ".");
      return { { -1, -1, -1, -1 }, 4, desiredOutComponents, false };
  }
}

//------------------------------------------------------------------------------
template <typename TOutput, typename TInput>
TOutput* vtkWebGPURenderWindow::GetTextureDataInternal(wgpu::Texture texture,
  wgpu::TextureFormat format, int x1, int y1, int x2, int y2,
  const ComponentMapping& componentMapping, std::function<TOutput(TInput)> converter /*=nullptr*/)
{
  int xMin = std::min(x1, x2);
  int xMax = std::max(x1, x2);
  int yMin = std::min(y1, y2);
  int yMax = std::max(y1, y2);
  uint32_t width = static_cast<uint32_t>(xMax - xMin + 1);
  uint32_t height = static_cast<uint32_t>(yMax - yMin + 1);

  wgpu::Origin3D origin;
  origin.x = static_cast<std::uint32_t>(xMin);
  origin.y = this->FlipY(static_cast<std::uint32_t>(yMax));

  wgpu::Extent3D extent = { width, height, 1 };

  auto* result = new TOutput[width * height * componentMapping.OutComponents];

  auto* callbackData = new PixelReadbackCallbackData<TOutput, TInput>();
  callbackData->Width = width;
  callbackData->Height = height;
  callbackData->Mapping = componentMapping;
  callbackData->Converter = converter;
  callbackData->OutputValues = result;

  auto onTextureMapped = [](const void* mappedValues, int bytesPerRow, void* userData)
  {
    auto* callbackDataPtr = reinterpret_cast<PixelReadbackCallbackData<TOutput, TInput>*>(userData);
    const auto& formatMapping = callbackDataPtr->Mapping;
    TOutput* dst = callbackDataPtr->OutputValues;
    const TInput* src = reinterpret_cast<const TInput*>(mappedValues);
    const vtkIdType stridePixels = bytesPerRow / (formatMapping.InComponents * sizeof(TInput));
    for (std::uint32_t y = 0; y < callbackDataPtr->Height; ++y)
    {
      const auto yFlipped = callbackDataPtr->Height - y - 1;
      for (std::uint32_t x = 0; x < callbackDataPtr->Width; ++x)
      {
        const vtkIdType pixelId = x + yFlipped * stridePixels;
        for (int c = 0; c < formatMapping.OutComponents; ++c)
        {
          const auto value = src[pixelId * formatMapping.InComponents + formatMapping.Map[c]];
          (*dst++) = callbackDataPtr->Converter(value);
        }
      }
    }
    delete callbackDataPtr;
  };

  this->ReadTextureFromGPU(texture, format, 0, wgpu::TextureAspect::All, origin, extent,
    onTextureMapped, reinterpret_cast<void*>(callbackData));
  this->WaitForCompletion();
  return result;
}

//------------------------------------------------------------------------------
std::uint32_t vtkWebGPURenderWindow::FlipY(std::uint32_t y)
{
  return this->Size[1] - 1 - y;
}

//------------------------------------------------------------------------------
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 vtkWebGPUTextureDeviceResource::GetBytesPerPixel: "
        << int(format));
      return;
  }

  // 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::TexelCopyTextureInfo texelCopyTexture;
  texelCopyTexture.mipLevel = mipLevel;
  texelCopyTexture.origin = offsets;
  texelCopyTexture.texture = wgpuTexture;
  texelCopyTexture.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::TexelCopyBufferInfo texelCopyBuffer;
  texelCopyBuffer.buffer = buffer;
  texelCopyBuffer.layout.offset = 0;
  texelCopyBuffer.layout.rowsPerImage = mipLevelHeight;
  texelCopyBuffer.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(&texelCopyTexture, &texelCopyBuffer, &copySize);

  // Submitting the comand
  wgpu::CommandBuffer commandBuffer = commandEncoder.Finish();
  this->WGPUConfiguration->GetDevice().GetQueue().Submit(1, &commandBuffer);

  auto bufferMapCallback =
    [](wgpu::MapAsyncStatus status, wgpu::StringView message, InternalMapTextureAsyncData* mapData)
  {
#ifdef Success
    // Avoiding macro collision on Xlib systems where Success is defined as 0 with the definition
    // wgpu::MapAsyncStatus::Success in dawn's webgpu_cpp.h.
    // See X11/Xlib.h: #define Success 0
    // We undefine preprocessor macro Success here and redefine it as a `constexpr auto` variable.
    constexpr auto Success_ = Success;
#undef Success
    [[maybe_unused]] constexpr auto Success = Success_;
#endif
    if (status == wgpu::MapAsyncStatus::Success)
    {
      const void* mappedRange = mapData->buffer.GetConstMappedRange(0, mapData->byteSize);
      mapData->userCallback(mappedRange, mapData->bytesPerRow, mapData->userData);
      mapData->buffer.Unmap();
    }
    else
    {
      vtkLog(WARNING, << "Failed to map [Texture \'"
                      << (mapData->bufferLabel.empty() ? "(nolabel)" : mapData->bufferLabel)
                      << "\'] with error=" << static_cast<std::uint32_t>(status) << ". "
                      << vtkWebGPUHelpers::StringViewToStdString(message));
    }
#if defined(__EMSCRIPTEN__)
    wgpuBufferRelease(mapData->buffer.Get());
#endif
    // 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 = this->GetObjectDescription() + " ReadTextureFromGPU map buffer";
  callbackData->byteSize = bufferDescriptor.size;
  callbackData->bytesPerRow = bytesPerRow;
  callbackData->userCallback = callback;
  callbackData->userData = userData;
#if defined(__EMSCRIPTEN__)
  // keep buffer alive for map.
  // See https://issues.chromium.org/issues/399131918
  wgpuBufferAddRef(callbackData->buffer.Get());
#endif
  callbackData->buffer.MapAsync(wgpu::MapMode::Read, 0, bufferDescriptor.size,
    wgpu::CallbackMode::AllowProcessEvents, bufferMapCallback, callbackData);
}

//------------------------------------------------------------------------------
void vtkWebGPURenderWindow::ReadTextureFromGPU(wgpu::Texture& wgpuTexture,
  wgpu::TextureFormat format, std::size_t mipLevel, wgpu::TextureAspect aspect,
  vtkWebGPURenderWindow::TextureMapCallback callback, void* userData)
{
  this->ReadTextureFromGPU(wgpuTexture, format, mipLevel, aspect, wgpu::Origin3D{ 0, 0, 0 },
    wgpu::Extent3D{
      wgpuTexture.GetWidth(), wgpuTexture.GetHeight(), wgpuTexture.GetDepthOrArrayLayers() },
    callback, userData);
}

//------------------------------------------------------------------------------
void vtkWebGPURenderWindow::RenderOffscreenTexture()
{
  vtkWebGPUCheckUnconfigured(this);
  if (this->Surface == nullptr)
  {
    vtkErrorMacro(<< "Cannot render offscreen texture because surface is null!");
    return;
  }
  wgpu::SurfaceTexture surfaceTexture;
  this->Surface.GetCurrentTexture(&surfaceTexture);

  // Early exit if surface did not give a texture
  if (surfaceTexture.texture == nullptr)
  {
    switch (surfaceTexture.status)
    {
      case wgpu::SurfaceGetCurrentTextureStatus::Timeout:
      case wgpu::SurfaceGetCurrentTextureStatus::Lost:
      case wgpu::SurfaceGetCurrentTextureStatus::Error:
        vtkErrorMacro(<< "Cannot render offscreen texture because SurfaceGetCurrentTextureStatus="
                      << static_cast<int>(surfaceTexture.status));
        return;
      case wgpu::SurfaceGetCurrentTextureStatus::Outdated:
        // Surface configuration is outdated, likely due to window resize
        vtkDebugMacro(<< "Surface texture is outdated, triggering reconfiguration");
        // Trigger a reconfiguration and re-render
        this->Modified();
        return;
      case wgpu::SurfaceGetCurrentTextureStatus::SuccessSuboptimal:
      {
        static bool warnOnce = false;
        if (!warnOnce)
        {
          vtkWarningMacro(<< "Surface texture format is suboptimal!");
          warnOnce = true;
        }
        break;
      }
      default:
        break;
    }
  }
  else
  {
    // Validate surface texture dimensions match expected size
    auto texWidth = surfaceTexture.texture.GetWidth();
    auto texHeight = surfaceTexture.texture.GetHeight();
    if (texWidth != static_cast<uint32_t>(this->SurfaceConfiguredSize[0]) ||
      texHeight != static_cast<uint32_t>(this->SurfaceConfiguredSize[1]))
    {
      vtkDebugMacro(<< "Surface texture size (" << texWidth << "x" << texHeight
                    << ") does not match configured size (" << this->SurfaceConfiguredSize[0] << "x"
                    << this->SurfaceConfiguredSize[1] << "). Surface needs reconfiguration.");
      // Surface size mismatch detected - this should trigger reconfiguration on next Start()
      // Skip this frame
      return;
    }
  }

  if (this->ColorAttachment.Texture == nullptr || this->ColorCopyRenderPipeline.Key.empty() ||
    this->ColorCopyRenderPipeline.BindGroup == nullptr || this->CommandEncoder == nullptr)
  {
    vtkErrorMacro(<< "Cannot render offscreen texture: missing required resources");
    return;
  }

  wgpu::TextureView surfaceView = surfaceTexture.texture.CreateView();
  vtkWebGPURenderPassDescriptorInternals renderPassDescriptor({ surfaceView });
  renderPassDescriptor.label = "Render offscreen texture";
  renderPassDescriptor.ColorAttachments[0].clearValue = { 0.0f, 0.0f, 0.0f, 1.0f };

  if (auto encoder = this->NewRenderPass(renderPassDescriptor))
  {
    encoder.SetViewport(
      0, 0, this->SurfaceConfiguredSize[0], this->SurfaceConfiguredSize[1], 0.0f, 1.0f);
    encoder.SetScissorRect(0, 0, this->SurfaceConfiguredSize[0], this->SurfaceConfiguredSize[1]);

    const auto pipeline =
      this->WGPUPipelineCache->GetRenderPipeline(this->ColorCopyRenderPipeline.Key);
    encoder.SetPipeline(pipeline);
    encoder.SetBindGroup(0, this->ColorCopyRenderPipeline.BindGroup);
    encoder.Draw(4);
    encoder.End();
  }
  else
  {
    vtkErrorMacro(<< "Failed to create render pass for offscreen texture");
    return;
  }
}

//------------------------------------------------------------------------------
void vtkWebGPURenderWindow::FlushCommandBuffers(vtkTypeUInt32 count, wgpu::CommandBuffer* buffers)
{
  vtkDebugMacro(<< __func__ << "count=" << count);
  vtkWebGPUCheckUnconfigured(this);
  auto device = this->WGPUConfiguration->GetDevice();
  if (device == nullptr)
  {
    vtkErrorMacro(<< "Cannot flush command buffers because WebGPU device is not ready!");
    return;
  }
  if (count > 0 && buffers == nullptr)
  {
    vtkErrorMacro(<< "Cannot flush command buffers because buffers is null even though count ("
                  << count << ") > 0");
    return;
  }
  if (auto queue = device.GetQueue())
  {
    queue.Submit(count, buffers);
  }
  else
  {
    vtkErrorMacro(<< "Cannot flush command buffers because this render window failed to obtain a "
                     "queue from device "
                  << device.Get());
    return;
  }
}

//------------------------------------------------------------------------------
void vtkWebGPURenderWindow::Start()
{
  vtkDebugMacro(<< __func__ << '(' << this->Size[0] << ',' << this->Size[1] << ')');

  this->Size[0] = (this->Size[0] > 0 ? this->Size[0] : 300);
  this->Size[1] = (this->Size[1] > 0 ? this->Size[1] : 300);

  vtkDebugMacro(<< __func__ << " Initialized=" << this->Initialized);

  if (!this->Initialized)
  {
    this->Initialize();
  }

  if (this->Size[0] != this->SurfaceConfiguredSize[0] ||
    this->Size[1] != this->SurfaceConfiguredSize[1])
  {
    // Window's size changed, need to recreate the swap chain, textures, ...
    this->DestroyColorCopyPipeline();
    this->DestroyDepthStencilAttachment();
    this->DestroyIdsAttachment();
    this->DestroyOffscreenColorAttachment();
    this->UnconfigureSurface();
    this->ConfigureSurface();
    this->CreateOffscreenColorAttachment();
    this->CreateDepthStencilAttachment();
    this->CreateIdsAttachment();
    this->CreateColorCopyPipeline();
    this->RecreateComputeRenderTextures();
  }

  this->CreateCommandEncoder();
}

//------------------------------------------------------------------------------
void vtkWebGPURenderWindow::Frame()
{
  vtkDebugMacro(<< __func__);
  vtkWebGPUCheckUnconfigured(this);
  if (this->Surface == nullptr)
  {
    vtkErrorMacro(<< "Cannot render frame because the surface is null!");
    return;
  }
  this->Superclass::Frame();

  wgpu::CommandBufferDescriptor 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->PostRenderComputePipelines();
  this->PostRasterizationRender();

  // New command encoder for the FSQ pass
  this->CreateCommandEncoder();
  this->RenderOffscreenTexture();

  // Flushing the FSQ render pass
  cmdBuffer = this->CommandEncoder.Finish(&cmdBufDesc);

  this->CommandEncoder = nullptr;
  this->FlushCommandBuffers(1, &cmdBuffer);

  // On web, html5 `requestAnimateFrame` takes care of presentation.
#ifndef __EMSCRIPTEN__
  this->Surface.Present();
#endif

  // Clean up staging buffer for SetPixelData.
  if (this->StagingPixelData.Buffer.Get() != nullptr)
  {
    this->StagingPixelData.Buffer.Destroy();
    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.
  this->WGPUConfiguration->ProcessEvents();
#endif
}

//------------------------------------------------------------------------------
void vtkWebGPURenderWindow::End()
{
  vtkDebugMacro(<< __func__);
  vtkWebGPUCheckUnconfigured(this);

  // If user called SetPixelData or it's variant, source our offscreen texture from that data.
  if (this->StagingPixelData.Buffer.Get() != nullptr)
  {
    if (this->CommandEncoder == nullptr)
    {
      vtkErrorMacro(<< "Cannot copy staging RGBA pixel buffer provided by SetPixelData into "
                       "texture because the command encoder is null!");
      return;
    }
    // copy data to texture.
    wgpu::TexelCopyTextureInfo destination;
    destination.texture = this->ColorAttachment.Texture;
    destination.mipLevel = 0;
    destination.origin = this->StagingPixelData.Origin;
    destination.aspect = wgpu::TextureAspect::All;

    wgpu::TexelCopyBufferInfo source;
    source.buffer = this->StagingPixelData.Buffer;
    source.layout = this->StagingPixelData.Layout;
    this->Start();
    vtkScopedEncoderDebugGroup(this->CommandEncoder, "Copy staging RGBA pixel buffer to texture");
    this->CommandEncoder.CopyBufferToTexture(
      &source, &destination, &(this->StagingPixelData.Extent));
  }
}

//------------------------------------------------------------------------------
void vtkWebGPURenderWindow::StereoMidpoint() {}

//------------------------------------------------------------------------------
const char* vtkWebGPURenderWindow::GetRenderingBackend()
{
  return "";
}

//------------------------------------------------------------------------------
void* vtkWebGPURenderWindow::GetGenericContext()
{
  return this->WGPUConfiguration->GetDevice().Get();
}

//------------------------------------------------------------------------------
unsigned char* vtkWebGPURenderWindow::GetPixelData(
  int x1, int y1, int x2, int y2, int front, int right)
{
  (void)front;
  (void)right;
  const auto componentMapping = this->GetComponentMapping(this->ColorAttachment.Format, 3);
  using TInput = unsigned char;
  using TOutput = unsigned char;
  auto converter = [](TInput val) -> TOutput { return val; };
  return this->GetTextureDataInternal<TOutput, TInput>(this->ColorAttachment.Texture,
    this->ColorAttachment.Format, x1, y1, x2, y2, componentMapping, converter);
}

//------------------------------------------------------------------------------
int vtkWebGPURenderWindow::GetPixelData(
  int x1, int y1, int x2, int y2, int front, vtkUnsignedCharArray* data, int right)
{
  int xMin = std::min(x1, x2);
  int xMax = std::max(x1, x2);
  int yMin = std::min(y1, y2);
  int yMax = std::max(y1, y2);
  int width = xMax - xMin + 1;
  int height = yMax - yMin + 1;

  const int numberOfComponents = 3;
  data->SetNumberOfComponents(numberOfComponents);
  data->SetNumberOfTuples(width * height);
  unsigned char* pixels = this->GetPixelData(xMin, yMin, xMax, yMax, front, right);
  // take ownership of pixels
  data->SetArray(pixels, width * height * numberOfComponents, 0);
  return data->GetNumberOfValues();
}

//------------------------------------------------------------------------------
int vtkWebGPURenderWindow::SetPixelData(
  int x, int y, int x2, int y2, unsigned char* data, int front, int right)
{
  vtkWebGPUCheckUnconfiguredWithReturn(this, 0);
  auto device = this->WGPUConfiguration->GetDevice();
  if (device == nullptr)
  {
    vtkErrorMacro(<< "Cannot set pixel data because WebGPU device is not ready!");
    return 0;
  }
  (void)front;
  (void)right;
  const int nComp = 3;
  int yOffset = this->Size[1] - y2 - 1;
  int width = (x2 - x) + 1;
  int height = (y2 - y) + 1;
  int bytesPerRow = vtkWebGPUConfiguration::Align(width * nComp, 256);
  int size = bytesPerRow * height;

  const std::string label = "StagingRGBPixelData-" + this->GetObjectDescription();
  wgpu::BufferDescriptor desc;
  desc.mappedAtCreation = true;
  desc.label = label.c_str();
  desc.size = size;
  desc.usage = wgpu::BufferUsage::CopySrc;

  this->StagingPixelData.Buffer = this->WGPUConfiguration->CreateBuffer(desc);
  if (this->StagingPixelData.Buffer == nullptr)
  {
    vtkErrorMacro(<< "Failed to create buffer for staging pixel data using device "
                  << device.Get());
    return 0;
  }
  auto mapped =
    reinterpret_cast<unsigned char*>(this->StagingPixelData.Buffer.GetMappedRange(0, size));
  if (mapped == nullptr)
  {
    vtkErrorMacro(<< "Failed to map staging pixel data!");
    return 0;
  }
  unsigned long dstIdx = 0;
  const unsigned long nPad = bytesPerRow - width * nComp;
  int componentMap[3] = {};
  if (this->ColorAttachment.Format == wgpu::TextureFormat::BGRA8Unorm)
  {
    componentMap[0] = 2;
    componentMap[1] = 1;
    componentMap[2] = 0;
  }
  else if (this->ColorAttachment.Format == wgpu::TextureFormat::RGBA8Unorm)
  {
    componentMap[0] = 0;
    componentMap[1] = 1;
    componentMap[2] = 2;
  }
  for (int j = height - 1; j > -1; --j)
  {
    for (int i = 0; i < width; ++i)
    {
      int srcIdx = i + j * bytesPerRow;
      for (const auto& comp : componentMap)
      {
        mapped[dstIdx + comp] = data[srcIdx++];
      }
      mapped[dstIdx + nComp] = 255;
      dstIdx += nComp;
    }
    dstIdx += nPad;
  }

  this->StagingPixelData.Layout.bytesPerRow = bytesPerRow;
  this->StagingPixelData.Layout.offset = 0;
  this->StagingPixelData.Layout.rowsPerImage = height;

  this->StagingPixelData.Extent.width = width;
  this->StagingPixelData.Extent.height = height;
  this->StagingPixelData.Extent.depthOrArrayLayers = 1;

  this->StagingPixelData.Origin.x = x;
  this->StagingPixelData.Origin.y = yOffset;
  this->StagingPixelData.Origin.z = 0;

  return size;
}

//------------------------------------------------------------------------------
int vtkWebGPURenderWindow::SetPixelData(
  int x, int y, int x2, int y2, vtkUnsignedCharArray* data, int front, int right)
{
  return this->SetPixelData(x, y, x2, y2, data->GetPointer(0), front, right);
}

//------------------------------------------------------------------------------
float* vtkWebGPURenderWindow::GetRGBAPixelData(
  int x1, int y1, int x2, int y2, int front, int right /*=0*/)
{
  (void)front;
  (void)right;
  const auto componentMapping = this->GetComponentMapping(this->ColorAttachment.Format, 4);
  using TInput = unsigned char;
  using TOutput = float;
  auto converter = [](TInput val) -> TOutput { return val / 255.0f; };
  return this->GetTextureDataInternal<TOutput, TInput>(this->ColorAttachment.Texture,
    this->ColorAttachment.Format, x1, y1, x2, y2, componentMapping, converter);
}

//------------------------------------------------------------------------------
int vtkWebGPURenderWindow::GetRGBAPixelData(
  int x1, int y1, int x2, int y2, int front, vtkFloatArray* data, int right /*=0*/)
{
  int xMin = std::min(x1, x2);
  int xMax = std::max(x1, x2);
  int yMin = std::min(y1, y2);
  int yMax = std::max(y1, y2);
  int width = xMax - xMin + 1;
  int height = yMax - yMin + 1;
  const int numberOfComponents = 4;
  data->SetNumberOfComponents(numberOfComponents);
  data->SetNumberOfTuples(width * height);
  float* pixels = this->GetRGBAPixelData(xMin, yMin, xMax, yMax, front, right);
  // take ownership of pixels
  data->SetArray(pixels, width * height * numberOfComponents, 0);
  return data->GetNumberOfValues();
}

//------------------------------------------------------------------------------
int vtkWebGPURenderWindow::SetRGBAPixelData(
  int x, int y, int x2, int y2, float* data, int front, int blend /*=0*/, int right /*=0*/)
{
  vtkWebGPUCheckUnconfiguredWithReturn(this, 0);
  auto device = this->WGPUConfiguration->GetDevice();
  if (device == nullptr)
  {
    vtkErrorMacro(<< "Cannot set RGBA pixel data because WebGPU device is not ready!");
    return 0;
  }
  (void)front;
  (void)blend;
  (void)right;
  const int nComp = 4;
  int yOffset = this->Size[1] - y2 - 1;
  int width = (x2 - x) + 1;
  int height = (y2 - y) + 1;
  int bytesPerRow = vtkWebGPUConfiguration::Align(width * nComp, 256);
  int size = bytesPerRow * height;

  const std::string label = "StagingRGBAPixelData-" + this->GetObjectDescription();
  wgpu::BufferDescriptor desc;
  desc.mappedAtCreation = true;
  desc.label = label.c_str();
  desc.size = size;
  desc.usage = wgpu::BufferUsage::CopySrc;

  this->StagingPixelData.Buffer = this->WGPUConfiguration->CreateBuffer(desc);
  if (this->StagingPixelData.Buffer == nullptr)
  {
    vtkErrorMacro(<< "Failed to create buffer for staging pixel data using device "
                  << device.Get());
    return 0;
  }
  auto mapped =
    reinterpret_cast<unsigned char*>(this->StagingPixelData.Buffer.GetMappedRange(0, size));
  if (mapped == nullptr)
  {
    vtkErrorMacro(<< "Failed to map staging pixel data!");
    return 0;
  }
  unsigned long dstIdx = 0;
  const unsigned long nPad = bytesPerRow - width * nComp;
  int componentMap[4] = {};
  if (this->ColorAttachment.Format == wgpu::TextureFormat::BGRA8Unorm)
  {
    componentMap[0] = 2;
    componentMap[1] = 1;
    componentMap[2] = 0;
    componentMap[3] = 3;
  }
  else if (this->ColorAttachment.Format == wgpu::TextureFormat::RGBA8Unorm)
  {
    componentMap[0] = 0;
    componentMap[1] = 1;
    componentMap[2] = 2;
    componentMap[3] = 3;
  }
  for (int j = height - 1; j > -1; --j)
  {
    for (int i = 0; i < width; ++i)
    {
      int srcIdx = i + j * bytesPerRow;
      for (const auto& comp : componentMap)
      {
        mapped[dstIdx + comp] = static_cast<unsigned char>(data[srcIdx++] * 255.0);
      }
      dstIdx += nComp;
    }
    dstIdx += nPad;
  }
  this->StagingPixelData.Buffer.Unmap();

  this->StagingPixelData.Layout.bytesPerRow = bytesPerRow;
  this->StagingPixelData.Layout.offset = 0;
  this->StagingPixelData.Layout.rowsPerImage = height;

  this->StagingPixelData.Extent.width = width;
  this->StagingPixelData.Extent.height = height;
  this->StagingPixelData.Extent.depthOrArrayLayers = 1;

  this->StagingPixelData.Origin.x = x;
  this->StagingPixelData.Origin.y = yOffset;
  this->StagingPixelData.Origin.z = 0;

  this->Start();
  this->End();
  this->Frame();
  return size;
}

//------------------------------------------------------------------------------
int vtkWebGPURenderWindow::SetRGBAPixelData(
  int x, int y, int x2, int y2, vtkFloatArray* data, int front, int blend /*=0*/, int right /*=0*/)
{
  return this->SetRGBAPixelData(x, y, x2, y2, data->GetPointer(0), front, blend, right);
}

//------------------------------------------------------------------------------
void vtkWebGPURenderWindow::ReleaseRGBAPixelData(float* data)
{
  delete[] data;
}

unsigned char* vtkWebGPURenderWindow::GetRGBACharPixelData(
  int x1, int y1, int x2, int y2, int front, int right /*=0*/)
{
  (void)front;
  (void)right;
  const auto componentMapping = this->GetComponentMapping(this->ColorAttachment.Format, 4);
  using TInput = unsigned char;
  using TOutput = unsigned char;
  auto converter = [](TInput val) -> TOutput { return val; };
  return this->GetTextureDataInternal<TOutput, TInput>(this->ColorAttachment.Texture,
    this->ColorAttachment.Format, x1, y1, x2, y2, componentMapping, converter);
}

//------------------------------------------------------------------------------
int vtkWebGPURenderWindow::GetRGBACharPixelData(
  int x1, int y1, int x2, int y2, int front, vtkUnsignedCharArray* data, int right /*=0*/)
{
  int xMin = std::min(x1, x2);
  int xMax = std::max(x1, x2);
  int yMin = std::min(y1, y2);
  int yMax = std::max(y1, y2);
  int width = xMax - xMin + 1;
  int height = yMax - yMin + 1;
  const int numberOfComponents = 4;
  data->SetNumberOfComponents(numberOfComponents);
  data->SetNumberOfTuples(width * height);
  unsigned char* pixels = this->GetRGBACharPixelData(xMin, yMin, xMax, yMax, front, right);
  // take ownership of pixels
  data->SetArray(pixels, width * height * numberOfComponents, 0);
  return data->GetNumberOfValues();
}

//------------------------------------------------------------------------------
int vtkWebGPURenderWindow::SetRGBACharPixelData(
  int x, int y, int x2, int y2, unsigned char* data, int front, int blend /*=0*/, int right /*=0*/)
{
  vtkWebGPUCheckUnconfiguredWithReturn(this, 0);
  auto device = this->WGPUConfiguration->GetDevice();
  if (device == nullptr)
  {
    vtkErrorMacro(<< "Cannot set RGBA char pixel data because WebGPU device is not ready!");
    return 0;
  }
  (void)front;
  (void)blend;
  (void)right;
  const int nComp = 4;
  int yOffset = this->Size[1] - y2 - 1;
  int width = (x2 - x) + 1;
  int height = (y2 - y) + 1;
  int bytesPerRow = vtkWebGPUConfiguration::Align(width * nComp, 256);
  int size = bytesPerRow * height;

  const std::string label = "StagingRGBACharPixelData-" + this->GetObjectDescription();
  wgpu::BufferDescriptor desc;
  desc.mappedAtCreation = true;
  desc.label = label.c_str();
  desc.size = size;
  desc.usage = wgpu::BufferUsage::CopySrc;

  this->StagingPixelData.Buffer = this->WGPUConfiguration->CreateBuffer(desc);
  if (this->StagingPixelData.Buffer == nullptr)
  {
    vtkErrorMacro(<< "Failed to create buffer for staging pixel data using device "
                  << device.Get());
    return 0;
  }
  auto* mapped =
    reinterpret_cast<unsigned char*>(this->StagingPixelData.Buffer.GetMappedRange(0, size));
  if (mapped == nullptr)
  {
    vtkErrorMacro(<< "Failed to map staging pixel data!");
    return 0;
  }
  unsigned long dstIdx = 0;
  const unsigned long nPad = bytesPerRow - width * nComp;
  int componentMap[4] = {};
  if (this->ColorAttachment.Format == wgpu::TextureFormat::BGRA8Unorm)
  {
    componentMap[0] = 2;
    componentMap[1] = 1;
    componentMap[2] = 0;
    componentMap[3] = 3;
  }
  else if (this->ColorAttachment.Format == wgpu::TextureFormat::RGBA8Unorm)
  {
    componentMap[0] = 0;
    componentMap[1] = 1;
    componentMap[2] = 2;
    componentMap[3] = 3;
  }
  else
  {
    // TODO: Handle other formats.
    vtkErrorMacro(<< "Unsupported offscreen texture format!");
  }
  for (int j = height - 1; j > -1; --j)
  {
    for (int i = 0; i < width; ++i)
    {
      int srcIdx = (i + j * width) * nComp;
      for (const auto& comp : componentMap)
      {
        mapped[dstIdx + comp] = data[srcIdx++];
      }
      dstIdx += nComp;
    }
    dstIdx += nPad;
  }
  this->StagingPixelData.Buffer.Unmap();

  this->StagingPixelData.Layout.bytesPerRow = bytesPerRow;
  this->StagingPixelData.Layout.offset = 0;
  this->StagingPixelData.Layout.rowsPerImage = height;

  this->StagingPixelData.Extent.width = width;
  this->StagingPixelData.Extent.height = height;
  this->StagingPixelData.Extent.depthOrArrayLayers = 1;

  this->StagingPixelData.Origin.x = x;
  this->StagingPixelData.Origin.y = yOffset;
  this->StagingPixelData.Origin.z = 0;

  this->Start();
  this->End();
  this->Frame();
  return size;
}

//------------------------------------------------------------------------------
int vtkWebGPURenderWindow::SetRGBACharPixelData(int x, int y, int x2, int y2,
  vtkUnsignedCharArray* data, int front, int blend /*=0*/, int right /*=0*/)
{
  return this->SetRGBACharPixelData(x, y, x2, y2, data->GetPointer(0), front, blend, right);
}

//------------------------------------------------------------------------------
float* vtkWebGPURenderWindow::GetZbufferData(int x1, int y1, int x2, int y2)
{
  int xMin = std::min(x1, x2);
  int xMax = std::max(x1, x2);
  int yMin = std::min(y1, y2);
  int yMax = std::max(y1, y2);
  int width = xMax - xMin + 1;
  int height = yMax - yMin + 1;
  float* zValues = new float[width * height];
  this->GetZbufferData(xMin, yMin, xMax, yMax, zValues);
  return zValues;
}

//------------------------------------------------------------------------------
int vtkWebGPURenderWindow::GetZbufferData(int x1, int y1, int x2, int y2, float* zValues)
{
  // Create a compute pipeline
  if (this->DepthCopyPipeline == nullptr)
  {
    this->DepthCopyPipeline = vtk::TakeSmartPointer(vtkWebGPUComputePipeline::New());
    this->DepthCopyPipeline->SetLabel("DepthCopy-" + 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-" + this->GetObjectDescription());
    vtkSmartPointer<vtkWebGPUComputeRenderTexture> depthTexture;
    depthTexture = this->AcquireDepthBufferRenderTexture();
    textureWidth = depthTexture->GetWidth();

    depthTexture->SetLabel("DepthCopy-" + 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-" + this->GetObjectDescription());
    depthTextureView->SetMode(vtkWebGPUTextureView::TextureViewMode::READ_ONLY);
    depthTextureView->SetAspect(vtkWebGPUTextureView::TextureViewAspect::ASPECT_DEPTH);
    depthTextureView->SetFormat(vtkWebGPUTextureDeviceResource::TextureFormat::DEPTH_24_PLUS);
    this->DepthCopyPass->AddTextureView(depthTextureView);

    vtkNew<vtkWebGPUComputeBuffer> buffer;
    buffer->SetGroup(0);
    buffer->SetBinding(1);
    buffer->SetLabel("DepthCopy-" + 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* callbackDataPtr = reinterpret_cast<CallbackData*>(userData);
    float* outputValues = callbackDataPtr->outputValues;
    const float* mappedDataAsF32 = reinterpret_cast<const float*>(mappedData);
    vtkIdType dstIdx = 0;
    for (int y = callbackDataPtr->yMax; y >= callbackDataPtr->yMin; y--)
    {
      for (int x = callbackDataPtr->xMin; x <= callbackDataPtr->xMax; x++)
      {
        const int mappedIndex = x + y * callbackDataPtr->width;
        outputValues[dstIdx++] = mappedDataAsF32[mappedIndex];
      }
    }
  };
  int xMin = std::min(x1, x2);
  int xMax = std::max(x1, x2);
  int yMin = std::min(y1, y2);
  int yMax = std::max(y1, y2);
  CallbackData callbackData;
  callbackData.xMin = xMin;
  callbackData.xMax = xMax;
  callbackData.yMin = this->FlipY(yMax);
  callbackData.yMax = this->FlipY(yMin);
  callbackData.outputValues = zValues;
  callbackData.width = textureWidth;
  this->DepthCopyPass->ReadBufferFromGPU(this->DepthCopyBufferIndex, onBufferMapped, &callbackData);
  this->DepthCopyPipeline->Update();
  return VTK_OK;
}

//------------------------------------------------------------------------------
int vtkWebGPURenderWindow::GetZbufferData(int x1, int y1, int x2, int y2, vtkFloatArray* buffer)
{
  int xMin = std::min(x1, x2);
  int xMax = std::max(x1, x2);
  int yMin = std::min(y1, y2);
  int yMax = std::max(y1, y2);
  int width = xMax - xMin + 1;
  int height = yMax - yMin + 1;
  buffer->SetNumberOfComponents(1);
  buffer->SetNumberOfTuples(width * height);
  return this->GetZbufferData(xMin, yMin, xMax, yMax, buffer->GetPointer(0));
}

//------------------------------------------------------------------------------
int vtkWebGPURenderWindow::SetZbufferData(int, int, int, int, float*)
{
  return 0;
}

//------------------------------------------------------------------------------
int vtkWebGPURenderWindow::SetZbufferData(int, int, int, int, vtkFloatArray*)
{
  return 0;
}

//------------------------------------------------------------------------------
vtkTypeUInt32* vtkWebGPURenderWindow::GetIdsData(int x1, int y1, int x2, int y2)
{
  int xMin = std::min(x1, x2);
  int xMax = std::max(x1, x2);
  int yMin = std::min(y1, y2);
  int yMax = std::max(y1, y2);
  const auto componentMapping = this->GetComponentMapping(this->IdsAttachment.Format, 4);
  using TInput = uint32_t;
  using TOutput = vtkTypeUInt32;
  auto converter = [](TInput val) -> TOutput { return static_cast<TOutput>(val); };
  return this->GetTextureDataInternal<TOutput, TInput>(this->IdsAttachment.Texture,
    this->IdsAttachment.Format, xMin, yMin, xMax, yMax, componentMapping, converter);
}

//------------------------------------------------------------------------------
void vtkWebGPURenderWindow::GetIdsData(int x1, int y1, int x2, int y2, vtkTypeUInt32Array* data)
{
  int xMin = std::min(x1, x2);
  int xMax = std::max(x1, x2);
  int yMin = std::min(y1, y2);
  int yMax = std::max(y1, y2);
  int width = xMax - xMin + 1;
  int height = yMax - yMin + 1;
  const int outNumberOfComponents = 4;
  data->SetNumberOfComponents(outNumberOfComponents);
  data->SetComponentName(0, "CellId");
  data->SetComponentName(1, "PropId");
  data->SetComponentName(2, "CompositeId");
  data->SetComponentName(3, "ProcessId");
  auto* ids = this->GetIdsData(xMin, yMin, xMax, yMax);
  // take ownership of ids
  data->SetArray(ids, width * height * outNumberOfComponents, /*save=*/0);
}

//------------------------------------------------------------------------------
int vtkWebGPURenderWindow::GetColorBufferSizes(int* rgba)
{
  rgba[0] = 8;
  rgba[1] = 8;
  rgba[2] = 8;
  rgba[3] = 8;
  return 32;
}

//------------------------------------------------------------------------------
void vtkWebGPURenderWindow::WaitForCompletion()
{
  vtkWebGPUCheckUnconfigured(this);
  auto device = this->WGPUConfiguration->GetDevice();
  if (device == nullptr)
  {
    vtkErrorMacro(<< "Cannot wait for completion because WebGPU device is not ready!");
    return;
  }
  if (auto queue = device.GetQueue())
  {
    wgpu::QueueWorkDoneStatus workStatus = wgpu::QueueWorkDoneStatus::Error;
    bool done = false;
    this->WGPUConfiguration->GetDevice().GetQueue().OnSubmittedWorkDone(
      wgpu::CallbackMode::AllowProcessEvents,
      [&workStatus, &done](wgpu::QueueWorkDoneStatus status, wgpu::StringView)
      {
        workStatus = status;
        done = true;
      });
    while (!done)
    {
      this->WGPUConfiguration->ProcessEvents();
    }
    if (workStatus != wgpu::QueueWorkDoneStatus::Success)
    {
      vtkErrorMacro(<< "Submitted work did not complete!");
    }
  }
  else
  {
    vtkErrorMacro(<< "Cannot wait for completion because this render window failed to obtain a "
                     "queue from device "
                  << device.Get());
    return;
  }
}

//------------------------------------------------------------------------------
int vtkWebGPURenderWindow::SupportsOpenGL()
{
  return 0;
}

//------------------------------------------------------------------------------
const char* vtkWebGPURenderWindow::ReportCapabilities()
{
  static std::string caps = "unknown";
  caps = this->WGPUConfiguration->ReportCapabilities();
  return caps.c_str();
}

//------------------------------------------------------------------------------
bool vtkWebGPURenderWindow::InitializeFromCurrentContext()
{
  // TODO: Integrate with dawn::native for d3d12, vulkan and metal swapchain bindings.
  return false;
}

//------------------------------------------------------------------------------
void vtkWebGPURenderWindow::ReleaseGraphicsResources(vtkWindow* w)
{
  if (w != this || !this->Renderers)
  {
    return;
  }
  for (auto renderer : vtk::Range(this->Renderers))
  {
    renderer->ReleaseGraphicsResources(this);
  }

  this->DepthCopyPass = nullptr;
  this->DepthCopyPipeline = nullptr;

  this->WGPUPipelineCache->ReleaseGraphicsResources(w);
  this->WGPUTextureCache->ReleaseGraphicsResources(w);
  this->DestroyColorCopyPipeline();
  this->DestroyIdsAttachment();
  this->DestroyDepthStencilAttachment();
  this->DestroyOffscreenColorAttachment();
  this->UnconfigureSurface();
  this->Surface = nullptr;
}

//------------------------------------------------------------------------------
void vtkWebGPURenderWindow::SetWGPUConfiguration(vtkWebGPUConfiguration* config)
{
  // Release all wgpu objects from the current device.
  const bool reInitialize = this->Initialized;
  if (this->Initialized)
  {
    this->WGPUFinalize();
  }
  vtkSetSmartPointerBodyMacro(WGPUConfiguration, vtkWebGPUConfiguration, config);
  if (reInitialize)
  {
    this->Initialize();
  }
}

//------------------------------------------------------------------------------
std::string vtkWebGPURenderWindow::PreprocessShaderSource(const std::string& source) const
{
  std::istringstream is(source);
  std::ostringstream os;
  std::string line;

  while (std::getline(is, line))
  {
    if (line.find("#include") != std::string::npos)
    {
      auto pieces = vtksys::SystemTools::SplitString(line, ' ');
      if (pieces.size() == 3)
      {
        const auto key = pieces[2].substr(1, pieces[2].size() - 2);
        const auto replacement = this->WGPUShaderDatabase->GetShaderSource(key);
        if (!replacement.empty())
        {
          os << "// Start " << key << '\n';
          os << this->PreprocessShaderSource(replacement) << '\n';
          os << "// End " << key << '\n';
        }
        else
        {
          vtkErrorMacro(<< "Failed to substitute " << line);
        }
      }
    }
    else
    {
      os << line << '\n';
    }
  }
  return os.str();
}

//------------------------------------------------------------------------------
vtkSmartPointer<vtkImageData> vtkWebGPURenderWindow::SaveAttachmentToVTI(
  AttachmentTypeForVTISnapshot type)
{
  auto image = vtk::TakeSmartPointer(vtkImageData::New());
  vtkNew<vtkFloatArray> colorF32;
  vtkNew<vtkUnsignedCharArray> colorU8;
  vtkNew<vtkTypeUInt32Array> colorU32;
  std::array<int, 3> dims = { 0, 0, 1 };

  switch (type)
  {
    case AttachmentTypeForVTISnapshot::ColorRGBA:
      dims[0] = this->ColorAttachment.Texture.GetWidth();
      dims[1] = this->ColorAttachment.Texture.GetHeight();
      image->SetDimensions(dims.data());
      this->GetRGBAPixelData(0, 0, dims[0] - 1, dims[1] - 1, 0, colorF32, 0);
      image->GetPointData()->SetScalars(colorF32);
      break;
    case AttachmentTypeForVTISnapshot::ColorRGB:
      dims[0] = this->ColorAttachment.Texture.GetWidth();
      dims[1] = this->ColorAttachment.Texture.GetHeight();
      image->SetDimensions(dims.data());
      this->GetPixelData(0, 0, dims[0] - 1, dims[1] - 1, 0, colorU8, 0);
      image->GetPointData()->SetScalars(colorU8);
      break;
    case AttachmentTypeForVTISnapshot::Depth:
      dims[0] = this->DepthStencilAttachment.Texture.GetWidth();
      dims[1] = this->DepthStencilAttachment.Texture.GetHeight();
      image->SetDimensions(dims.data());
      this->GetZbufferData(0, 0, dims[0] - 1, dims[1] - 1, colorF32);
      image->GetPointData()->SetScalars(colorF32);
      break;
    case AttachmentTypeForVTISnapshot::Ids:
      dims[0] = this->IdsAttachment.Texture.GetWidth();
      dims[1] = this->IdsAttachment.Texture.GetHeight();
      image->SetDimensions(dims.data());
      this->GetIdsData(0, 0, dims[0] - 1, dims[1] - 1, colorU32);
      image->GetPointData()->SetScalars(colorU32);
      break;
  }
  return image;
}

//-------------------------------------------------------------------------------------------------
void vtkWebGPURenderWindow::CreateAWindow()
{
  if (!this->HardwareWindow)
  {
    this->SetHardwareWindow(vtk::TakeSmartPointer(vtkHardwareWindow::New()));
    if (this->Interactor)
    {
      this->HardwareWindow->SetInteractor(this->Interactor);
    }
  }
  this->SyncWithHardware();
  this->HardwareWindow->Create();
}

//-------------------------------------------------------------------------------------------------
void vtkWebGPURenderWindow::DestroyWindow()
{
  if (this->HardwareWindow)
  {
    this->HardwareWindow->Destroy();
  }
}

//-------------------------------------------------------------------------------------------------
bool vtkWebGPURenderWindow::EnsureDisplay()
{
  if (!this->HardwareWindow)
  {
    this->HardwareWindow = vtk::TakeSmartPointer(vtkHardwareWindow::New());
  }
  if (this->Interactor && this->Interactor->GetHardwareWindow() != this->HardwareWindow)
  {
    this->HardwareWindow->SetInteractor(this->Interactor);
  }
  return this->HardwareWindow->EnsureDisplay();
}

//-------------------------------------------------------------------------------------------------
void vtkWebGPURenderWindow::SyncWithHardware()
{
  if (!this->HardwareWindow)
  {
    return;
  }
  this->HardwareWindow->SetWindowName(this->GetWindowName());
  int* renderWindowSize = this->GetSize();
  int* hardwareWindowSize = this->HardwareWindow->GetSize();
  if (renderWindowSize[0] != hardwareWindowSize[0] || renderWindowSize[1] != hardwareWindowSize[1])
  {
    // Prioritize explicitly set render window size (> 0) over hardware window size
    // This ensures SetSize() calls from user code are respected during initialization
    if (renderWindowSize[0] > 0 && renderWindowSize[1] > 0)
    {
      // Render window size was explicitly set, use it
      this->HardwareWindow->SetSize(renderWindowSize);
    }
    else if (this->HardwareWindow->GetMTime() > this->GetMTime())
    {
      // Hardware window was modified more recently, use its size
      this->Superclass::SetSize(hardwareWindowSize[0], hardwareWindowSize[1]);
    }
    else
    {
      // Fall back to render window size (even if 0)
      this->HardwareWindow->SetSize(renderWindowSize);
    }
  }
  this->HardwareWindow->SetCoverable(this->GetCoverable());
}

//-------------------------------------------------------------------------------------------------
void* vtkWebGPURenderWindow::GetGenericDisplayId()
{
  if (this->HardwareWindow)
  {
    return this->HardwareWindow->GetGenericDisplayId();
  }
  return this->Superclass::GetGenericDisplayId();
}

//-------------------------------------------------------------------------------------------------
std::string vtkWebGPURenderWindow::MakeDefaultWindowNameWithBackend()
{
  std::string result = "Visualization Toolkit - ";
  if (this->HardwareWindow)
  {
    result += this->HardwareWindow->GetPlatform();
  }
  result += " ";
  if (this->WGPUConfiguration)
  {
    result += this->WGPUConfiguration->GetBackendInUseAsString();
  }
  else
  {
    result += "undefined backend";
  }
  return result;
}

VTK_ABI_NAMESPACE_END
