Ok but what if: WGPU renderer?

This commit is contained in:
Tulpen 2023-06-26 20:46:11 +02:00
parent a5380c4417
commit 1cca66b66f
No known key found for this signature in database
GPG key ID: 12294D73B907E784
10 changed files with 518 additions and 13 deletions

0
.gitmodules vendored Normal file
View file

View file

@ -5,7 +5,7 @@ namespace hibis {
// - Image
/// RGBA value struct. Value is between 0 and 255
struct Color {
unsigned char r, g, b, a;
double r, g, b, a;
};
// - 2D

159
external/glfw3webgpu/glfw3webgpu.c vendored Normal file
View file

@ -0,0 +1,159 @@
/**
* This is an extension of GLFW for WebGPU, abstracting away the details of
* OS-specific operations.
*
* This file is part of the "Learn WebGPU for C++" book.
* https://eliemichel.github.io/LearnWebGPU
*
* Most of this code comes from the wgpu-native triangle example:
* https://github.com/gfx-rs/wgpu-native/blob/master/examples/triangle/main.c
*
* MIT License
* Copyright (c) 2022-2023 Elie Michel and the wgpu-native authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "glfw3webgpu.h"
#include <webgpu/webgpu.h>
#define WGPU_TARGET_MACOS 1
#define WGPU_TARGET_LINUX_X11 2
#define WGPU_TARGET_WINDOWS 3
#define WGPU_TARGET_LINUX_WAYLAND 4
#if defined(_WIN32)
#define WGPU_TARGET WGPU_TARGET_WINDOWS
#elif defined(__APPLE__)
#define WGPU_TARGET WGPU_TARGET_MACOS
#else
#define WGPU_TARGET WGPU_TARGET_LINUX_X11
#endif
#if WGPU_TARGET == WGPU_TARGET_MACOS
#include <Foundation/Foundation.h>
#include <QuartzCore/CAMetalLayer.h>
#endif
#include <GLFW/glfw3.h>
#if WGPU_TARGET == WGPU_TARGET_MACOS
#define GLFW_EXPOSE_NATIVE_COCOA
#elif WGPU_TARGET == WGPU_TARGET_LINUX_X11
#define GLFW_EXPOSE_NATIVE_X11
#elif WGPU_TARGET == WGPU_TARGET_LINUX_WAYLAND
#define GLFW_EXPOSE_NATIVE_WAYLAND
#elif WGPU_TARGET == WGPU_TARGET_WINDOWS
#define GLFW_EXPOSE_NATIVE_WIN32
#endif
#include <GLFW/glfw3native.h>
WGPUSurface glfwGetWGPUSurface(WGPUInstance instance, GLFWwindow* window) {
#if WGPU_TARGET == WGPU_TARGET_MACOS
{
id metal_layer = NULL;
NSWindow* ns_window = glfwGetCocoaWindow(window);
[ns_window.contentView setWantsLayer : YES] ;
metal_layer = [CAMetalLayer layer];
[ns_window.contentView setLayer : metal_layer] ;
return wgpuInstanceCreateSurface(
instance,
&(WGPUSurfaceDescriptor){
.label = NULL,
.nextInChain =
(const WGPUChainedStruct*)&(
WGPUSurfaceDescriptorFromMetalLayer) {
.chain =
(WGPUChainedStruct){
.next = NULL,
.sType = WGPUSType_SurfaceDescriptorFromMetalLayer,
},
.layer = metal_layer,
},
});
}
#elif WGPU_TARGET == WGPU_TARGET_LINUX_X11
{
Display* x11_display = glfwGetX11Display();
Window x11_window = glfwGetX11Window(window);
return wgpuInstanceCreateSurface(
instance,
&(WGPUSurfaceDescriptor){
.label = NULL,
.nextInChain =
(const WGPUChainedStruct*)&(
WGPUSurfaceDescriptorFromXlibWindow) {
.chain =
(WGPUChainedStruct){
.next = NULL,
.sType = WGPUSType_SurfaceDescriptorFromXlibWindow,
},
.display = x11_display,
.window = x11_window,
},
});
}
#elif WGPU_TARGET == WGPU_TARGET_LINUX_WAYLAND
{
struct wl_display* wayland_display = glfwGetWaylandDisplay();
struct wl_surface* wayland_surface = glfwGetWaylandWindow(window);
return wgpuInstanceCreateSurface(
instance,
&(WGPUSurfaceDescriptor){
.label = NULL,
.nextInChain =
(const WGPUChainedStruct*)&(
WGPUSurfaceDescriptorFromWaylandSurface) {
.chain =
(WGPUChainedStruct){
.next = NULL,
.sType =
WGPUSType_SurfaceDescriptorFromWaylandSurface,
},
.display = wayland_display,
.surface = wayland_surface,
},
});
}
#elif WGPU_TARGET == WGPU_TARGET_WINDOWS
{
HWND hwnd = glfwGetWin32Window(window);
HINSTANCE hinstance = GetModuleHandle(NULL);
return wgpuInstanceCreateSurface(
instance,
&(WGPUSurfaceDescriptor){
.label = NULL,
.nextInChain =
(const WGPUChainedStruct*)&(
WGPUSurfaceDescriptorFromWindowsHWND) {
.chain =
(WGPUChainedStruct){
.next = NULL,
.sType = WGPUSType_SurfaceDescriptorFromWindowsHWND,
},
.hinstance = hinstance,
.hwnd = hwnd,
},
});
}
#else
#error "Unsupported WGPU_TARGET"
#endif
}

49
external/glfw3webgpu/glfw3webgpu.h vendored Normal file
View file

@ -0,0 +1,49 @@
/**
* This is an extension of GLFW for WebGPU, abstracting away the details of
* OS-specific operations.
*
* This file is part of the "Learn WebGPU for C++" book.
* https://eliemichel.github.io/LearnWebGPU
*
* MIT License
* Copyright (c) 2022-2023 Elie Michel and the wgpu-native authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef _glfw3_webgpu_h_
#define _glfw3_webgpu_h_
#include <webgpu/webgpu.h>
#include <GLFW/glfw3.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* Get a WGPUSurface from a GLFW window.
*/
WGPUSurface glfwGetWGPUSurface(WGPUInstance instance, GLFWwindow* window);
#ifdef __cplusplus
}
#endif
#endif // _glfw3_webgpu_h_

View file

@ -1,6 +1,10 @@
project('hibis', 'cpp', version: '0.0.0' + (not get_option('buildtype').startswith('release') ? '-' + run_command('git', 'rev-parse', '--short', 'HEAD', check: true).stdout().strip() : ''),
project('hibis', 'cpp', 'c', version: '0.0.0' + (not get_option('buildtype').startswith('release') ? '-' + run_command('git', 'rev-parse', '--short', 'HEAD', check: true).stdout().strip() : ''),
license: 'LGPL-3.0-only', meson_version: '>=0.60.3', default_options: ['cpp_std=c++17'])
# Imports
cmake = import('cmake')
# Configure data
confdata = configuration_data()
confdata.set('version', meson.project_version())
@ -16,6 +20,7 @@ libhibis_src_renderer = files('core/renderer/renderer.cpp')
libhibis_src_resources = files('core/resources/texture.cpp', 'core/resources/font.cpp', 'core/resources/shader.cpp')
libhibis_src = [libhibis_src_core, libhibis_src_renderer, libhibis_src_resources]
libhibis_rwgpu_src = files('renderer/rwgpu/rwgpu.cpp')
libhibis_rglcore_src = files('renderer/rglcore/rglcore.cpp')
libhibis_test_src = files('test/app.cpp')
@ -24,9 +29,14 @@ libgl = dependency('gl')
libglfw = dependency('glfw3')
libglew = dependency('GLEW')
libfmt = dependency('fmt')
libwgpu = meson.get_compiler('cpp').find_library('wgpu_native')
libwgpu_glfw = static_library('glfw3webgpu', 'external/glfw3webgpu/glfw3webgpu.c', dependencies: [libglfw])
libfreetype2 = dependency('freetype2')
# Compile
libhibis = library('hibis', libhibis_src, include_directories: include_dirs, dependencies: [libfreetype2])
libhibis_rglcore = library('hibis_rglcore', libhibis_rglcore_src, include_directories: [include_dirs, './core'], link_with: libhibis, dependencies: [libfreetype2, libgl, libglfw, libglew, libfmt])
hibistest = executable('hibistest.exec', libhibis_test_src, include_directories: [include_dirs, './core', './renderer/rglcore'], link_with: [libhibis, libhibis_rglcore], dependencies: [libfreetype2, libgl, libglfw, libglew, libfmt])
libhibis_rwgpu = library('hibis_rwgpu', libhibis_rwgpu_src, include_directories: [include_dirs, './core'], link_with: [libhibis, libwgpu_glfw], dependencies: [libfreetype2, libglfw, libfmt, libwgpu])
hibistest = executable('hibistest.exec', libhibis_test_src, include_directories: [include_dirs, './core', './renderer/rwgpu'], link_with: [libhibis, libhibis_rwgpu], dependencies: [libfreetype2, libglfw, libfmt, libwgpu])

View file

@ -78,8 +78,6 @@ namespace hibis::rglcore {
glUseProgram(shader->mShaderProgram);
glBindVertexArray(shader->mShaderVAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(0);
}
void RGLCore::stopUsingShaders() {

232
renderer/rwgpu/rwgpu.cpp Normal file
View file

@ -0,0 +1,232 @@
#define WEBGPU_CPP_IMPLEMENTATION
#include <fmt/format.h>
#include <sstream>
#include <webgpu/webgpu.hpp>
#include <glfw3webgpu/glfw3webgpu.h>
#include "rwgpu.hpp"
namespace hibis::rwgpu {
RWGPU::RWGPU(std::string title, IntVec2 size, LoggerCallback callback) : Renderer(callback) {
wgpu::InstanceDescriptor instanceDescriptor = {};
wgpu::RequestAdapterOptions reqAdaptOpts = {};
wgpu::DeviceDescriptor deviceDescriptor = {};
deviceDescriptor.nextInChain = nullptr;
deviceDescriptor.label = "My Device"; // anything works here, that's your call
deviceDescriptor.requiredFeaturesCount = 0; // we do not require any specific feature
deviceDescriptor.requiredLimits = nullptr; // we do not require any specific limit
deviceDescriptor.defaultQueue.nextInChain = nullptr;
deviceDescriptor.defaultQueue.label = "The default queue";
mWebGPUInstance = wgpu::createInstance(instanceDescriptor);
mWebGPUAdapter = requestAdapter(&reqAdaptOpts);
mWebGPUDevice = requestDevice(&deviceDescriptor);
mWebGPUQueue = mWebGPUDevice.getQueue();
if (!mWebGPUInstance) {
mLogger(Fatal, "Could not initialize WebGPU!");
exit(1);
} else {
const void * address = static_cast<const void*>(mWebGPUInstance);
std::stringstream pointerAsStream;
pointerAsStream << address;
mLogger(Information, fmt::format("WGPU Instance Addr: {}", pointerAsStream.str()));
}
glfwInit();
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
mWindow = glfwCreateWindow(size.x, size.y, title.c_str(), NULL, NULL);
mWebGPUSurface = glfwGetWGPUSurface(mWebGPUInstance, mWindow);
size_t featureCount = wgpuAdapterEnumerateFeatures(mWebGPUAdapter, nullptr);
// Allocate memory (could be a new, or a malloc() if this were a C program)
mWebGPUFeatures.resize(featureCount);
// Call the function a second time, with a non-null return address
wgpuAdapterEnumerateFeatures(mWebGPUAdapter, mWebGPUFeatures.data());
mLogger(Information, "Adapter features: ");
for (wgpu::FeatureName f : mWebGPUFeatures) {
mLogger(Information, fmt::format("WebGPU Feature: {}", f));
}
auto onDeviceError = [](WGPUErrorType type, char const* message, void* /* pUserData */) {
std::cout << "Uncaptured device error: type " << type;
if (message) std::cout << " (" << message << ")";
std::cout << std::endl;
};
wgpuDeviceSetUncapturedErrorCallback(mWebGPUDevice, onDeviceError, nullptr /* pUserData */);
// Not implemented, causes a panic
// auto onQueueWorkDone = [](WGPUQueueWorkDoneStatus status, void* /* pUserData */) {
// std::cout << "Queued work finished with status: " << status << std::endl;
// };
// wgpuQueueOnSubmittedWorkDone(mWebGPUQueue, onQueueWorkDone, nullptr /* pUserData */);
WGPUSwapChainDescriptor swapChainDesc = {};
swapChainDesc.nextInChain = nullptr;
swapChainDesc.width = size.x;
swapChainDesc.height = size.y;
WGPUTextureFormat swapChainFormat = wgpuSurfaceGetPreferredFormat(mWebGPUSurface, mWebGPUAdapter);
swapChainDesc.format = swapChainFormat;
swapChainDesc.usage = WGPUTextureUsage_RenderAttachment;
swapChainDesc.presentMode = WGPUPresentMode_Fifo;
mWebGPUSwapChain = wgpuDeviceCreateSwapChain(mWebGPUDevice, mWebGPUSurface, &swapChainDesc);
}
RWGPU::~RWGPU() {
glfwDestroyWindow(mWindow);
mWebGPUSwapChain.drop();
mWebGPUSurface.drop();
mWebGPUDevice.drop();
mWebGPUAdapter.drop();
mWebGPUInstance.drop();
}
WGPUAdapter RWGPU::requestAdapter(WGPURequestAdapterOptions const * options) {
// A simple structure holding the local information shared with the
// onAdapterRequestEnded callback.
struct UserData {
WGPUAdapter adapter = nullptr;
bool requestEnded = false;
};
UserData userData;
// Callback called by wgpuInstanceRequestAdapter when the request returns
// This is a C++ lambda function, but could be any function defined in the
// global scope. It must be non-capturing (the brackets [] are empty) so
// that it behaves like a regular C function pointer, which is what
// wgpuInstanceRequestAdapter expects (WebGPU being a C API). The workaround
// is to convey what we want to capture through the pUserData pointer,
// provided as the last argument of wgpuInstanceRequestAdapter and received
// by the callback as its last argument.
auto onAdapterRequestEnded = [](WGPURequestAdapterStatus status, WGPUAdapter adapter, char const * message, void * pUserData) {
UserData& userData = *reinterpret_cast<UserData*>(pUserData);
if (status == WGPURequestAdapterStatus_Success) {
userData.adapter = adapter;
} else {
std::cout << "Could not get WebGPU adapter: " << message << std::endl;
}
userData.requestEnded = true;
};
// Call to the WebGPU request adapter procedure
wgpuInstanceRequestAdapter(
mWebGPUInstance /* equivalent of navigator.gpu */,
options,
onAdapterRequestEnded,
(void*)&userData
);
// In theory we should wait until onAdapterReady has been called, which
// could take some time (what the 'await' keyword does in the JavaScript
// code). In practice, we know that when the wgpuInstanceRequestAdapter()
// function returns its callback has been called.
assert(userData.requestEnded);
return userData.adapter;
}
WGPUDevice RWGPU::requestDevice(WGPUDeviceDescriptor const * descriptor) {
struct UserData {
WGPUDevice device = nullptr;
bool requestEnded = false;
};
UserData userData;
auto onDeviceRequestEnded = [](WGPURequestDeviceStatus status, WGPUDevice device, char const * message, void * pUserData) {
UserData& userData = *reinterpret_cast<UserData*>(pUserData);
if (status == WGPURequestDeviceStatus_Success) {
userData.device = device;
} else {
std::cout << "Could not get WebGPU device: " << message << std::endl;
}
userData.requestEnded = true;
};
wgpuAdapterRequestDevice(
mWebGPUAdapter,
descriptor,
onDeviceRequestEnded,
(void*)&userData
);
assert(userData.requestEnded);
return userData.device;
}
void RWGPU::clearScreen(Color col) {
mCurrentClearColor = {col.r, col.g, col.b, col.a};
}
void RWGPU::renderCurrent() {
wgpu::TextureView nextTexture = mWebGPUSwapChain.getCurrentTextureView();
if (!nextTexture) {
mLogger(Error, "Couldn't create next texture");
return;
}
WGPUCommandEncoderDescriptor encoderDesc = {};
encoderDesc.nextInChain = nullptr;
encoderDesc.label = "HibisDrawCommandEncoder";
WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(mWebGPUDevice, &encoderDesc);
WGPURenderPassDescriptor renderPassDesc = {};
WGPURenderPassColorAttachment renderPassColorAttachment = {};
renderPassDesc.colorAttachmentCount = 1;
renderPassDesc.colorAttachments = &renderPassColorAttachment;
renderPassColorAttachment.view = nextTexture;
renderPassColorAttachment.resolveTarget = nullptr;
renderPassColorAttachment.loadOp = WGPULoadOp_Clear;
renderPassColorAttachment.storeOp = WGPUStoreOp_Store;
renderPassColorAttachment.clearValue = mCurrentClearColor;
renderPassDesc.depthStencilAttachment = nullptr;
renderPassDesc.timestampWriteCount = 0;
renderPassDesc.timestampWrites = nullptr;
renderPassDesc.nextInChain = nullptr;
WGPURenderPassEncoder renderPass = wgpuCommandEncoderBeginRenderPass(encoder, &renderPassDesc);
wgpuRenderPassEncoderEnd(renderPass);
WGPUCommandBufferDescriptor cmdBufferDescriptor = {};
cmdBufferDescriptor.nextInChain = nullptr;
cmdBufferDescriptor.label = "Command buffer";
WGPUCommandBuffer command = wgpuCommandEncoderFinish(encoder, &cmdBufferDescriptor);
wgpuQueueSubmit(mWebGPUQueue, 1, &command);
mWebGPUSwapChain.present();
nextTexture.drop();
}
void RWGPU::drawText(Resource* resource, std::string text, IntVec2 pos, Color color) {}
void RWGPU::drawTexture(Texture* resource, IntRect size) {}
void RWGPU::useShader(Shader* shader, Point2D points[3]) {}
void RWGPU::stopUsingShaders() {}
void RWGPU::preDraw() {}
void RWGPU::postDraw() {}
void RWGPU::update() {
mKeepOpen = !glfwWindowShouldClose(mWindow);
glfwPollEvents();
}
void RWGPU::compileShader(Shader* shader) {}
void RWGPU::toggleWireframe() {}
void RWGPU::setWindowTitle(std::string title) {}
}

55
renderer/rwgpu/rwgpu.hpp Normal file
View file

@ -0,0 +1,55 @@
#pragma once
#include <vector>
#include <webgpu/webgpu.hpp>
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
#include <renderer/renderer.hpp>
namespace hibis::rwgpu {
class RWGPU : public Renderer {
public:
RWGPU(std::string title, IntVec2 size, LoggerCallback callback);
~RWGPU();
// Draw
void clearScreen(Color col = Color {0, 0, 0, 255}) override;
void renderCurrent() override;
void drawText(Resource* resource, std::string text, IntVec2 pos, Color color) override;
void drawTexture(Texture* resource, IntRect size) override;
void useShader(Shader* shader, Point2D points[3]) override;
void stopUsingShaders() override;
// Pre and Post draw
void preDraw() override;
void postDraw() override;
// Update
void update() override;
// Util
void compileShader(Shader* shader) override;
void toggleWireframe() override;
void setWindowTitle(std::string title) override;
std::vector<WGPUFeatureName> mWebGPUFeatures;
private:
WGPUAdapter requestAdapter(WGPURequestAdapterOptions const * options);
WGPUDevice requestDevice(WGPUDeviceDescriptor const * descriptor);
GLFWwindow* mWindow;
wgpu::Instance mWebGPUInstance = nullptr;
wgpu::Adapter mWebGPUAdapter = nullptr;
wgpu::Device mWebGPUDevice = nullptr;
wgpu::Queue mWebGPUQueue = nullptr;
wgpu::SwapChain mWebGPUSwapChain = nullptr;
wgpu::Surface mWebGPUSurface = nullptr;
wgpu::Color mCurrentClearColor = {0.9, 0.1, 0.2, 1.0};
};
}

View file

@ -1,8 +1,7 @@
#include <logging/types.hpp>
#include <fmt/format.h>
#include <renderer/renderer.hpp>
#include <rglcore.hpp>
#include <resources/font.hpp>
#include <rwgpu.hpp>
#include <resources/font.hpp>
#include <string>
#include <iostream>
@ -18,7 +17,7 @@ WARNING("Please avoid using MSVC in C++ projects utilising std::chrono and std::
#endif
using namespace hibis;
using namespace hibis::rglcore;
using namespace hibis::rwgpu;
void logger(LoggingSeverity severity, std::string message) {
std::string sevString;
@ -45,8 +44,9 @@ void logger(LoggingSeverity severity, std::string message) {
int main() {
logger(Information, fmt::format("PWD: {}", std::getenv("PWD")));
RGLCore renderer = RGLCore("test", IntVec2 {800, 600}, &logger);
RWGPU renderer = RWGPU("test", IntVec2 {800, 600}, &logger);
Engine engine = Engine(&renderer, &logger);
/*
#ifdef _WIN32
Font font = Font(engine.mFreeTypeLibrary, "C:\\Windows\\Fonts\\Arial.ttf", 16);
@ -67,9 +67,10 @@ int main() {
uint f = 0;
Point2D points[3] = {{-0.5f, -0.5f}, {0.5f, -0.5f}, {0.0f, 0.5f}};
*/
logger(Information, "Started Hibis test app! BEHOLD: Colours.");
while (renderer.mKeepOpen) {
/*
// Colour changing background!
if ((red == 255 && increaseRed) || (red == 0 && !increaseRed)) {
increaseRed = !increaseRed;
@ -87,9 +88,10 @@ int main() {
else size -= 2;
font.setFontSize(size);
}
*/
// Clear screen then sleep for ~16ms
renderer.clearScreen(Color {red, 0, 0, 255});
//renderer.clearScreen(Color {red, 0, 0, 255});
//renderer.useShader(&shader, points);
//renderer.drawText(&font, "Testing Text", IntVec2 {0, 0}, Color {255, 255, 255, 255});
//renderer.drawTexture(&image, 1.0f, IntVec2 {10, 10});

View file

@ -2,5 +2,5 @@
layout (location = 0) in vec3 aPos;
void main() {
gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
gl_Position = vec4(aPos, 1.0);
}