Draw example
This example draw a triangle with two controls.
- One control is an uniform exposed directly to the object.
- Another is a CPU-only control which the author of the node then maps to another uniform in a custom way.
- The texture also comes from the node's code.
#pragma once
#include <avnd/common/member_reflection.hpp>
#include <cmath>
#include <gpp/commands.hpp>
#include <gpp/meta.hpp>
#include <gpp/ports.hpp>
#include <halp/controls.hpp>
#include <halp/static_string.hpp>
namespace examples
{
struct GpuFilterExample
{
halp_meta(name, "Helpers GPU pipeline");
halp_meta(uuid, "ebe6f07a-5e7f-4cf8-bd2b-e5dc3e22a1f9");
// Define the layout of our pipeline in C++ simply through the structure of a struct
static constexpr struct layout
{
halp_flags(graphics);
struct vertex_input
{
gpp_attribute(0, v_position, float[3], position)
pos;
gpp_attribute(1, v_texcoord, float[2], texcoord)
tex;
} vertex_input;
struct vertex_output
{
gpp_attribute(0, texcoord, float[2], texcoord)
tex;
gpp::vertex_position_out position;
} vertex_output;
struct fragment_input
{
gpp_attribute(0, texcoord, float[2], texcoord)
tex;
} fragment_input;
struct fragment_output
{
gpp_attribute(0, fragColor, float[4], color)
col;
} fragment_output;
// Define the ubos, samplers, etc.
struct bindings
{
struct custom_ubo
{
halp_meta(name, "custom");
halp_flags(std140, ubo);
static constexpr int binding() { return 0; }
gpp::uniform<"foo", float[2]> pad;
gpp::uniform<"bar", float> slider;
} ubo;
gpp::sampler<"tex", 1> texture_input;
} bindings;
} lay{};
using bindings = decltype(layout::bindings);
using uniforms = decltype(bindings::ubo);
struct
{
// If samplers & buffers are referenced here the GPU side of things
// will be automatically allocated as they are expect to come from "outside"
gpp::uniform_control_port<halp::hslider_f32<"Alpha">, &uniforms::slider> bright;
// It's also possible to have purely CPU-side controls to manage e.g. texture sizes, etc...
halp::hslider_f32<"Other"> other;
} inputs;
struct
{
gpp::color_attachment_port<"Main out", &layout::fragment_output::col> fragColor;
} outputs;
std::string_view vertex()
{
return R"_(
void main()
{
texcoord = v_texcoord;
gl_Position = vec4(v_position.x / 3., v_position.y / 3, 0.0, 1.);
}
)_";
}
std::string_view fragment()
{
return R"_(
void main()
{
fragColor = vec4(texture(tex, texcoord.xy * foo).rgb, bar) ;
}
)_";
}
gpp::co_update update()
{
// In this example we test the automatic UBO filling with the inputs declared above.
// Here the surrounding environment makes sure that the UBO already has a handle
auto ubo = co_yield gpp::get_ubo_handle{.binding = lay.bindings.ubo.binding()};
// Upload some data into it, using an input (non-uniform) of our node
using namespace std;
float xy[2] = {cos(inputs.other), sin(inputs.other)};
co_yield gpp::dynamic_ubo_upload{
.handle = ubo, .offset = 0, .size = sizeof(xy), .data = &xy};
// The sampler is not used by the inputs block, so we have to allocate it ourselves
int sz = 16 * 16 * 4;
if(!tex_handle)
{
this->tex_handle = co_yield gpp::texture_allocation{
.binding = lay.bindings.texture_input.binding(), .width = 16, .height = 16};
}
// And upload some data
tex.resize(sz);
for(int i = 0; i < sz; i++)
tex[i] = rand();
co_yield gpp::texture_upload{
.handle = tex_handle, .offset = 0, .size = sz, .data = tex.data()};
}
gpp::co_release release()
{
if(tex_handle)
{
co_yield gpp::texture_release{.handle = tex_handle};
tex_handle = nullptr;
}
}
private:
std::vector<uint8_t> tex;
gpp::texture_handle tex_handle{};
};
}