Skip to content

Instantly share code, notes, and snippets.

@dov
Created July 20, 2024 20:53
Show Gist options
  • Save dov/f7d8e0adf755d6ced43ce685efeb1c43 to your computer and use it in GitHub Desktop.
Save dov/f7d8e0adf755d6ced43ce685efeb1c43 to your computer and use it in GitHub Desktop.
Testing using a custom uniform in a shader in VSG
//======================================================================
// This is an example of using a custom uniform in a shader.
//
// 2024-07-20 Sat
// Dov Grobgeld <dov.grobgeld@gmail.com>
//----------------------------------------------------------------------
#include <vsgXchange/all.h>
#include <iostream>
#include <vsg/all.h>
using namespace std;
struct ToyUniform {
vsg::ivec2 iResolution;
vsg::vec2 iMouse;
float iTime;
};
using ToyUniformValue = vsg::Value<ToyUniform>;
// Create a vsg node containing an image
vsg::ref_ptr<vsg::Node> createToyNode(vsg::ref_ptr<vsg::Options> options,
// output
vsg::ref_ptr<ToyUniformValue>& toyUniform)
{
// load shaders
vsg::ref_ptr<vsg::ShaderStage> vertexShader = vsg::read_cast<vsg::ShaderStage>("shaders/shader-toy.vert", options);
vsg::ref_ptr<vsg::ShaderStage> fragmentShader = vsg::read_cast<vsg::ShaderStage>("shaders/shader-toy.frag", options);
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().iMouse = {0,0};
toyUniform->dirty();
// set up graphics pipeline
vsg::DescriptorSetLayoutBindings descriptorBindings{
{0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, 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;
}
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");
// add vsgXchange's support for reading and writing 3rd party file formats
options->add(vsgXchange::all::create());
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->windowTitle = "hello-shader";
windowTraits->height = 600;
windowTraits->width = 800;
auto scene = vsg::Group::create();
// Add the image to the scene
vsg::ref_ptr<ToyUniformValue> toyUniform;
scene->addChild(createToyNode(options,
// output
toyUniform
));
toyUniform->dirty();
if (1)
{
// Test adding a quad to the scene to make sure that I can
// render anything
auto builder = vsg::Builder::create();
vsg::GeometryInfo geomInfo;
vsg::StateInfo stateInfo;
geomInfo.position = vsg::vec3 {0.0f, 0.0f, -0.49f};
geomInfo.color = vsg::vec4 {0.0f, 1.0f, 0.0f, 0.3f}; // green
auto node = builder->createQuad(geomInfo, stateInfo);
scene->addChild(node);
}
// 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)});
// main frame loop
while (viewer->advanceToNextFrame())
{
toyUniform->value().iTime += 1;
toyUniform->dirty();
viewer->handleEvents();
viewer->update();
viewer->recordAndSubmit();
viewer->present();
}
// clean up done automatically thanks to ref_ptr<>
return 0;
}
#version 450
layout(set = 0, binding = 0) uniform UBO {
ivec2 iResolution;
vec2 iMouse;
float iTime;
} ubo;
layout(location = 0) in vec2 fragCoord;
layout(location = 0) out vec4 fragColor;
void main()
{
// Do a dummy test to see if iResolution has been initialized
if (abs(ubo.iResolution.x) == 0)
fragColor = vec4(1.0,0.0,0.0,1.0); // red
else
fragColor = vec4(0.0,1.0,0.0,1.0); // green
}
#version 450
layout(push_constant) uniform PushConstants {
mat4 projection;
mat4 modelView;
} pc;
layout(set = 0, binding = 0) uniform UBO {
ivec2 iResolution;
vec2 iMouse;
float iTime;
} 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);
// How can I "fill the quad" without using the redundant
// project and model view matrixes?
gl_Position = (pc.projection * pc.modelView) * vertex;
fragCoord = vec2(inTexture.x * ubo.iResolution.x,
inTexture.y * ubo.iResolution.y);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment