From ae810e1a9c68d05bee254ef570fbb0e783e25931 Mon Sep 17 00:00:00 2001 From: skal Date: Tue, 10 Feb 2026 17:47:15 +0100 Subject: feat: Add ShaderToy conversion tools MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add automated conversion pipeline for ShaderToy shaders to demo effects: - convert_shadertoy.py: Automated code generation script - Manual templates: Header, implementation, and WGSL boilerplate - Example shader: Test case for conversion workflow - README: Complete conversion guide with examples Handles basic GLSL→WGSL conversion (types, uniforms, mainImage extraction). Manual fixes needed for fragColor returns and complex type inference. Organized under tools/shadertoy/ for maintainability. Co-Authored-By: Claude Sonnet 4.5 --- tools/shadertoy/template.cc | 120 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 tools/shadertoy/template.cc (limited to 'tools/shadertoy/template.cc') diff --git a/tools/shadertoy/template.cc b/tools/shadertoy/template.cc new file mode 100644 index 0000000..288283d --- /dev/null +++ b/tools/shadertoy/template.cc @@ -0,0 +1,120 @@ +// This file is part of the 64k demo project. +// ShaderToy effect implementation - REPLACE THIS LINE +// TODO: Update description, rename class + +#include "gpu/effects/shadertoy_effect.h" +#include "gpu/effects/shader_composer.h" +#include "generated/assets.h" + +// TODO: Rename class and adjust constructor parameters +ShaderToyEffect::ShaderToyEffect(const GpuContext& ctx) : Effect(ctx) { +} + +ShaderToyEffect::~ShaderToyEffect() { + if (sampler_) + wgpuSamplerRelease(sampler_); + if (bind_group_) + wgpuBindGroupRelease(bind_group_); + if (pipeline_) + wgpuRenderPipelineRelease(pipeline_); +} + +void ShaderToyEffect::init(MainSequence* demo) { + demo_ = demo; + params_.init(ctx_.device); + + WGPUSamplerDescriptor sampler_desc = {}; + sampler_desc.addressModeU = WGPUAddressMode_ClampToEdge; + sampler_desc.addressModeV = WGPUAddressMode_ClampToEdge; + sampler_desc.magFilter = WGPUFilterMode_Linear; + sampler_desc.minFilter = WGPUFilterMode_Linear; + sampler_desc.mipmapFilter = WGPUMipmapFilterMode_Linear; + sampler_desc.maxAnisotropy = 1; + sampler_ = wgpuDeviceCreateSampler(ctx_.device, &sampler_desc); + + // TODO: Update asset name to match your shader file + size_t shader_size; + const char* shader_code = (const char*)GetAsset( + AssetId::ASSET_SHADERTOY_SHADER, &shader_size); + + std::string composed = ShaderComposer::Get().Compose({}, shader_code); + + WGPUShaderSourceWGSL wgsl = {}; + wgsl.chain.sType = WGPUSType_ShaderSourceWGSL; + wgsl.code = str_view(composed.c_str()); + + WGPUShaderModuleDescriptor desc = {}; + desc.nextInChain = &wgsl.chain; + WGPUShaderModule module = wgpuDeviceCreateShaderModule(ctx_.device, &desc); + + const WGPUColorTargetState target = { + .format = ctx_.format, + .writeMask = WGPUColorWriteMask_All, + }; + WGPUFragmentState frag = {}; + frag.module = module; + frag.entryPoint = str_view("fs_main"); + frag.targetCount = 1; + frag.targets = ⌖ + + const WGPUDepthStencilState depth_stencil = { + .format = WGPUTextureFormat_Depth24Plus, + .depthWriteEnabled = WGPUOptionalBool_False, + .depthCompare = WGPUCompareFunction_Always, + }; + + WGPURenderPipelineDescriptor pipeline_desc = {}; + pipeline_desc.label = label_view("ShaderToyEffect"); + pipeline_desc.vertex.module = module; + pipeline_desc.vertex.entryPoint = str_view("vs_main"); + pipeline_desc.primitive.topology = WGPUPrimitiveTopology_TriangleList; + pipeline_desc.primitive.cullMode = WGPUCullMode_None; + pipeline_desc.depthStencil = &depth_stencil; + pipeline_desc.multisample.count = 1; + pipeline_desc.multisample.mask = 0xFFFFFFFF; + pipeline_desc.fragment = &frag; + + pipeline_ = wgpuDeviceCreateRenderPipeline(ctx_.device, &pipeline_desc); + wgpuShaderModuleRelease(module); + + WGPUTextureView prev_view = demo_->get_prev_texture_view(); + const WGPUBindGroupEntry entries[] = { + {.binding = 0, .sampler = sampler_}, + {.binding = 1, .textureView = prev_view}, + {.binding = 2, + .buffer = uniforms_.get().buffer, + .size = sizeof(CommonPostProcessUniforms)}, + {.binding = 3, + .buffer = params_.get().buffer, + .size = sizeof(ShaderToyParams)}, + }; + const WGPUBindGroupDescriptor bg_desc = { + .layout = wgpuRenderPipelineGetBindGroupLayout(pipeline_, 0), + .entryCount = 4, + .entries = entries, + }; + bind_group_ = wgpuDeviceCreateBindGroup(ctx_.device, &bg_desc); +} + +void ShaderToyEffect::render(WGPURenderPassEncoder pass, float time, + float beat, float intensity, float aspect_ratio) { + const CommonPostProcessUniforms uniforms = { + .resolution = {static_cast(width_), static_cast(height_)}, + .aspect_ratio = aspect_ratio, + .time = time, + .beat = beat, + .audio_intensity = intensity, + }; + uniforms_.update(ctx_.queue, uniforms); + + // TODO: Update parameters based on your effect + const ShaderToyParams params = { + .param1 = 1.0f, + .param2 = beat, + }; + params_.update(ctx_.queue, params); + + wgpuRenderPassEncoderSetPipeline(pass, pipeline_); + wgpuRenderPassEncoderSetBindGroup(pass, 0, bind_group_, 0, nullptr); + wgpuRenderPassEncoderDraw(pass, 3, 1, 0, 0); +} -- cgit v1.2.3