From a910cfd73b1d46683eee5430919b9a5668381abb Mon Sep 17 00:00:00 2001 From: Dov Grobgeld Date: Tue, 23 Jul 2024 23:26:06 +0300 Subject: [PATCH 1/4] Added a shadertoy example --- examples/app/CMakeLists.txt | 1 + examples/app/vsgshadertoy/CMakeLists.txt | 9 + examples/app/vsgshadertoy/glowmouse.shy | 31 ++ examples/app/vsgshadertoy/rgb-balls.shy | 37 +++ examples/app/vsgshadertoy/vsgshadertoy.cpp | 332 +++++++++++++++++++++ 5 files changed, 410 insertions(+) create mode 100644 examples/app/vsgshadertoy/CMakeLists.txt create mode 100644 examples/app/vsgshadertoy/glowmouse.shy create mode 100644 examples/app/vsgshadertoy/rgb-balls.shy create mode 100644 examples/app/vsgshadertoy/vsgshadertoy.cpp diff --git a/examples/app/CMakeLists.txt b/examples/app/CMakeLists.txt index 51d0a3c3..668de561 100644 --- a/examples/app/CMakeLists.txt +++ b/examples/app/CMakeLists.txt @@ -15,6 +15,7 @@ add_subdirectory(vsganaglyphicstereo) add_subdirectory(vsgwindows) add_subdirectory(vsgcameras) add_subdirectory(vsgvalidate) +add_subdirectory(vsgshadertoy) if (vsgXchange_FOUND) add_subdirectory(vsghelloworld) diff --git a/examples/app/vsgshadertoy/CMakeLists.txt b/examples/app/vsgshadertoy/CMakeLists.txt new file mode 100644 index 00000000..9736a3b5 --- /dev/null +++ b/examples/app/vsgshadertoy/CMakeLists.txt @@ -0,0 +1,9 @@ +set(SOURCES + vsgshadertoy.cpp +) + +add_executable(vsgshadertoy ${SOURCES}) + +target_link_libraries(vsgshadertoy vsg::vsg) + +install(TARGETS vsgshadertoy RUNTIME DESTINATION bin) diff --git a/examples/app/vsgshadertoy/glowmouse.shy b/examples/app/vsgshadertoy/glowmouse.shy new file mode 100644 index 00000000..ef07fed3 --- /dev/null +++ b/examples/app/vsgshadertoy/glowmouse.shy @@ -0,0 +1,31 @@ +// Draw a glowing spot around the mouse position + +void mainImage( out vec4 fragColor, in vec2 fragCoord ) +{ + // Normalized coordinates (from 0 to 1) + vec2 uv = fragCoord / iResolution.xy; + + // Correct uv using the aspect ratio to ensure the circle's aspect ratio is maintained + float aspectRatio = 1.0*iResolution.x / iResolution.y; + vec2 correctedUV = vec2(uv.x * aspectRatio, uv.y); + + // Background gradient: black at the bottom, dark blue at the top + vec3 bgColor = mix(vec3(0.0, 0.0, 0.0), vec3(0.0, 0.0, 0.4), uv.y); + + // Mouse position in normalized coordinates, corrected for aspect ratio + vec2 mousePos = iMouse.xy / iResolution.xy; + vec2 correctedMousePos = vec2(mousePos.x * aspectRatio, mousePos.y); + + // Calculate distance from current fragment to mouse position, corrected for aspect ratio + float distanceToMouse = distance(correctedUV, correctedMousePos); + + // Glow effect based on distance to mouse position + float glowIntensity = 0.1 / sqrt(distanceToMouse); + vec3 glowColor = vec3(1.0, 0.0, 0.0) * glowIntensity; + + // Combine background and glow color + vec3 color = bgColor + glowColor; + + // Output the color + fragColor = vec4(color, 1.0); +} \ No newline at end of file diff --git a/examples/app/vsgshadertoy/rgb-balls.shy b/examples/app/vsgshadertoy/rgb-balls.shy new file mode 100644 index 00000000..94943929 --- /dev/null +++ b/examples/app/vsgshadertoy/rgb-balls.shy @@ -0,0 +1,37 @@ +void mainImage( out vec4 fragColor, in vec2 fragCoord ) +{ + // Correct aspect ratio + vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y; + + // Background gradient + vec3 col = mix(vec3(0.0, 0.0, 0.0), vec3(0.0, 0.0, 0.4), uv.y * 0.5 + 0.5); + + // Light source direction + vec3 lightDir = normalize(vec3(-10.0, -10.0, 20.0)); + + // Sphere properties + float sphereRadius = 0.20; + vec3 sphereColors[3] = vec3[](vec3(1.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0), vec3(0.0, 0.0, 1.0)); + + // Time factor for rotation + float time = iTime * 0.5; + + // Draw each sphere + for(int i = 0; i < 3; i++) { + float angle = time + float(i) * 2.0944; // 120 degrees apart + vec2 position = vec2(sin(angle), cos(angle)) * sphereRadius * 2; + + // Sphere shading + float dist = length(uv - position); + if(dist < sphereRadius) { + float diff = dot(lightDir, normalize(vec3(position - uv, sqrt(sphereRadius * sphereRadius - dist * dist)))); + diff = clamp(diff, 0.0, 1.0); + + // Combine sphere color with light intensity and background + col = mix(col, sphereColors[i] * diff, smoothstep(sphereRadius, sphereRadius - 0.03, dist)); + } + } + + // Output to screen + fragColor = vec4(col,1.0); +} \ No newline at end of file diff --git a/examples/app/vsgshadertoy/vsgshadertoy.cpp b/examples/app/vsgshadertoy/vsgshadertoy.cpp new file mode 100644 index 00000000..1b57b6d3 --- /dev/null +++ b/examples/app/vsgshadertoy/vsgshadertoy.cpp @@ -0,0 +1,332 @@ +//====================================================================== +// This programs allows playing with the fragment shader. The syntax +// of the shader programs is based on the shadertoy syntax at +// https://www.shadertoy.com/new. Note that currently only +// the following uniforms are supported: +// +// uniform vec2 iResolution; // viewport resolution (in pixels) +// uniform float iTime; // shader playback time (in seconds) +// uniform vec2 iMouse; // mouse pixel coords. xy: current +// uniform int iFrame; // shader playback frame +// +// However it is enough to run a large number of shadertoy shaders. +// +// 2024-07-20 Sat +// Dov Grobgeld +//---------------------------------------------------------------------- + +#include +#include +#include + +using namespace std; + +struct ToyUniform { + vsg::ivec2 iResolution; + vsg::vec2 iMouse; + float iTime; + int iFrame; +}; +using ToyUniformValue = vsg::Value; + +// The fixed toy vertex shader +const auto shadertoy_vert = R"( +#version 450 + +layout(set = 0, binding = 0) uniform UBO { + ivec2 iResolution; + vec2 iMouse; + float iTime; + int iFrame; +} ubo; + +layout(location = 0) in vec2 inVertex; +layout(location = 1) in vec2 inTexture; + +layout(location = 0) out vec2 fragCoord; + +out gl_PerVertex{ vec4 gl_Position; }; + +void main() +{ + vec4 vertex = vec4(inVertex, 0, 1.0); + + gl_Position = vec4(inVertex.x, -inVertex.y, 0.5, 1.0); + + fragCoord = vec2(inTexture.x * ubo.iResolution.x, + (1-inTexture.y) * ubo.iResolution.y); +} +)"; + + +string readFile(const string& filename) +{ + ifstream fh(filename); + + if (!fh.good()) + throw runtime_error(std::string("Error opening file \"") + filename + "\" for input!"); + + string ret; + ret.assign((istreambuf_iterator(fh)), istreambuf_iterator()); + fh.close(); + return ret; +} + +const string defaultShader = R"( +void mainImage( out vec4 fragColor, in vec2 fragCoord ) +{ + // Normalized coordinates + vec2 uv = fragCoord/iResolution.xy; + + // Time varying pixel color + vec3 col = 0.5 + 0.5*cos(iTime+uv.xyx+vec3(0,2,4)); + + // Output to screen + fragColor = vec4(col,1.0); +} +)"; + +// The default shader is composed of three dancing spheres +string shaderToyToFragmentShader(const string& toyShader) +{ + return R"( +#version 450 + +layout(set = 0, binding = 0) uniform UBO { + ivec2 iResolution; + vec2 iMouse; + float iTime; + int iFrame; +} ubo; + +layout(location = 0) in vec2 fragCoord; +layout(location = 0) out vec4 fragColor; + +// Create shortcuts to the uniform buffer. This could a +// be solved by a search and replace of the shader string. +ivec2 iResolution = ubo.iResolution; +float iTime = ubo.iTime; + +vec2 iMouse = ubo.iMouse; +int iFrame = ubo.iFrame; + +// This should be exactly shadertoy syntax +)" + toyShader + R"( +void main() +{ + mainImage(fragColor, fragCoord); +})"; + +} + + +// Create a vsg node containing an image +vsg::ref_ptr createToyNode(string toyShader, + // output + vsg::ref_ptr& toyUniform) +{ + // load shaders + auto vertexShader = vsg::ShaderStage::create(VK_SHADER_STAGE_VERTEX_BIT, "main", shadertoy_vert); + + auto fragmentShader = vsg::ShaderStage::create(VK_SHADER_STAGE_FRAGMENT_BIT, "main", shaderToyToFragmentShader(toyShader)); + + if (!vertexShader || !fragmentShader) + throw std::runtime_error("Could not create shaders."); + + toyUniform = ToyUniformValue::create(); + toyUniform->properties.dataVariance = vsg::DataVariance::DYNAMIC_DATA; + toyUniform->value().iResolution = {800,600}; + toyUniform->value().iTime = 0; + toyUniform->value().iFrame = 0; + toyUniform->value().iMouse = vsg::vec2 {0,0}; + toyUniform->dirty(); + + // set up graphics pipeline + vsg::DescriptorSetLayoutBindings descriptorBindings{ + {0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT|VK_SHADER_STAGE_FRAGMENT_BIT, nullptr} // { binding, descriptorType, descriptorCount, stageFlags, pImmutableSamplers} + }; + + auto descriptorSetLayout = vsg::DescriptorSetLayout::create(descriptorBindings); + + vsg::VertexInputState::Bindings vertexBindingsDescriptions{ + VkVertexInputBindingDescription{0, sizeof(vsg::vec2), VK_VERTEX_INPUT_RATE_VERTEX}, // vertex data + VkVertexInputBindingDescription{1, sizeof(vsg::vec2), VK_VERTEX_INPUT_RATE_VERTEX} // tex coord data + }; + + vsg::VertexInputState::Attributes vertexAttributeDescriptions{ + VkVertexInputAttributeDescription{0, 0, VK_FORMAT_R32G32_SFLOAT, 0}, // vertex data + VkVertexInputAttributeDescription{1, 1, VK_FORMAT_R32G32_SFLOAT, 0} // tex coord data + }; + + vsg::GraphicsPipelineStates pipelineStates{ + vsg::VertexInputState::create(vertexBindingsDescriptions, vertexAttributeDescriptions), + vsg::InputAssemblyState::create(), + vsg::RasterizationState::create(), + vsg::MultisampleState::create(), + vsg::ColorBlendState::create(), + vsg::DepthStencilState::create()}; + + // Do I need this when having a constant V,M, and P? + vsg::PushConstantRanges pushConstantRanges{ + {VK_SHADER_STAGE_VERTEX_BIT, 0, 128} // projection, view, and model matrices, actual push constant calls automatically provided by the VSG's RecordTraversal + }; + + auto toyUniformDescriptor = vsg::DescriptorBuffer::create(toyUniform, 0, 0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); + + auto pipelineLayout = vsg::PipelineLayout::create(vsg::DescriptorSetLayouts{descriptorSetLayout}, pushConstantRanges); + auto graphicsPipeline = vsg::GraphicsPipeline::create(pipelineLayout, vsg::ShaderStages{vertexShader, fragmentShader}, pipelineStates); + auto bindGraphicsPipeline = vsg::BindGraphicsPipeline::create(graphicsPipeline); + + auto descriptorSet = vsg::DescriptorSet::create(descriptorSetLayout, vsg::Descriptors{toyUniformDescriptor}); + auto bindDescriptorSet = vsg::BindDescriptorSet::create(VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, descriptorSet); + + // create StateGroup as the root of the scene/command graph to hold the GraphicsPipeline, and binding of Descriptors to decorate the whole graph + auto node = vsg::StateGroup::create(); + node->add(bindGraphicsPipeline); + node->add(bindDescriptorSet); + + // set up vertex and index arrays + auto vertices = vsg::vec2Array::create( + {{-1.0f, -1.0f}, + {1.0f, -1.0f}, + {1.0f, 1.0f}, + {-1.0f, 1.0f}}); // VK_FORMAT_R32G32_SFLOAT, VK_VERTEX_INPUT_RATE_INSTANCE, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_SHARING_MODE_EXCLUSIVE + + auto texcoords = vsg::vec2Array::create( + {{0.0f, 1.0f}, + {1.0f, 1.0f}, + {1.0f, 0.0f}, + {0.0f, 0.0f}}); // VK_FORMAT_R32G32_SFLOAT, VK_VERTEX_INPUT_RATE_VERTEX, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_SHARING_MODE_EXCLUSIVE + + auto indices = vsg::ushortArray::create( + {0, 1, 2, + 2, 3, 0}); // VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_SHARING_MODE_EXCLUSIVE + + // setup geometry + auto drawCommands = vsg::Commands::create(); + drawCommands->addChild(vsg::BindVertexBuffers::create(0, vsg::DataList{vertices, texcoords})); + drawCommands->addChild(vsg::BindIndexBuffer::create(indices)); + drawCommands->addChild(vsg::DrawIndexed::create(6, 1, 0, 0, 0)); + + // add drawCommands to transform + node->addChild(drawCommands); + + return node; +} + +class MouseHandler : public vsg::Inherit +{ +public: + void apply(vsg::PointerEvent& pointerEvent) override + { + lastPointerEvent = &pointerEvent; + } + + + vsg::ref_ptr lastPointerEvent; +}; + +int main(int argc, char** argv) +{ + // set up defaults and read command line arguments to override them + vsg::CommandLine arguments(&argc, argv); + + auto options = vsg::Options::create(); + options->fileCache = vsg::getEnv("VSG_FILE_CACHE"); + options->paths = vsg::getEnvPaths("VSG_FILE_PATH"); + + auto windowTraits = vsg::WindowTraits::create(); + windowTraits->debugLayer = arguments.read({"--debug", "-d"}); + windowTraits->apiDumpLayer = arguments.read({"--api", "-a"}); + arguments.read({"--window", "-w"}, windowTraits->width, windowTraits->height); + + if (arguments.errors()) return arguments.writeErrorMessages(std::cerr); + + + windowTraits->height = 600; + windowTraits->width = 800; + + auto scene = vsg::Group::create(); + + // Read the shader from the command line + std::string toyShader; + if (argc < 2) + { + windowTraits->windowTitle = "vsgshadertoy"; + toyShader = defaultShader; + } + else { + toyShader = readFile(argv[1]); + string title; + // extract the filename from the path + auto pos = string(argv[1]).find_last_of("/"); + if (pos != string::npos) + title = string(argv[1]).substr(pos+1); + else + title = argv[1]; + + windowTraits->windowTitle = title; + } + + + // Add the image to the scene + vsg::ref_ptr toyUniform; + scene->addChild(createToyNode(toyShader, + // output + toyUniform + )); + toyUniform->dirty(); + + // create the viewer and assign window(s) to it + auto viewer = vsg::Viewer::create(); + auto window = vsg::Window::create(windowTraits); + if (!window) + { + std::cout << "Could not create window." << std::endl; + return 1; + } + + viewer->addWindow(window); + + // camera related details + auto viewport = vsg::ViewportState::create(0, 0, window->extent2D().width, window->extent2D().height); + auto ortho = vsg::Orthographic::create(); + ortho->nearDistance = -1000; + ortho->farDistance = 1000; + auto lookAt = vsg::LookAt::create(vsg::dvec3(0, 0, 1.0), vsg::dvec3(0.0, 0.0, 0.0), vsg::dvec3(0.0, 1.0, 0.0)); + auto camera = vsg::Camera::create(ortho, lookAt, viewport); + + auto commandGraph = vsg::createCommandGraphForView(window, camera, scene); + viewer->assignRecordAndSubmitTaskAndPresentation({commandGraph}); + + // compile the Vulkan objects + viewer->compile(); + + // assign a CloseHandler to the Viewer to respond to pressing Escape or the window close button + viewer->addEventHandlers({vsg::CloseHandler::create(viewer)}); + + // Handle the mouse and resize + auto mouseHandler = MouseHandler::create(); + viewer->addEventHandler(mouseHandler); + + // main frame loop + auto t0 = std::chrono::steady_clock::now(); + while (viewer->advanceToNextFrame()) + { + auto extent = window->extent2D(); + toyUniform->value().iResolution = {(int)extent.width, (int)extent.height}; + toyUniform->value().iTime = std::chrono::duration(std::chrono::steady_clock::now()-t0).count(); + toyUniform->value().iFrame += 1; + toyUniform->value().iMouse = mouseHandler->lastPointerEvent ? vsg::vec2(mouseHandler->lastPointerEvent->x, extent.height-mouseHandler->lastPointerEvent->y) : vsg::vec2(0,0); + + toyUniform->dirty(); + + viewer->handleEvents(); + viewer->update(); + viewer->recordAndSubmit(); + viewer->present(); + } + + // clean up done automatically thanks to ref_ptr<> + return 0; +} From 6016a40976027e3883f8c79fd3fdd331d2063951 Mon Sep 17 00:00:00 2001 From: Dov Grobgeld Date: Wed, 24 Jul 2024 19:46:18 +0300 Subject: [PATCH 2/4] Use vsg::Path() instead of std::find_last_of() to extract the filename from a path --- examples/app/vsgshadertoy/vsgshadertoy.cpp | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/examples/app/vsgshadertoy/vsgshadertoy.cpp b/examples/app/vsgshadertoy/vsgshadertoy.cpp index 1b57b6d3..c769b9a8 100644 --- a/examples/app/vsgshadertoy/vsgshadertoy.cpp +++ b/examples/app/vsgshadertoy/vsgshadertoy.cpp @@ -86,7 +86,6 @@ void mainImage( out vec4 fragColor, in vec2 fragCoord ) } )"; -// The default shader is composed of three dancing spheres string shaderToyToFragmentShader(const string& toyShader) { return R"( @@ -257,15 +256,9 @@ int main(int argc, char** argv) } else { toyShader = readFile(argv[1]); - string title; - // extract the filename from the path - auto pos = string(argv[1]).find_last_of("/"); - if (pos != string::npos) - title = string(argv[1]).substr(pos+1); - else - title = argv[1]; - - windowTraits->windowTitle = title; + vsg::Path filePath(argv[1]); + + windowTraits->windowTitle = vsg::simpleFilename(filePath) + vsg::fileExtension(filePath); } From f46f31cb6e59bbe6c58399767860abf22c5cdd7c Mon Sep 17 00:00:00 2001 From: Dov Grobgeld Date: Wed, 24 Jul 2024 22:52:47 +0300 Subject: [PATCH 3/4] Miscellaneous vsgshadertoy cleanup - Use vsg::Path for paths instead of strings - Got rid of the redundant push constant - Removed `using std` as that is not the convention in vsgExamples - Changed the title so that it always display "vsgshadertoy" --- examples/app/vsgshadertoy/vsgshadertoy.cpp | 35 ++++++++++------------ 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/examples/app/vsgshadertoy/vsgshadertoy.cpp b/examples/app/vsgshadertoy/vsgshadertoy.cpp index c769b9a8..51803ee7 100644 --- a/examples/app/vsgshadertoy/vsgshadertoy.cpp +++ b/examples/app/vsgshadertoy/vsgshadertoy.cpp @@ -19,8 +19,6 @@ #include #include -using namespace std; - struct ToyUniform { vsg::ivec2 iResolution; vsg::vec2 iMouse; @@ -59,20 +57,21 @@ void main() )"; -string readFile(const string& filename) +std::string readFile(const vsg::Path& filename) { - ifstream fh(filename); + std::ifstream fh(filename); if (!fh.good()) - throw runtime_error(std::string("Error opening file \"") + filename + "\" for input!"); + throw std::runtime_error(std::string("Error opening file \"") + filename + "\" for input!"); - string ret; - ret.assign((istreambuf_iterator(fh)), istreambuf_iterator()); + std::string ret; + ret.assign((std::istreambuf_iterator(fh)), + std::istreambuf_iterator()); fh.close(); return ret; } -const string defaultShader = R"( +const std::string defaultShader = R"( void mainImage( out vec4 fragColor, in vec2 fragCoord ) { // Normalized coordinates @@ -86,7 +85,7 @@ void mainImage( out vec4 fragColor, in vec2 fragCoord ) } )"; -string shaderToyToFragmentShader(const string& toyShader) +std::string shaderToyToFragmentShader(const std::string& toyShader) { return R"( #version 450 @@ -119,8 +118,8 @@ void main() } -// Create a vsg node containing an image -vsg::ref_ptr createToyNode(string toyShader, +// Create a vsg node containing the shadertoy command +vsg::ref_ptr createToyNode(const std::string& toyShader, // output vsg::ref_ptr& toyUniform) { @@ -165,14 +164,9 @@ vsg::ref_ptr createToyNode(string toyShader, vsg::ColorBlendState::create(), vsg::DepthStencilState::create()}; - // Do I need this when having a constant V,M, and P? - vsg::PushConstantRanges pushConstantRanges{ - {VK_SHADER_STAGE_VERTEX_BIT, 0, 128} // projection, view, and model matrices, actual push constant calls automatically provided by the VSG's RecordTraversal - }; - auto toyUniformDescriptor = vsg::DescriptorBuffer::create(toyUniform, 0, 0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); - auto pipelineLayout = vsg::PipelineLayout::create(vsg::DescriptorSetLayouts{descriptorSetLayout}, pushConstantRanges); + auto pipelineLayout = vsg::PipelineLayout::create(vsg::DescriptorSetLayouts{descriptorSetLayout}, vsg::PushConstantRanges {}); auto graphicsPipeline = vsg::GraphicsPipeline::create(pipelineLayout, vsg::ShaderStages{vertexShader, fragmentShader}, pipelineStates); auto bindGraphicsPipeline = vsg::BindGraphicsPipeline::create(graphicsPipeline); @@ -254,11 +248,12 @@ int main(int argc, char** argv) windowTraits->windowTitle = "vsgshadertoy"; toyShader = defaultShader; } - else { - toyShader = readFile(argv[1]); + else + { vsg::Path filePath(argv[1]); + toyShader = readFile(filePath); - windowTraits->windowTitle = vsg::simpleFilename(filePath) + vsg::fileExtension(filePath); + windowTraits->windowTitle = std::string("vsgshadertoy: ") + vsg::simpleFilename(filePath) + vsg::fileExtension(filePath); } From 3e45ebc853798304649e06da31046ef842013230 Mon Sep 17 00:00:00 2001 From: Dov Grobgeld Date: Mon, 29 Jul 2024 22:40:25 +0300 Subject: [PATCH 4/4] Simplify shadertoy shaders and buffers - Removed the vertex and the index buffers - Removed their respective descriptors - Use single large triangle for covering quad like in https://www.saschawillems.de/blog/2016/08/13/vulkan-tutorial-on-rendering-a-fullscreen-quad-without-buffers/ - Change mouse behavior to react only if mouse is pressed --- examples/app/vsgshadertoy/vsgshadertoy.cpp | 59 ++++++---------------- 1 file changed, 16 insertions(+), 43 deletions(-) diff --git a/examples/app/vsgshadertoy/vsgshadertoy.cpp b/examples/app/vsgshadertoy/vsgshadertoy.cpp index 51803ee7..871abe42 100644 --- a/examples/app/vsgshadertoy/vsgshadertoy.cpp +++ b/examples/app/vsgshadertoy/vsgshadertoy.cpp @@ -38,21 +38,20 @@ layout(set = 0, binding = 0) uniform UBO { int iFrame; } ubo; -layout(location = 0) in vec2 inVertex; -layout(location = 1) in vec2 inTexture; - layout(location = 0) out vec2 fragCoord; out gl_PerVertex{ vec4 gl_Position; }; void main() { - vec4 vertex = vec4(inVertex, 0, 1.0); - - gl_Position = vec4(inVertex.x, -inVertex.y, 0.5, 1.0); - - fragCoord = vec2(inTexture.x * ubo.iResolution.x, - (1-inTexture.y) * ubo.iResolution.y); + // fragCord is from (0→w,0→h) + fragCoord = vec2((gl_VertexIndex << 1) & 2, + (gl_VertexIndex & 2)) * ubo.iResolution; + + // gl_Position is from (-1→1,-1→1) + gl_Position = vec4(fragCoord.x/ubo.iResolution.x * 2.0 - 1.0, + (1.0-fragCoord.y/ubo.iResolution.y) * 2.0 - 1.0, + 0.5, 1.0); } )"; @@ -141,23 +140,13 @@ vsg::ref_ptr createToyNode(const std::string& toyShader, // set up graphics pipeline vsg::DescriptorSetLayoutBindings descriptorBindings{ - {0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT|VK_SHADER_STAGE_FRAGMENT_BIT, nullptr} // { binding, descriptorType, descriptorCount, stageFlags, pImmutableSamplers} + {0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT|VK_SHADER_STAGE_FRAGMENT_BIT, nullptr} // { binding, descriptorType, descriptorCount, stageFlags, pImmutableSamplers} }; auto descriptorSetLayout = vsg::DescriptorSetLayout::create(descriptorBindings); - vsg::VertexInputState::Bindings vertexBindingsDescriptions{ - VkVertexInputBindingDescription{0, sizeof(vsg::vec2), VK_VERTEX_INPUT_RATE_VERTEX}, // vertex data - VkVertexInputBindingDescription{1, sizeof(vsg::vec2), VK_VERTEX_INPUT_RATE_VERTEX} // tex coord data - }; - - vsg::VertexInputState::Attributes vertexAttributeDescriptions{ - VkVertexInputAttributeDescription{0, 0, VK_FORMAT_R32G32_SFLOAT, 0}, // vertex data - VkVertexInputAttributeDescription{1, 1, VK_FORMAT_R32G32_SFLOAT, 0} // tex coord data - }; - vsg::GraphicsPipelineStates pipelineStates{ - vsg::VertexInputState::create(vertexBindingsDescriptions, vertexAttributeDescriptions), + vsg::VertexInputState::create(), // No vertices for shader toy vsg::InputAssemblyState::create(), vsg::RasterizationState::create(), vsg::MultisampleState::create(), @@ -178,28 +167,9 @@ vsg::ref_ptr createToyNode(const std::string& toyShader, node->add(bindGraphicsPipeline); node->add(bindDescriptorSet); - // set up vertex and index arrays - auto vertices = vsg::vec2Array::create( - {{-1.0f, -1.0f}, - {1.0f, -1.0f}, - {1.0f, 1.0f}, - {-1.0f, 1.0f}}); // VK_FORMAT_R32G32_SFLOAT, VK_VERTEX_INPUT_RATE_INSTANCE, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_SHARING_MODE_EXCLUSIVE - - auto texcoords = vsg::vec2Array::create( - {{0.0f, 1.0f}, - {1.0f, 1.0f}, - {1.0f, 0.0f}, - {0.0f, 0.0f}}); // VK_FORMAT_R32G32_SFLOAT, VK_VERTEX_INPUT_RATE_VERTEX, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_SHARING_MODE_EXCLUSIVE - - auto indices = vsg::ushortArray::create( - {0, 1, 2, - 2, 3, 0}); // VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_SHARING_MODE_EXCLUSIVE - // setup geometry auto drawCommands = vsg::Commands::create(); - drawCommands->addChild(vsg::BindVertexBuffers::create(0, vsg::DataList{vertices, texcoords})); - drawCommands->addChild(vsg::BindIndexBuffer::create(indices)); - drawCommands->addChild(vsg::DrawIndexed::create(6, 1, 0, 0, 0)); + drawCommands->addChild(vsg::Draw::create(3, 1, 0, 0)); // Draw without vertices, as they are generated from gl_VertexIndex // add drawCommands to transform node->addChild(drawCommands); @@ -213,10 +183,11 @@ class MouseHandler : public vsg::Inherit void apply(vsg::PointerEvent& pointerEvent) override { lastPointerEvent = &pointerEvent; + isPressed = pointerEvent.mask != vsg::BUTTON_MASK_OFF; } - vsg::ref_ptr lastPointerEvent; + bool isPressed = false; }; int main(int argc, char** argv) @@ -305,7 +276,9 @@ int main(int argc, char** argv) toyUniform->value().iResolution = {(int)extent.width, (int)extent.height}; toyUniform->value().iTime = std::chrono::duration(std::chrono::steady_clock::now()-t0).count(); toyUniform->value().iFrame += 1; - toyUniform->value().iMouse = mouseHandler->lastPointerEvent ? vsg::vec2(mouseHandler->lastPointerEvent->x, extent.height-mouseHandler->lastPointerEvent->y) : vsg::vec2(0,0); + + if (mouseHandler->isPressed) + toyUniform->value().iMouse = mouseHandler->lastPointerEvent ? vsg::vec2(mouseHandler->lastPointerEvent->x, extent.height-mouseHandler->lastPointerEvent->y) : vsg::vec2(0,0); toyUniform->dirty();