#version 450 layout(location = 0) in vec2 inPosition; layout(location = 1) in vec4 inColor; layout(location = 2) in vec2 inTexCoord; layout(location = 0) out vec4 fragColor; layout(location = 1) out vec2 fragTexCoord; // Use std140 layout and separate floats instead of vec2 to avoid alignment issues layout(binding = 0, std140) uniform UniformBufferObject { float time; // offset 0 float resX; // offset 4 float resY; // offset 8 float rotation; // offset 12 float wavePhase; // offset 16 float padding1; // offset 20 float padding2; // offset 24 } ubo; void main() { // Input position is in pixel coordinates (0,0 = top-left) vec2 pos = inPosition; // Convert to NDC: (0, 0) to (width, height) -> (-1, -1) to (1, 1) // Note: Vulkan NDC has Y pointing down, but we want traditional Y-up float ndcX = (pos.x / ubo.resX) * 2.0 - 1.0; float ndcY = (pos.y / ubo.resY) * 2.0 - 1.0; // NO Y-flip needed here - text coordinates are already in screen space // where (0,0) is top-left, which matches Vulkan's framebuffer coordinates gl_Position = vec4(ndcX, ndcY, 0.0, 1.0); fragColor = inColor; fragTexCoord = inTexCoord; }