diff --git a/src/client/classes.hpp b/src/client/classes.hpp index 0626ef50d..a0eabf1e8 100644 --- a/src/client/classes.hpp +++ b/src/client/classes.hpp @@ -38,12 +38,29 @@ namespace builtin_scene /** * @namespace client_graphics * The `client_graphics` namespace contains classes related to graphics rendering, - * such as `WebGLContext` and `WebGL2Context`. + * such as `WebGLContext`, `WebGL2Context`, and `WebGPUContext`. */ namespace client_graphics { class WebGLContext; // Represents a WebGL 1.0 rendering context class WebGL2Context; // Represents a WebGL 2.0 rendering context + + // WebGPU classes (split into individual files) + class WebGPUContextAttributes; // Context configuration attributes + class WebGPUAdapter; // Represents a WebGPU adapter + class WebGPUDevice; // Represents a WebGPU device + class WebGPUQueue; // Represents a WebGPU queue + class WebGPUContext; // Main WebGPU context interface + class WebGPUCommandEncoder; // Records GPU commands + class WebGPUCommandBuffer; // Container for recorded commands + class WebGPURenderPassEncoder; // Records render pass commands + + // WebGPU resource classes (placeholders) + class WebGPUBuffer; // Represents a WebGPU buffer + class WebGPUTexture; // Represents a WebGPU texture + class WebGPUBindGroup; // Represents a WebGPU bind group + class WebGPURenderPipeline; // Represents a WebGPU render pipeline + class WebGPUComputePipeline; // Represents a WebGPU compute pipeline } /** diff --git a/src/client/graphics/webgpu_adapter.cpp b/src/client/graphics/webgpu_adapter.cpp new file mode 100644 index 000000000..1018ebc4e --- /dev/null +++ b/src/client/graphics/webgpu_adapter.cpp @@ -0,0 +1,50 @@ +#include "./webgpu_adapter.hpp" +#include "./webgpu_device.hpp" + +#include +#include + +namespace client_graphics +{ + using namespace std; + + // WebGPUAdapter implementation + WebGPUAdapter::WebGPUAdapter(const commandbuffers::GPUAdapterInfo &info) + : adapter_info_(info) + , features_() + , limits_() + { + // Initialize with default features and limits + // In a full implementation, these would be queried from the actual adapter + } + + std::unique_ptr WebGPUAdapter::requestDevice( + std::optional label, + const std::vector &requiredFeatures, + const std::unordered_map &requiredLimits) + { + // Validate required features + for (const auto &feature : requiredFeatures) + { + if (features_.find(feature) == features_.end()) + { + // Feature not supported + return nullptr; + } + } + + // Validate required limits + for (const auto &[limitName, requiredValue] : requiredLimits) + { + auto it = limits_.find(limitName); + if (it == limits_.end() || it->second < requiredValue) + { + // Limit not supported or insufficient + return nullptr; + } + } + + // Create device with the requested configuration + return std::make_unique(adapter_info_, label); + } +} \ No newline at end of file diff --git a/src/client/graphics/webgpu_adapter.hpp b/src/client/graphics/webgpu_adapter.hpp new file mode 100644 index 000000000..284d70352 --- /dev/null +++ b/src/client/graphics/webgpu_adapter.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include +#include +#include +#include + +#include + +namespace client_graphics +{ + /** + * The `WebGPUAdapter` class represents a WebGPU adapter which provides information + * about the underlying graphics hardware and its capabilities. + */ + class WebGPUAdapter + { + public: + WebGPUAdapter(const commandbuffers::GPUAdapterInfo &info); + ~WebGPUAdapter() = default; + + public: + const commandbuffers::GPUAdapterInfo &info() const + { + return adapter_info_; + } + const commandbuffers::GPUSupportedFeatures &features() const + { + return features_; + } + const commandbuffers::GPUSupportedLimits &limits() const + { + return limits_; + } + + /** + * Request a device from this adapter. + * @param label Optional label for the device + * @param requiredFeatures Features required by the device + * @param requiredLimits Limits required by the device + * @returns A WebGPU device or nullptr if device creation fails + */ + std::unique_ptr requestDevice( + std::optional label = std::nullopt, + const std::vector &requiredFeatures = {}, + const std::unordered_map &requiredLimits = {}); + + private: + commandbuffers::GPUAdapterInfo adapter_info_; + commandbuffers::GPUSupportedFeatures features_; + commandbuffers::GPUSupportedLimits limits_; + }; +} \ No newline at end of file diff --git a/src/client/graphics/webgpu_bind_group.hpp b/src/client/graphics/webgpu_bind_group.hpp new file mode 100644 index 000000000..48d1584da --- /dev/null +++ b/src/client/graphics/webgpu_bind_group.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include +#include + +namespace client_graphics +{ + /** + * Represents a WebGPU bind group. + * This is a placeholder implementation for the client-side bind group interface. + */ + class WebGPUBindGroup + { + public: + WebGPUBindGroup(std::string label = "WebGPUBindGroup") + : label_(std::move(label)) + , id_(commandbuffers::GPUHandle("").id) // Generate unique ID + { + } + + const std::string &label() const + { + return label_; + } + commandbuffers::GPUIdentifier id() const + { + return id_; + } + + private: + std::string label_; + commandbuffers::GPUIdentifier id_; + }; +} \ No newline at end of file diff --git a/src/client/graphics/webgpu_buffer.hpp b/src/client/graphics/webgpu_buffer.hpp new file mode 100644 index 000000000..c12f51872 --- /dev/null +++ b/src/client/graphics/webgpu_buffer.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include +#include + +namespace client_graphics +{ + /** + * Represents a WebGPU buffer resource. + * This is a placeholder implementation for the client-side buffer interface. + */ + class WebGPUBuffer + { + public: + WebGPUBuffer(std::string label = "WebGPUBuffer") + : label_(std::move(label)) + , id_(commandbuffers::GPUHandle("").id) // Generate unique ID + { + } + + const std::string &label() const + { + return label_; + } + commandbuffers::GPUIdentifier id() const + { + return id_; + } + + private: + std::string label_; + commandbuffers::GPUIdentifier id_; + }; +} \ No newline at end of file diff --git a/src/client/graphics/webgpu_command_buffer.cpp b/src/client/graphics/webgpu_command_buffer.cpp new file mode 100644 index 000000000..48cc36a0d --- /dev/null +++ b/src/client/graphics/webgpu_command_buffer.cpp @@ -0,0 +1,36 @@ +#include "./webgpu_command_buffer.hpp" + +#include + +namespace client_graphics +{ + using namespace std; + + // WebGPUCommandBuffer implementation + WebGPUCommandBuffer::WebGPUCommandBuffer(std::optional label) + : label_(label.value_or("WebGPUCommandBuffer")) + { + } + + void WebGPUCommandBuffer::execute() const + { + // In the client-side implementation, this would typically transmit + // the recorded commands to the graphics server for execution. + // For now, we just log the execution for debugging purposes. + + if (commands_.empty()) + { + return; + } + + // In a full implementation, this would serialize and send the commands + // to the server-side renderer via the command buffer system. + // Following the WebGL pattern in webgl_context.cpp + + std::cout << "Executing WebGPU command buffer '" << label_ + << "' with " << commands_.size() << " commands" << std::endl; + + // Placeholder for command transmission logic + // This would follow the same pattern as WebGLContext::sendCommandBufferRequest() + } +} \ No newline at end of file diff --git a/src/client/graphics/webgpu_command_buffer.hpp b/src/client/graphics/webgpu_command_buffer.hpp new file mode 100644 index 000000000..a351e3e88 --- /dev/null +++ b/src/client/graphics/webgpu_command_buffer.hpp @@ -0,0 +1,67 @@ +#pragma once + +#include +#include +#include +#include + +#include + +namespace client_graphics +{ + /** + * The `WebGPUCommandBuffer` class represents a recorded sequence of GPU commands. + * This is the client-side implementation that holds recorded commands for later submission. + */ + class WebGPUCommandBuffer + { + public: + WebGPUCommandBuffer(std::optional label = std::nullopt); + ~WebGPUCommandBuffer() = default; + + // Non-copyable but movable + WebGPUCommandBuffer(const WebGPUCommandBuffer &) = delete; + WebGPUCommandBuffer &operator=(const WebGPUCommandBuffer &) = delete; + WebGPUCommandBuffer(WebGPUCommandBuffer &&) = default; + WebGPUCommandBuffer &operator=(WebGPUCommandBuffer &&) = default; + + public: + const std::string &label() const + { + return label_; + } + bool isEmpty() const + { + return commands_.empty(); + } + size_t commandCount() const + { + return commands_.size(); + } + + /** + * Execute the recorded commands. In the client-side implementation, + * this would typically transmit the commands to the server. + * For now, this is a placeholder for the command recording pattern. + */ + void execute() const; + + private: + friend class WebGPUCommandEncoder; + friend class WebGPURenderPassEncoder; + + std::string label_; + std::vector> commands_; + + /** + * Add a command to the command buffer. + * This follows the same pattern as the existing GPU command buffer implementation. + */ + template + void addCommand(Args &&...args) + { + auto command = std::make_shared(std::forward(args)...); + commands_.push_back(command); + } + }; +} \ No newline at end of file diff --git a/src/client/graphics/webgpu_command_encoder.cpp b/src/client/graphics/webgpu_command_encoder.cpp new file mode 100644 index 000000000..b74b9c738 --- /dev/null +++ b/src/client/graphics/webgpu_command_encoder.cpp @@ -0,0 +1,128 @@ +#include "./webgpu_command_encoder.hpp" + +#include +#include + +namespace client_graphics +{ + using namespace std; + + // WebGPUCommandEncoder implementation + WebGPUCommandEncoder::WebGPUCommandEncoder(std::optional label) + : command_buffer_(std::make_shared(label)) + , label_(label.value_or("WebGPUCommandEncoder")) + { + } + + std::unique_ptr WebGPUCommandEncoder::beginRenderPass( + const commandbuffers::GPURenderPassDescriptor &descriptor) + { + if (finished_) + { + throw std::runtime_error("Cannot begin render pass on finished command encoder"); + } + + // Create render pass encoder with shared command buffer + auto encoder = std::make_unique( + command_buffer_, descriptor, "RenderPass"); + + std::cout << "WebGPU: Begin render pass in encoder '" << label_ << "'" << std::endl; + + return encoder; + } + + void WebGPUCommandEncoder::copyBufferToBuffer(const WebGPUBuffer &source, + uint64_t sourceOffset, + const WebGPUBuffer &destination, + uint64_t destinationOffset, + uint64_t size) + { + if (finished_) + { + throw std::runtime_error("Cannot copy buffer on finished command encoder"); + } + + // Record buffer copy command + // In a full implementation, this would use actual buffer objects + std::cout << "WebGPU: Copy buffer to buffer (" << size << " bytes) in encoder '" << label_ << "'" << std::endl; + } + + void WebGPUCommandEncoder::clearBuffer(const WebGPUBuffer &buffer, + uint64_t offset, + uint64_t size) + { + if (finished_) + { + throw std::runtime_error("Cannot clear buffer on finished command encoder"); + } + + // Record buffer clear command + std::cout << "WebGPU: Clear buffer (" << size << " bytes at offset " << offset + << ") in encoder '" << label_ << "'" << std::endl; + } + + std::unique_ptr WebGPUCommandEncoder::finish( + std::optional label) + { + if (finished_) + { + throw std::runtime_error("Command encoder already finished"); + } + + if (debug_group_depth_ > 0) + { + throw std::runtime_error("Cannot finish command encoder with unclosed debug groups"); + } + + finished_ = true; + + // Create a new command buffer with the recorded commands + auto finalLabel = label.value_or(command_buffer_->label()); + auto finishedBuffer = std::make_unique(finalLabel); + + // Transfer commands from shared buffer to the finished buffer + finishedBuffer->commands_ = std::move(command_buffer_->commands_); + + std::cout << "WebGPU: Finish command encoder '" << label_ + << "' with " << finishedBuffer->commandCount() << " commands" << std::endl; + + return finishedBuffer; + } + + void WebGPUCommandEncoder::pushDebugGroup(const std::string &groupLabel) + { + if (finished_) + { + throw std::runtime_error("Cannot push debug group on finished command encoder"); + } + + debug_group_depth_++; + std::cout << "WebGPU: Push debug group '" << groupLabel << "' in encoder '" << label_ << "'" << std::endl; + } + + void WebGPUCommandEncoder::popDebugGroup() + { + if (finished_) + { + throw std::runtime_error("Cannot pop debug group on finished command encoder"); + } + + if (debug_group_depth_ == 0) + { + throw std::runtime_error("No debug group to pop"); + } + + debug_group_depth_--; + std::cout << "WebGPU: Pop debug group in encoder '" << label_ << "'" << std::endl; + } + + void WebGPUCommandEncoder::insertDebugMarker(const std::string &markerLabel) + { + if (finished_) + { + throw std::runtime_error("Cannot insert debug marker on finished command encoder"); + } + + std::cout << "WebGPU: Debug marker '" << markerLabel << "' in encoder '" << label_ << "'" << std::endl; + } +} \ No newline at end of file diff --git a/src/client/graphics/webgpu_command_encoder.hpp b/src/client/graphics/webgpu_command_encoder.hpp new file mode 100644 index 000000000..a3bcdc4a7 --- /dev/null +++ b/src/client/graphics/webgpu_command_encoder.hpp @@ -0,0 +1,110 @@ +#pragma once + +#include +#include +#include + +#include +#include "./webgpu_command_buffer.hpp" +#include "./webgpu_render_pass_encoder.hpp" +#include "./webgpu_buffer.hpp" + +namespace client_graphics +{ + /** + * The `WebGPUCommandEncoder` class allows recording of GPU commands. + * This is the client-side implementation that records commands into command buffers. + */ + class WebGPUCommandEncoder + { + public: + WebGPUCommandEncoder(std::optional label = std::nullopt); + ~WebGPUCommandEncoder() = default; + + // Non-copyable, non-movable (following WebGPU specification) + WebGPUCommandEncoder(const WebGPUCommandEncoder &) = delete; + WebGPUCommandEncoder &operator=(const WebGPUCommandEncoder &) = delete; + WebGPUCommandEncoder(WebGPUCommandEncoder &&) = delete; + WebGPUCommandEncoder &operator=(WebGPUCommandEncoder &&) = delete; + + public: + /** + * Begin a render pass. + * @param descriptor Render pass configuration + * @returns A render pass encoder for recording render commands + */ + std::unique_ptr beginRenderPass( + const commandbuffers::GPURenderPassDescriptor &descriptor); + + /** + * Begin a compute pass (placeholder for future implementation). + * @param descriptor Compute pass configuration + * @returns A compute pass encoder for recording compute commands + */ + // std::unique_ptr beginComputePass( + // const WebGPUComputePassDescriptor& descriptor = {}); + + /** + * Copy buffer to buffer. + * @param source Source buffer + * @param sourceOffset Offset in source buffer + * @param destination Destination buffer + * @param destinationOffset Offset in destination buffer + * @param size Number of bytes to copy + */ + void copyBufferToBuffer(const WebGPUBuffer &source, uint64_t sourceOffset, const WebGPUBuffer &destination, uint64_t destinationOffset, uint64_t size); + + /** + * Copy buffer to texture (placeholder for future implementation). + */ + // void copyBufferToTexture(const WebGPUImageCopyBuffer& source, + // const WebGPUImageCopyTexture& destination, + // const WebGPUExtent3D& copySize); + + /** + * Copy texture to buffer (placeholder for future implementation). + */ + // void copyTextureToBuffer(const WebGPUImageCopyTexture& source, + // const WebGPUImageCopyBuffer& destination, + // const WebGPUExtent3D& copySize); + + /** + * Copy texture to texture (placeholder for future implementation). + */ + // void copyTextureToTexture(const WebGPUImageCopyTexture& source, + // const WebGPUImageCopyTexture& destination, + // const WebGPUExtent3D& copySize); + + /** + * Clear buffer with zeros. + * @param buffer Buffer to clear + * @param offset Offset to start clearing from + * @param size Number of bytes to clear + */ + void clearBuffer(const WebGPUBuffer &buffer, uint64_t offset = 0, uint64_t size = 0); + + /** + * Resolve query set (placeholder for future implementation). + */ + // void resolveQuerySet(const WebGPUQuerySet& querySet, uint32_t firstQuery, uint32_t queryCount, + // const WebGPUBuffer& destination, uint64_t destinationOffset); + + /** + * Finish recording and return the command buffer. + * @param label Optional label for the command buffer + * @returns The recorded command buffer + */ + std::unique_ptr finish(std::optional label = std::nullopt); + + // Debug commands + void pushDebugGroup(const std::string &groupLabel); + void popDebugGroup(); + void insertDebugMarker(const std::string &markerLabel); + + private: + std::shared_ptr command_buffer_; + std::string label_; + bool finished_ = false; + uint32_t debug_group_depth_ = 0; + }; +} \ No newline at end of file diff --git a/src/client/graphics/webgpu_compute_pipeline.hpp b/src/client/graphics/webgpu_compute_pipeline.hpp new file mode 100644 index 000000000..3d9de9e36 --- /dev/null +++ b/src/client/graphics/webgpu_compute_pipeline.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include +#include + +namespace client_graphics +{ + /** + * Represents a WebGPU compute pipeline. + * This is a placeholder implementation for the client-side compute pipeline interface. + */ + class WebGPUComputePipeline + { + public: + WebGPUComputePipeline(std::string label = "WebGPUComputePipeline") + : label_(std::move(label)) + , id_(commandbuffers::GPUHandle("").id) // Generate unique ID + { + } + + const std::string &label() const + { + return label_; + } + commandbuffers::GPUIdentifier id() const + { + return id_; + } + + private: + std::string label_; + commandbuffers::GPUIdentifier id_; + }; +} \ No newline at end of file diff --git a/src/client/graphics/webgpu_context.cpp b/src/client/graphics/webgpu_context.cpp new file mode 100644 index 000000000..e8bd81bcf --- /dev/null +++ b/src/client/graphics/webgpu_context.cpp @@ -0,0 +1,62 @@ +#include "./webgpu_context.hpp" +#include "./webgpu_adapter.hpp" + +#include +#include + +namespace client_graphics +{ + using namespace std; + + // WebGPUContext implementation + WebGPUContext::WebGPUContext(const WebGPUContextAttributes &attrs) + : attributes_(attrs) + { + // Context initialization + // In a full implementation, this would set up communication with the graphics server + } + + std::vector> WebGPUContext::enumerateAdapters() + { + std::vector> adapters; + + // Create a default adapter for this client-side implementation + commandbuffers::GPUAdapterInfo defaultAdapterInfo; + defaultAdapterInfo.vendor = "JSAR Runtime"; + defaultAdapterInfo.description = "Default WebGPU Adapter"; + defaultAdapterInfo.device = "Software Renderer"; + defaultAdapterInfo.architecture = "Generic"; + defaultAdapterInfo.subgroupMaxSize = 64; + defaultAdapterInfo.subgroupMinSize = 4; + + adapters.push_back(std::make_unique(defaultAdapterInfo)); + + return adapters; + } + + std::unique_ptr WebGPUContext::requestAdapter( + std::optional powerPreference, + bool forceFallbackAdapter) + { + // For this client-side implementation, return the default adapter + auto adapters = enumerateAdapters(); + if (!adapters.empty()) + { + return std::move(adapters[0]); + } + + return nullptr; + } + + void WebGPUContext::configure(const WebGPUContextAttributes &configuration) + { + attributes_ = configuration; + configured_ = true; + } + + void WebGPUContext::setUncapturedErrorCallback( + std::function callback) + { + error_callback_ = std::move(callback); + } +} \ No newline at end of file diff --git a/src/client/graphics/webgpu_context.hpp b/src/client/graphics/webgpu_context.hpp new file mode 100644 index 000000000..043eddfb0 --- /dev/null +++ b/src/client/graphics/webgpu_context.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "./webgpu_context_attributes.hpp" +#include "./webgpu_adapter.hpp" + +namespace client_graphics +{ + /** + * The `WebGPUContext` class provides the main interface for WebGPU functionality. + * This is analogous to WebGLContext but for the WebGPU API. + */ + class WebGPUContext + { + public: + WebGPUContext(const WebGPUContextAttributes &attrs = WebGPUContextAttributes{}); + ~WebGPUContext() = default; + + public: + // Adapter and device management + std::vector> enumerateAdapters(); + std::unique_ptr requestAdapter( + std::optional powerPreference = std::nullopt, + bool forceFallbackAdapter = false); + + // Canvas configuration (for future canvas integration) + void configure(const WebGPUContextAttributes &configuration); + WebGPUContextAttributes getConfiguration() const + { + return attributes_; + } + + // Context state + bool isConfigured() const + { + return configured_; + } + + // Error handling + void setUncapturedErrorCallback(std::function callback); + + private: + WebGPUContextAttributes attributes_; + bool configured_ = false; + std::function error_callback_; + }; +} \ No newline at end of file diff --git a/src/client/graphics/webgpu_context_attributes.hpp b/src/client/graphics/webgpu_context_attributes.hpp new file mode 100644 index 000000000..ac7fbf5db --- /dev/null +++ b/src/client/graphics/webgpu_context_attributes.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +namespace client_graphics +{ + /** + * Context attributes for WebGPU context creation. + * Based on GPUCanvasConfiguration from WebGPU specification. + */ + class WebGPUContextAttributes + { + public: + std::string powerPreference = "default"; // "low-power" | "high-performance" | "default" + bool forceFallbackAdapter = false; + + // Canvas configuration + std::string format = "bgra8unorm"; + std::vector viewFormats; + std::string colorSpace = "srgb"; + std::string alphaMode = "opaque"; + }; +} \ No newline at end of file diff --git a/src/client/graphics/webgpu_device.cpp b/src/client/graphics/webgpu_device.cpp new file mode 100644 index 000000000..4ed0cd70c --- /dev/null +++ b/src/client/graphics/webgpu_device.cpp @@ -0,0 +1,54 @@ +#include "./webgpu_device.hpp" +#include "./webgpu_queue.hpp" +#include "./webgpu_command_encoder.hpp" + +#include +#include + +namespace client_graphics +{ + using namespace std; + + // WebGPUDevice implementation + WebGPUDevice::WebGPUDevice(const commandbuffers::GPUAdapterInfo &adapter_info, + std::optional label) + : adapter_info_(adapter_info) + , features_() + , limits_() + , queue_(std::make_unique("DefaultQueue")) + , label_(label.value_or("WebGPUDevice")) + { + // Device initialization would typically involve creating actual GPU resources + // For this client-side implementation, we just set up the command recording infrastructure + } + + std::unique_ptr WebGPUDevice::createCommandEncoder( + std::optional label) + { + if (is_lost_) + { + if (error_callback_) + { + error_callback_("device-lost", "Cannot create command encoder on lost device"); + } + return nullptr; + } + + return std::make_unique(label); + } + + void WebGPUDevice::setUncapturedErrorCallback( + std::function callback) + { + error_callback_ = std::move(callback); + } + + void WebGPUDevice::simulateLoss() + { + is_lost_ = true; + if (error_callback_) + { + error_callback_("device-lost", "Device lost"); + } + } +} \ No newline at end of file diff --git a/src/client/graphics/webgpu_device.hpp b/src/client/graphics/webgpu_device.hpp new file mode 100644 index 000000000..f81ca17b3 --- /dev/null +++ b/src/client/graphics/webgpu_device.hpp @@ -0,0 +1,73 @@ +#pragma once + +#include +#include +#include +#include + +#include + +namespace client_graphics +{ + /** + * The `WebGPUDevice` class represents a logical WebGPU device. + * This is the client-side implementation that records commands into command buffers + * following the same pattern as WebGLContext. + */ + class WebGPUDevice + { + public: + WebGPUDevice(const commandbuffers::GPUAdapterInfo &adapter_info, + std::optional label = std::nullopt); + ~WebGPUDevice() = default; + + public: + // Device properties + const commandbuffers::GPUAdapterInfo &adapterInfo() const + { + return adapter_info_; + } + const commandbuffers::GPUSupportedFeatures &features() const + { + return features_; + } + const commandbuffers::GPUSupportedLimits &limits() const + { + return limits_; + } + + // Command recording + std::unique_ptr createCommandEncoder( + std::optional label = std::nullopt); + + // Resource creation (placeholder - full implementation would be in subsequent issues) + // std::unique_ptr createBuffer(const WebGPUBufferDescriptor& descriptor); + // std::unique_ptr createTexture(const WebGPUTextureDescriptor& descriptor); + // std::unique_ptr createRenderPipeline(const WebGPURenderPipelineDescriptor& descriptor); + + // Queue for command submission + class WebGPUQueue &queue() + { + return *queue_; + } + + // Error handling + void setUncapturedErrorCallback(std::function callback); + + // Device loss handling + bool isLost() const + { + return is_lost_; + } + void simulateLoss(); // For testing purposes + + private: + commandbuffers::GPUAdapterInfo adapter_info_; + commandbuffers::GPUSupportedFeatures features_; + commandbuffers::GPUSupportedLimits limits_; + std::unique_ptr queue_; + std::function error_callback_; + bool is_lost_ = false; + std::string label_; + }; +} \ No newline at end of file diff --git a/src/client/graphics/webgpu_queue.cpp b/src/client/graphics/webgpu_queue.cpp new file mode 100644 index 000000000..6516dbbf8 --- /dev/null +++ b/src/client/graphics/webgpu_queue.cpp @@ -0,0 +1,29 @@ +#include "./webgpu_queue.hpp" +#include "./webgpu_command_buffer.hpp" + +#include + +namespace client_graphics +{ + using namespace std; + + // WebGPUQueue implementation + WebGPUQueue::WebGPUQueue(std::optional label) + : label_(label.value_or("WebGPUQueue")) + { + } + + void WebGPUQueue::submit(const std::vector> &commandBuffers) + { + // In the client-side implementation, we record the submission + // In a full implementation, this would transmit commands to the server + for (const auto &commandBuffer : commandBuffers) + { + if (commandBuffer && !commandBuffer->isEmpty()) + { + // Execute the command buffer (placeholder) + commandBuffer->execute(); + } + } + } +} \ No newline at end of file diff --git a/src/client/graphics/webgpu_queue.hpp b/src/client/graphics/webgpu_queue.hpp new file mode 100644 index 000000000..741e57afe --- /dev/null +++ b/src/client/graphics/webgpu_queue.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include + +namespace client_graphics +{ + /** + * The `WebGPUQueue` class represents the command queue for a WebGPU device. + * It handles command buffer submission and manages the command recording lifecycle. + */ + class WebGPUQueue + { + public: + WebGPUQueue(std::optional label = std::nullopt); + ~WebGPUQueue() = default; + + public: + /** + * Submit command buffers to the queue. + * In this client-side implementation, this records the submission for later transmission. + */ + void submit(const std::vector> &commandBuffers); + + // Write operations (placeholder for future implementation) + // void writeBuffer(WebGPUBuffer& buffer, uint64_t offset, const void* data, size_t size); + // void writeTexture(const WebGPUImageCopyTexture& destination, const void* data, size_t dataSize, const WebGPUImageDataLayout& dataLayout, const WebGPUExtent3D& size); + + private: + std::string label_; + std::vector> submitted_command_buffers_; + }; +} \ No newline at end of file diff --git a/src/client/graphics/webgpu_render_pass_encoder.cpp b/src/client/graphics/webgpu_render_pass_encoder.cpp new file mode 100644 index 000000000..d569ecc2f --- /dev/null +++ b/src/client/graphics/webgpu_render_pass_encoder.cpp @@ -0,0 +1,203 @@ +#include "./webgpu_render_pass_encoder.hpp" +#include "./webgpu_command_buffer.hpp" + +#include +#include + +namespace client_graphics +{ + using namespace std; + + // WebGPURenderPassEncoder implementation + WebGPURenderPassEncoder::WebGPURenderPassEncoder( + std::shared_ptr commandBuffer, + const commandbuffers::GPURenderPassDescriptor &descriptor, + std::optional label) + : command_buffer_(std::move(commandBuffer)) + , descriptor_(descriptor) + , label_(label.value_or("WebGPURenderPassEncoder")) + { + if (!command_buffer_) + { + throw std::runtime_error("WebGPURenderPassEncoder: Command buffer cannot be null"); + } + } + + void WebGPURenderPassEncoder::setPipeline(const WebGPURenderPipeline &pipeline) + { + if (ended_) + { + throw std::runtime_error("Cannot set pipeline on ended render pass encoder"); + } + + // Record pipeline setting command + // In a full implementation, this would use the actual pipeline object + // command_buffer_->addCommand(pipeline); + + // Placeholder implementation + std::cout << "WebGPU: Set render pipeline in pass '" << label_ << "'" << std::endl; + } + + void WebGPURenderPassEncoder::setBindGroup(uint32_t index, + const WebGPUBindGroup &bindGroup, + const std::vector &dynamicOffsets) + { + if (ended_) + { + throw std::runtime_error("Cannot set bind group on ended render pass encoder"); + } + + // Record bind group setting command + // command_buffer_->addCommand(bindGroup, index); + + std::cout << "WebGPU: Set bind group " << index << " in pass '" << label_ << "'" << std::endl; + } + + void WebGPURenderPassEncoder::setVertexBuffer(uint32_t slot, + const WebGPUBuffer &buffer, + uint64_t offset, + uint64_t size) + { + if (ended_) + { + throw std::runtime_error("Cannot set vertex buffer on ended render pass encoder"); + } + + // Record vertex buffer setting command following the existing pattern + // command_buffer_->addCommand(slot, buffer, offset, size); + + std::cout << "WebGPU: Set vertex buffer slot " << slot << " in pass '" << label_ << "'" << std::endl; + } + + void WebGPURenderPassEncoder::setIndexBuffer(const WebGPUBuffer &buffer, + commandbuffers::GPUIndexFormat format, + uint64_t offset, + uint64_t size) + { + if (ended_) + { + throw std::runtime_error("Cannot set index buffer on ended render pass encoder"); + } + + // Record index buffer setting command + // command_buffer_->addCommand(buffer, format, offset, size); + + std::cout << "WebGPU: Set index buffer in pass '" << label_ << "'" << std::endl; + } + + void WebGPURenderPassEncoder::draw(uint32_t vertexCount, + uint32_t instanceCount, + uint32_t firstVertex, + uint32_t firstInstance) + { + if (ended_) + { + throw std::runtime_error("Cannot draw on ended render pass encoder"); + } + + // Record draw command using the existing GPU command structure + command_buffer_->addCommand( + vertexCount, instanceCount, firstVertex, firstInstance); + + std::cout << "WebGPU: Draw " << vertexCount << " vertices in pass '" << label_ << "'" << std::endl; + } + + void WebGPURenderPassEncoder::drawIndexed(uint32_t indexCount, + uint32_t instanceCount, + uint32_t firstIndex, + int32_t baseVertex, + uint32_t firstInstance) + { + if (ended_) + { + throw std::runtime_error("Cannot draw indexed on ended render pass encoder"); + } + + // Record indexed draw command + command_buffer_->addCommand( + indexCount, instanceCount, firstIndex, baseVertex, firstInstance); + + std::cout << "WebGPU: Draw indexed " << indexCount << " indices in pass '" << label_ << "'" << std::endl; + } + + void WebGPURenderPassEncoder::setViewport(float x, float y, float width, float height, float minDepth, float maxDepth) + { + if (ended_) + { + throw std::runtime_error("Cannot set viewport on ended render pass encoder"); + } + + // Record viewport command + command_buffer_->addCommand( + x, y, width, height, minDepth, maxDepth); + + std::cout << "WebGPU: Set viewport " << width << "x" << height << " in pass '" << label_ << "'" << std::endl; + } + + void WebGPURenderPassEncoder::setScissorRect(uint32_t x, uint32_t y, uint32_t width, uint32_t height) + { + if (ended_) + { + throw std::runtime_error("Cannot set scissor rect on ended render pass encoder"); + } + + // Record scissor command + command_buffer_->addCommand( + static_cast(x), static_cast(y), static_cast(width), static_cast(height)); + + std::cout << "WebGPU: Set scissor rect " << width << "x" << height << " in pass '" << label_ << "'" << std::endl; + } + + void WebGPURenderPassEncoder::end() + { + if (ended_) + { + throw std::runtime_error("Render pass encoder already ended"); + } + + if (debug_group_depth_ > 0) + { + throw std::runtime_error("Cannot end render pass encoder with unclosed debug groups"); + } + + ended_ = true; + std::cout << "WebGPU: End render pass '" << label_ << "'" << std::endl; + } + + void WebGPURenderPassEncoder::pushDebugGroup(const std::string &groupLabel) + { + if (ended_) + { + throw std::runtime_error("Cannot push debug group on ended render pass encoder"); + } + + debug_group_depth_++; + std::cout << "WebGPU: Push debug group '" << groupLabel << "' in pass '" << label_ << "'" << std::endl; + } + + void WebGPURenderPassEncoder::popDebugGroup() + { + if (ended_) + { + throw std::runtime_error("Cannot pop debug group on ended render pass encoder"); + } + + if (debug_group_depth_ == 0) + { + throw std::runtime_error("No debug group to pop"); + } + + debug_group_depth_--; + std::cout << "WebGPU: Pop debug group in pass '" << label_ << "'" << std::endl; + } + + void WebGPURenderPassEncoder::insertDebugMarker(const std::string &markerLabel) + { + if (ended_) + { + throw std::runtime_error("Cannot insert debug marker on ended render pass encoder"); + } + + std::cout << "WebGPU: Debug marker '" << markerLabel << "' in pass '" << label_ << "'" << std::endl; + } +} \ No newline at end of file diff --git a/src/client/graphics/webgpu_render_pass_encoder.hpp b/src/client/graphics/webgpu_render_pass_encoder.hpp new file mode 100644 index 000000000..df8549674 --- /dev/null +++ b/src/client/graphics/webgpu_render_pass_encoder.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include "./webgpu_buffer.hpp" +#include "./webgpu_bind_group.hpp" +#include "./webgpu_render_pipeline.hpp" + +namespace client_graphics +{ + /** + * The `WebGPURenderPassEncoder` class allows recording of render commands within a render pass. + * This extends the common GPU render pass encoder with client-side specific functionality. + */ + class WebGPURenderPassEncoder + { + public: + WebGPURenderPassEncoder(std::shared_ptr commandBuffer, + const commandbuffers::GPURenderPassDescriptor &descriptor, + std::optional label = std::nullopt); + ~WebGPURenderPassEncoder() = default; + + // Non-copyable, non-movable (following WebGPU specification) + WebGPURenderPassEncoder(const WebGPURenderPassEncoder &) = delete; + WebGPURenderPassEncoder &operator=(const WebGPURenderPassEncoder &) = delete; + WebGPURenderPassEncoder(WebGPURenderPassEncoder &&) = delete; + WebGPURenderPassEncoder &operator=(WebGPURenderPassEncoder &&) = delete; + + public: + // Render state management + void setPipeline(const class WebGPURenderPipeline &pipeline); + void setBindGroup(uint32_t index, const class WebGPUBindGroup &bindGroup, const std::vector &dynamicOffsets = {}); + + // Vertex and index data + void setVertexBuffer(uint32_t slot, const class WebGPUBuffer &buffer, uint64_t offset = 0, uint64_t size = 0); + void setIndexBuffer(const class WebGPUBuffer &buffer, + commandbuffers::GPUIndexFormat format, + uint64_t offset = 0, + uint64_t size = 0); + + // Drawing commands + void draw(uint32_t vertexCount, uint32_t instanceCount = 1, uint32_t firstVertex = 0, uint32_t firstInstance = 0); + void drawIndexed(uint32_t indexCount, uint32_t instanceCount = 1, uint32_t firstIndex = 0, int32_t baseVertex = 0, uint32_t firstInstance = 0); + + // Render state commands + void setViewport(float x, float y, float width, float height, float minDepth = 0.0f, float maxDepth = 1.0f); + void setScissorRect(uint32_t x, uint32_t y, uint32_t width, uint32_t height); + + // Pass management + void end(); + bool isEnded() const + { + return ended_; + } + + // Debug commands + void pushDebugGroup(const std::string &groupLabel); + void popDebugGroup(); + void insertDebugMarker(const std::string &markerLabel); + + private: + std::shared_ptr command_buffer_; + commandbuffers::GPURenderPassDescriptor descriptor_; + std::string label_; + bool ended_ = false; + uint32_t debug_group_depth_ = 0; + }; +} \ No newline at end of file diff --git a/src/client/graphics/webgpu_render_pipeline.hpp b/src/client/graphics/webgpu_render_pipeline.hpp new file mode 100644 index 000000000..f811bb0a6 --- /dev/null +++ b/src/client/graphics/webgpu_render_pipeline.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include +#include + +namespace client_graphics +{ + /** + * Represents a WebGPU render pipeline. + * This is a placeholder implementation for the client-side render pipeline interface. + */ + class WebGPURenderPipeline + { + public: + WebGPURenderPipeline(std::string label = "WebGPURenderPipeline") + : label_(std::move(label)) + , id_(commandbuffers::GPUHandle("").id) // Generate unique ID + { + } + + const std::string &label() const + { + return label_; + } + commandbuffers::GPUIdentifier id() const + { + return id_; + } + + private: + std::string label_; + commandbuffers::GPUIdentifier id_; + }; +} \ No newline at end of file diff --git a/src/client/graphics/webgpu_texture.hpp b/src/client/graphics/webgpu_texture.hpp new file mode 100644 index 000000000..c7edc7185 --- /dev/null +++ b/src/client/graphics/webgpu_texture.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include +#include + +namespace client_graphics +{ + /** + * Represents a WebGPU texture resource. + * This is a placeholder implementation for the client-side texture interface. + */ + class WebGPUTexture + { + public: + WebGPUTexture(std::string label = "WebGPUTexture") + : label_(std::move(label)) + , id_(commandbuffers::GPUHandle("").id) // Generate unique ID + { + } + + const std::string &label() const + { + return label_; + } + commandbuffers::GPUIdentifier id() const + { + return id_; + } + + private: + std::string label_; + commandbuffers::GPUIdentifier id_; + }; +} \ No newline at end of file diff --git a/tests/client/webgpu_client_tests.cpp b/tests/client/webgpu_client_tests.cpp new file mode 100644 index 000000000..b9af91ba5 --- /dev/null +++ b/tests/client/webgpu_client_tests.cpp @@ -0,0 +1,244 @@ +#define CATCH_CONFIG_MAIN +#include "../catch2/catch_amalgamated.hpp" + +#include +#include +#include +#include +#include +#include + +using namespace client_graphics; + +TEST_CASE("WebGPU Context Creation", "[webgpu-context]") +{ + SECTION("Create WebGPU context with default attributes") + { + WebGPUContextAttributes attrs; + WebGPUContext context(attrs); + + REQUIRE_FALSE(context.isConfigured()); + REQUIRE(context.getConfiguration().powerPreference == "default"); + REQUIRE(context.getConfiguration().format == "bgra8unorm"); + REQUIRE(context.getConfiguration().colorSpace == "srgb"); + REQUIRE(context.getConfiguration().alphaMode == "opaque"); + } + + SECTION("Configure WebGPU context") + { + WebGPUContext context; + WebGPUContextAttributes config; + config.powerPreference = "high-performance"; + config.format = "rgba8unorm"; + + context.configure(config); + + REQUIRE(context.isConfigured()); + REQUIRE(context.getConfiguration().powerPreference == "high-performance"); + REQUIRE(context.getConfiguration().format == "rgba8unorm"); + } +} + +TEST_CASE("WebGPU Adapter Request", "[webgpu-adapter]") +{ + SECTION("Enumerate adapters") + { + WebGPUContext context; + auto adapters = context.enumerateAdapters(); + + REQUIRE_FALSE(adapters.empty()); + REQUIRE(adapters.size() >= 1); + + const auto& adapter = adapters[0]; + REQUIRE(adapter != nullptr); + REQUIRE(adapter->info().vendor == "JSAR Runtime"); + REQUIRE(adapter->info().description == "Default WebGPU Adapter"); + } + + SECTION("Request adapter") + { + WebGPUContext context; + auto adapter = context.requestAdapter(); + + REQUIRE(adapter != nullptr); + REQUIRE(adapter->info().vendor == "JSAR Runtime"); + + // Check adapter capabilities + const auto& features = adapter->features(); + const auto& limits = adapter->limits(); + + REQUIRE(limits.maxTextureDimension2D() >= 8192); + REQUIRE(limits.maxBindGroups() >= 4); + } +} + +TEST_CASE("WebGPU Device Creation", "[webgpu-device]") +{ + SECTION("Request device from adapter") + { + WebGPUContext context; + auto adapter = context.requestAdapter(); + REQUIRE(adapter != nullptr); + + auto device = adapter->requestDevice("TestDevice"); + REQUIRE(device != nullptr); + REQUIRE_FALSE(device->isLost()); + + // Verify device properties + REQUIRE(device->adapterInfo().vendor == "JSAR Runtime"); + REQUIRE(device->limits().maxTextureDimension2D() >= 8192); + } + + SECTION("Device with required features") + { + WebGPUContext context; + auto adapter = context.requestAdapter(); + REQUIRE(adapter != nullptr); + + // Request device with no specific features (should succeed) + std::vector requiredFeatures; + auto device = adapter->requestDevice("TestDevice", requiredFeatures); + REQUIRE(device != nullptr); + } + + SECTION("Device error handling") + { + WebGPUContext context; + auto adapter = context.requestAdapter(); + REQUIRE(adapter != nullptr); + + auto device = adapter->requestDevice("TestDevice"); + REQUIRE(device != nullptr); + + bool errorCalled = false; + device->setUncapturedErrorCallback([&errorCalled](const std::string& type, const std::string& message) { + errorCalled = true; + }); + + // Simulate device loss + device->simulateLoss(); + REQUIRE(device->isLost()); + REQUIRE(errorCalled); + } +} + +TEST_CASE("WebGPU Command Recording", "[webgpu-commands]") +{ + SECTION("Create command encoder") + { + WebGPUContext context; + auto adapter = context.requestAdapter(); + auto device = adapter->requestDevice("TestDevice"); + REQUIRE(device != nullptr); + + auto encoder = device->createCommandEncoder("TestEncoder"); + REQUIRE(encoder != nullptr); + } + + SECTION("Create and finish command buffer") + { + WebGPUContext context; + auto adapter = context.requestAdapter(); + auto device = adapter->requestDevice("TestDevice"); + auto encoder = device->createCommandEncoder("TestEncoder"); + + auto commandBuffer = encoder->finish("TestCommandBuffer"); + REQUIRE(commandBuffer != nullptr); + REQUIRE(commandBuffer->label() == "TestCommandBuffer"); + REQUIRE(commandBuffer->isEmpty()); // No commands recorded yet + } + + SECTION("Record render pass commands") + { + WebGPUContext context; + auto adapter = context.requestAdapter(); + auto device = adapter->requestDevice("TestDevice"); + auto encoder = device->createCommandEncoder("TestEncoder"); + + // Create a simple render pass descriptor + commandbuffers::GPURenderPassDescriptor descriptor; + descriptor.label = "TestRenderPass"; + + auto renderPass = encoder->beginRenderPass(descriptor); + REQUIRE(renderPass != nullptr); + REQUIRE_FALSE(renderPass->isEnded()); + + // Record some drawing commands + renderPass->draw(3, 1, 0, 0); // Draw a triangle + renderPass->setViewport(0, 0, 800, 600); + + renderPass->end(); + REQUIRE(renderPass->isEnded()); + + auto commandBuffer = encoder->finish("TestCommandBuffer"); + REQUIRE(commandBuffer != nullptr); + REQUIRE_FALSE(commandBuffer->isEmpty()); // Should have commands now + REQUIRE(commandBuffer->commandCount() >= 2); // At least draw and viewport commands + } + + SECTION("Queue submission") + { + WebGPUContext context; + auto adapter = context.requestAdapter(); + auto device = adapter->requestDevice("TestDevice"); + auto encoder = device->createCommandEncoder("TestEncoder"); + + // Create simple command buffer + commandbuffers::GPURenderPassDescriptor descriptor; + auto renderPass = encoder->beginRenderPass(descriptor); + renderPass->draw(3); + renderPass->end(); + + auto commandBuffer = encoder->finish(); + + // Submit to queue + std::vector> commandBuffers; + commandBuffers.push_back(std::move(commandBuffer)); + + auto& queue = device->queue(); + REQUIRE_NOTHROW(queue.submit(commandBuffers)); + } + + SECTION("Command encoder error handling") + { + WebGPUContext context; + auto adapter = context.requestAdapter(); + auto device = adapter->requestDevice("TestDevice"); + auto encoder = device->createCommandEncoder("TestEncoder"); + + auto commandBuffer = encoder->finish(); + + // Should throw when trying to use finished encoder + REQUIRE_THROWS_AS(encoder->finish(), std::runtime_error); + + commandbuffers::GPURenderPassDescriptor descriptor; + REQUIRE_THROWS_AS(encoder->beginRenderPass(descriptor), std::runtime_error); + } +} + +TEST_CASE("WebGPU Resource Placeholders", "[webgpu-resources]") +{ + SECTION("Create resource objects") + { + WebGPUBuffer buffer("TestBuffer"); + REQUIRE(buffer.label() == "TestBuffer"); + REQUIRE(buffer.id() != 0); + + WebGPUTexture texture("TestTexture"); + REQUIRE(texture.label() == "TestTexture"); + REQUIRE(texture.id() != 0); + + WebGPUBindGroup bindGroup("TestBindGroup"); + REQUIRE(bindGroup.label() == "TestBindGroup"); + REQUIRE(bindGroup.id() != 0); + + WebGPURenderPipeline pipeline("TestPipeline"); + REQUIRE(pipeline.label() == "TestPipeline"); + REQUIRE(pipeline.id() != 0); + + // Verify unique IDs + REQUIRE(buffer.id() != texture.id()); + REQUIRE(texture.id() != bindGroup.id()); + REQUIRE(bindGroup.id() != pipeline.id()); + } +} \ No newline at end of file