// Sequence v2 implementation #include "gpu/sequence_v2.h" #include "gpu/effect_v2.h" #include "util/fatal_error.h" #include // NodeRegistry implementation NodeRegistry::NodeRegistry(WGPUDevice device, int default_width, int default_height) : device_(device), default_width_(default_width), default_height_(default_height) { // Create placeholder source/sink nodes (will be updated externally before rendering) Node placeholder = {}; placeholder.type = NodeType::U8X4_NORM; placeholder.width = default_width; placeholder.height = default_height; placeholder.texture = nullptr; placeholder.view = nullptr; nodes_["source"] = placeholder; nodes_["sink"] = placeholder; } NodeRegistry::~NodeRegistry() { for (auto& [name, node] : nodes_) { if (node.view) { wgpuTextureViewRelease(node.view); } for (auto& mip_view : node.mip_views) { wgpuTextureViewRelease(mip_view); } if (node.texture) { wgpuTextureRelease(node.texture); } } } void NodeRegistry::declare_node(const std::string& name, NodeType type, int width, int height) { FATAL_CHECK(nodes_.find(name) != nodes_.end(), "Node already declared: %s\n", name.c_str()); if (width <= 0) width = default_width_; if (height <= 0) height = default_height_; Node node; node.type = type; node.width = width; node.height = height; create_texture(node); nodes_[name] = node; } void NodeRegistry::declare_aliased_node(const std::string& name, const std::string& alias_of) { FATAL_CHECK(nodes_.find(alias_of) == nodes_.end(), "Alias target does not exist: %s\n", alias_of.c_str()); FATAL_CHECK(aliases_.find(name) != aliases_.end(), "Alias already exists: %s\n", name.c_str()); aliases_[name] = alias_of; } WGPUTextureView NodeRegistry::get_view(const std::string& name) { // Check aliases first auto alias_it = aliases_.find(name); if (alias_it != aliases_.end()) { return get_view(alias_it->second); } auto it = nodes_.find(name); FATAL_CHECK(it == nodes_.end(), "Node not found: %s\n", name.c_str()); return it->second.view; } std::vector NodeRegistry::get_output_views(const std::vector& names) { std::vector views; views.reserve(names.size()); for (const auto& name : names) { views.push_back(get_view(name)); } return views; } void NodeRegistry::resize(int width, int height) { default_width_ = width; default_height_ = height; for (auto& [name, node] : nodes_) { // Release old texture if (node.view) { wgpuTextureViewRelease(node.view); } for (auto& mip_view : node.mip_views) { wgpuTextureViewRelease(mip_view); } if (node.texture) { wgpuTextureRelease(node.texture); } // Recreate with new dimensions node.width = width; node.height = height; create_texture(node); } } bool NodeRegistry::has_node(const std::string& name) const { return nodes_.find(name) != nodes_.end() || aliases_.find(name) != aliases_.end(); } void NodeRegistry::set_external_view(const std::string& name, WGPUTextureView view) { // Register external view (texture not owned by registry) Node node = {}; node.view = view; node.texture = nullptr; // Not owned nodes_[name] = node; } void NodeRegistry::create_texture(Node& node) { WGPUTextureFormat format; WGPUTextureUsage usage; switch (node.type) { case NodeType::U8X4_NORM: format = WGPUTextureFormat_RGBA8Unorm; usage = WGPUTextureUsage_RenderAttachment | WGPUTextureUsage_TextureBinding; break; case NodeType::F32X4: format = WGPUTextureFormat_RGBA32Float; usage = WGPUTextureUsage_RenderAttachment | WGPUTextureUsage_TextureBinding; break; case NodeType::F16X8: format = WGPUTextureFormat_RGBA16Float; // WebGPU doesn't have 8-channel, use RGBA16 usage = WGPUTextureUsage_RenderAttachment | WGPUTextureUsage_TextureBinding; break; case NodeType::DEPTH24: format = WGPUTextureFormat_Depth24Plus; usage = WGPUTextureUsage_RenderAttachment; break; case NodeType::COMPUTE_F32: format = WGPUTextureFormat_RGBA32Float; usage = WGPUTextureUsage_StorageBinding | WGPUTextureUsage_TextureBinding; break; } WGPUTextureDescriptor desc = {}; desc.usage = usage; desc.dimension = WGPUTextureDimension_2D; desc.size = {static_cast(node.width), static_cast(node.height), 1}; desc.format = format; desc.mipLevelCount = 1; desc.sampleCount = 1; node.texture = wgpuDeviceCreateTexture(device_, &desc); FATAL_CHECK(node.texture == nullptr, "Failed to create texture\n"); WGPUTextureViewDescriptor view_desc = {}; view_desc.format = format; view_desc.dimension = WGPUTextureViewDimension_2D; view_desc.baseMipLevel = 0; view_desc.mipLevelCount = 1; view_desc.baseArrayLayer = 0; view_desc.arrayLayerCount = 1; view_desc.aspect = (node.type == NodeType::DEPTH24) ? WGPUTextureAspect_DepthOnly : WGPUTextureAspect_All; node.view = wgpuTextureCreateView(node.texture, &view_desc); FATAL_CHECK(node.view == nullptr, "Failed to create texture view\n"); } // SequenceV2 implementation SequenceV2::SequenceV2(const GpuContext& ctx, int width, int height) : ctx_(ctx), width_(width), height_(height), nodes_(ctx.device, width, height) { uniforms_buffer_.init(ctx.device); } void SequenceV2::preprocess(float seq_time, float beat_time, float beat_phase, float audio_intensity) { params_.resolution = {static_cast(width_), static_cast(height_)}; params_.aspect_ratio = static_cast(width_) / static_cast(height_); params_.time = seq_time; params_.beat_time = beat_time; params_.beat_phase = beat_phase; params_.audio_intensity = audio_intensity; params_._pad = 0.0f; uniforms_buffer_.update(ctx_.queue, params_); } void SequenceV2::postprocess(WGPUCommandEncoder encoder) { (void)encoder; // Default: No-op (last effect writes to sink directly) } void SequenceV2::render_effects(WGPUCommandEncoder encoder) { // Execute DAG in topological order (pre-sorted by compiler) for (const auto& dag_node : effect_dag_) { dag_node.effect->render(encoder, params_, nodes_); } } void SequenceV2::resize(int width, int height) { width_ = width; height_ = height; nodes_.resize(width, height); // Notify effects for (auto& dag_node : effect_dag_) { dag_node.effect->resize(width, height); } } void SequenceV2::init_effect_nodes() { for (auto& dag_node : effect_dag_) { dag_node.effect->declare_nodes(nodes_); } }