3DScene, RasterRender: Vulkan scene renderer, typed ray hits, throwing loader #10

Merged
ilusha merged 3 commits from 3DScene-RasterRender into master 2026-10-29 12:42:29 +03:00
56 changed files with 1873 additions and 1905 deletions

View file

@ -1,8 +1,8 @@
project(3DEditor)
### ---------------------- Externals --------------------- ###
set(BINDINGS_INCLUDE ../Externals/glfw/include ../Externals)
set(BINDINGS_LIBS glfw Imgui OpenImageDenoise)
set(BINDINGS_INCLUDE ../Externals)
set(BINDINGS_LIBS OpenImageDenoise)
### ---------------------- Static Library --------------------- ###
file(GLOB SOURCES "./private/*.cpp" "./private/*/*.cpp")
@ -20,5 +20,4 @@ add_executable(3DEditorApp ${APP_SOURCES})
target_link_libraries(3DEditorApp ${PROJECT_NAME})
graphic_application_resources(3DEditorApp)
file(COPY "../RasterRender/rsc" DESTINATION "${CMAKE_BINARY_DIR}/${PROJECT_NAME}/")
file(COPY "../3DScene/rsc" DESTINATION "${CMAKE_BINARY_DIR}/${PROJECT_NAME}/")

View file

@ -8,14 +8,14 @@ using namespace tp;
class EditorGUI : public WidgetApplication {
public:
EditorGUI() {
EditorGUI() :
mEditor(application().gpu()) {
auto canvas = &this->canvas();
mGui = new EditorWidget(canvas, &mEditor);
mEditor.loadDefaults();
setRoot(mGui);
// mScene.mCamera.lookAtPoint({ 0, 0, 0 }, { 3, 3, 2 }, { 0, 0, 1 });
}
~EditorGUI() override { delete mGui; }

View file

@ -1,140 +1,74 @@
#include "Editor.hpp"
#include "GraphicsApi.hpp"
using namespace tp;
Editor::Editor() {
{ // create path tracer gpu texture
glGenTextures(1, &mPathRenderTexture);
glBindTexture(GL_TEXTURE_2D, mPathRenderTexture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, 100, 100, 0, GL_RGBA, GL_FLOAT, nullptr);
glBindTexture(GL_TEXTURE_2D, 0);
namespace {
uint32_t packColor(const RGBA& color) {
const auto byte = [](halnf value) { return (uint32_t) (clamp(value, 0.f, 1.f) * 255.f + 0.5f); };
return byte(color.r) | (byte(color.g) << 8) | (byte(color.b) << 16) | (byte(color.a) << 24);
}
}
void Editor::renderViewport() {
switch (mRenderType) {
case RenderType::RASTER:
mRasterRenderer.beginRender(mScene, mScene.mRenderSettings.size);
mRasterRenderer.renderDefault(mScene);
if (mActive) mRasterRenderer.renderOutline(mScene.mCamera, *mActive);
mRasterRenderer.endRender();
break;
case RenderType::PATH_TRACER:
{
// TODO : include in render viewport method
// renderPathFrame();
break;
}
default:
break;
}
}
uint4 Editor::getViewportTexID() {
switch (mRenderType) {
case RenderType::PATH_TRACER:
return mPathRenderTexture;
case RenderType::RASTER:
return mRasterRenderer.getRenderBufferID();
default:
return 0;
}
}
Editor::Editor(GpuDevice& gpu) :
mRasterRenderer(gpu) {}
void Editor::loadDefaults() {
mScene.load("rsc/scene/script.lua");
mScene = Scene::loadLua("rsc/scene/script.lua");
mResetCamera = mScene.mCamera;
}
void Editor::setRenderType(RenderType type) { mRenderType = type; }
void Editor::setViewportSize(const Vec2F& size) {
if (size.x <= 0 || size.y <= 0) return;
// TODO remove
// mScene.mCamera.rotate(0.01f, 0.0);
mScene.mRenderSettings.size = size;
mRasterRenderer.getRenderBuffer()->resize(size);
if (size.x < 1 || size.y < 1) {
return;
}
mScene.mRenderSettings.size = { (ualni) size.x, (ualni) size.y };
mRasterRenderer.target().resize({ (uint4) size.x, (uint4) size.y });
mScene.mCamera.setRatio(size.y / size.x);
}
Editor::~Editor() {
glDeleteTextures(1, &mPathRenderTexture);
}
void Editor::renderRaster() { mRasterRenderer.render(mScene, getActiveObject()); }
void Editor::renderPathFrame() {
mScene.updateCache();
mPathRenderer.render(mScene, mPathTracerBuffers, mScene.mRenderSettings);
sendBuffersToGPU();
}
void Editor::sendBuffersToGPU() {
glBindTexture(GL_TEXTURE_2D, mPathRenderTexture);
const auto size = mPathTracerBuffers.color.size();
glTexImage2D(
GL_TEXTURE_2D,
0,
GL_RGBA32F,
(GLsizei) size.x,
(GLsizei) size.y,
0,
GL_RGBA,
GL_FLOAT,
mPathTracerBuffers.color.getBuff()
);
glBindTexture(GL_TEXTURE_2D, 0);
convertPathImage();
}
void Editor::denoisePathRenderBuffers() {
denoise();
sendBuffersToGPU();
convertPathImage();
}
void Editor::setRenderType(Editor::RenderType type) {
mRenderType = type;
// Note: rows keep the tracer order, which matches the raster target layout.
void Editor::convertPathImage() {
const auto size = mPathTracerBuffers.color.size();
mPathImageSize = { (uint4) size.x, (uint4) size.y };
mPathImage.resize((size_t) size.x * size.y);
for (Index y = 0; y < size.y; y++) {
for (Index x = 0; x < size.x; x++) {
mPathImage[y * size.x + x] = packColor(mPathTracerBuffers.color.get({ x, y }));
}
}
}
void Editor::navigationOrbit(const Vec2F& delta) {
mScene.mCamera.rotate(delta.x, delta.y);
}
void Editor::navigationOrbit(const Vec2F& delta) { mScene.mCamera.rotate(delta.x, delta.y); }
void Editor::navigationPan(const Vec2F& pos, const Vec2F& prevPos) {
mScene.mCamera.move(pos, prevPos);
}
void Editor::navigationPan(const Vec2F& pos, const Vec2F& prevPos) { mScene.mCamera.move(pos, prevPos); }
void Editor::navigationZoom(halnf factor) {
mScene.mCamera.zoom(factor);
}
void Editor::navigationZoom(halnf factor) { mScene.mCamera.zoom(factor); }
void Editor::navigationReset() {
mScene.mCamera = mResetCamera;
// mScene.mCamera.lookAtPoint({ 0, 0, 0 }, { 1, 5, 1 }, { 0, 0, 1 });
}
void Editor::navigationReset() { mScene.mCamera = mResetCamera; }
void Editor::selectObject(const Vec2F& screenPos) {
mScene.updateCache();
auto dir = (mScene.mCamera.project(screenPos) - mScene.mCamera.getPos()).normalize();
Ray ray = Ray( dir, mScene.mCamera.getPos() );
auto rayCastData = mScene.castRay(ray, 1000);
mActive = rayCastData.obj;
const auto dir = (mScene.mCamera.project(screenPos) - mScene.mCamera.getPos()).normalize();
const auto hit = mScene.castRay(Ray(dir, mScene.mCamera.getPos()), 1000);
mActiveId = hit ? hit->object->id() : 0;
}
Object* Editor::getActiveObject() { return mActive; }
Object* Editor::getActiveObject() { return mScene.findObject(mActiveId); }
Scene* Editor::getScene() { return &mScene; }
Scene* Editor::getScene() { return &mScene; }

View file

@ -3,25 +3,33 @@
#include "RayTracer.hpp"
#include "RasterRender.hpp"
#include <vector>
namespace tp {
class Editor {
public:
enum class RenderType { RASTER, PATH_TRACER };
public:
Editor();
~Editor();
explicit Editor(GpuDevice& gpu);
void loadDefaults();
uint4 getViewportTexID();
[[nodiscard]] RenderType renderType() const { return mRenderType; }
void setRenderType(RenderType type);
void setViewportSize(const Vec2F& size);
void renderViewport();
// Raster mode: renders into the RasterRender target whose registry id is rasterTargetId().
void renderRaster();
[[nodiscard]] uint32_t rasterTargetId() { return mRasterRenderer.target().registryId(); }
// Path tracer mode: the last traced image as RGBA8 rows in the raster target's row order.
void renderPathFrame();
void setRenderType(RenderType type);
void denoisePathRenderBuffers();
[[nodiscard]] Vec2<uint4> pathImageSize() const { return mPathImageSize; }
[[nodiscard]] const std::vector<uint32_t>& pathImage() const { return mPathImage; }
void navigationOrbit(const Vec2F& delta);
void navigationPan(const Vec2F& pos, const Vec2F& prevPos);
@ -34,7 +42,7 @@ namespace tp {
Scene* getScene();
private:
void sendBuffersToGPU();
void convertPathImage();
void denoise();
private:
@ -48,8 +56,10 @@ namespace tp {
RayTracer mPathRenderer;
RayTracer::OutputBuffers mPathTracerBuffers;
uint4 mPathRenderTexture = 0;
Vec2<uint4> mPathImageSize = { 0, 0 };
std::vector<uint32_t> mPathImage;
Object* mActive = nullptr;
// Note: 0 means nothing is selected.
uint8 mActiveId = 0;
};
}
}

View file

@ -8,36 +8,57 @@ namespace tp {
class ViewportWidget : public Widget {
public:
explicit ViewportWidget(Canvas* canvas, Editor* editor) {
mEditor = editor;
mImage = canvas->createImageFromRenderTarget(mEditor->getViewportTexID());
mCanvas = canvas;
ViewportWidget(Canvas& canvas, Editor& editor) :
mEditor(editor),
mCanvas(canvas),
mRasterImage(canvas.createImageFromRenderTarget(editor.rasterTargetId())) {}
~ViewportWidget() override {
mCanvas.deleteImage(mRasterImage);
mCanvas.deleteImage(mPathImage);
}
~ViewportWidget() override { mCanvas->deleteImage(mImage); }
void process(const InputView& events) override {
mEditor->setViewportSize(getAreaT().size);
}
void process(const InputView& events) override { mEditor.setViewportSize(getAreaT().size); }
void draw(Canvas& canvas) override {
mEditor->renderViewport();
canvas.updateImageFromRenderTarget(mImage, mEditor->getViewportTexID());
canvas.drawImage(getArea().relative(), mImage, PI);
if (mEditor.renderType() == Editor::RenderType::PATH_TRACER) {
syncPathImage(canvas);
canvas.drawImage(getArea().relative(), mPathImage, kImageAngle);
return;
}
mEditor.renderRaster();
// Note: resizing recreates the target images, so the Canvas image is pointed at them every frame.
canvas.updateImageFromRenderTarget(mRasterImage, mEditor.rasterTargetId());
canvas.drawImage(getArea().relative(), mRasterImage, kImageAngle);
}
public:
Editor* mEditor;
private:
// Note: both images store row 0 at the camera's bottom edge (y = -1); a half turn shows them upright.
static constexpr halnf kImageAngle = (halnf) PI;
Canvas* mCanvas = nullptr;
ImageHandle mImage;
void syncPathImage(Canvas& canvas) {
const auto size = mEditor.pathImageSize();
const auto* pixels = (const unsigned char*) mEditor.pathImage().data();
if (mPathImage.isValid() && size == mPathImageSize) {
canvas.updateImageRGBA(mPathImage, pixels);
return;
}
canvas.deleteImage(mPathImage);
mPathImage = size.x && size.y ? canvas.createImageFromMemoryRGBA((int) size.x, (int) size.y, pixels) : ImageHandle();
mPathImageSize = size;
}
Editor& mEditor;
Canvas& mCanvas;
ImageHandle mRasterImage;
ImageHandle mPathImage;
Vec2<uint4> mPathImageSize = { 0, 0 };
};
class EditorWidget : public DockWidget {
public:
EditorWidget(Canvas* canvas, Editor* editor) :
mViewport(canvas, editor) {
mViewport(*canvas, *editor) {
mEditor = editor;
mPanel.setText("Controls");

View file

@ -1,4 +1,3 @@
#include "Scene.hpp"
extern "C" {
@ -7,217 +6,133 @@ extern "C" {
}
#include <filesystem>
#include <utility>
#include <memory>
tp::Vec3F tp::Scene::getVec3(lua_State* state, const char* name) {
int stackSize = lua_gettop(state);
using namespace tp;
// Access the "pos" field and validate it
lua_getfield(state, -1, name);
if (!lua_istable(state, -1) || lua_rawlen(state, -1) != 3) {
throw IOError(std::string("Invalid vec3 named ") + name);
namespace {
using LuaState = std::unique_ptr<lua_State, decltype(&lua_close)>;
// Pops the value pushed just before it when the scope ends.
class PushedValue {
public:
explicit PushedValue(lua_State* state) :
mState(state) {}
~PushedValue() { lua_pop(mState, 1); }
PushedValue(const PushedValue&) = delete;
PushedValue& operator=(const PushedValue&) = delete;
private:
lua_State* mState;
};
PushedValue pushGlobal(lua_State* state, const char* name) {
lua_getglobal(state, name);
return PushedValue(state);
}
// Read the values from the table
float pos[3];
for (int i = 0; i < 3; i++) {
lua_rawgeti(state, -1, i + 1);
if (lua_isnumber(state, -1)) {
pos[i] = lua_tonumber(state, -1);
} else {
throw IOError("vec3 values must be numbers");
PushedValue pushField(lua_State* state, const char* name) {
lua_getfield(state, -1, name);
return PushedValue(state);
}
PushedValue pushElement(lua_State* state, lua_Integer index) {
lua_rawgeti(state, -1, index);
return PushedValue(state);
}
void requireTable(lua_State* state, const std::string& name) {
if (!lua_istable(state, -1)) {
throw SceneLoadError(name + " must be a table");
}
lua_pop(state, 1); // pop number
}
lua_pop(state, 1); // pop vec3
ASSERT(stackSize == lua_gettop(state))
return { pos[0], pos[1], pos[2] };
}
tp::Vec2F tp::Scene::getVec2(lua_State* state, const char* name) {
int stackSize = lua_gettop(state);
// Access the "pos" field and validate it
lua_getfield(state, -1, name);
if (!lua_istable(state, -1) || lua_rawlen(state, -1) != 2) {
throw IOError(std::string("Invalid vec2 named ") + name);
}
// Read the values from the table
float pos[2];
for (int i = 0; i < 2; i++) {
lua_rawgeti(state, -1, i + 1);
if (lua_isnumber(state, -1)) {
pos[i] = lua_tonumber(state, -1);
} else {
throw IOError("vec3 values must be numbers");
halnf readNumber(lua_State* state, const char* name) {
const auto value = pushField(state, name);
if (!lua_isnumber(state, -1)) {
throw SceneLoadError(std::string(name) + " must be a number");
}
lua_pop(state, 1); // pop number
}
lua_pop(state, 1); // pop vec3
ASSERT(stackSize == lua_gettop(state))
return { pos[0], pos[1] };
}
// Function to read a Lua table representing RenderSettings
int readRenderSettings(lua_State* L, tp::RenderSettings& settings) {
lua_getglobal(L, "RenderSettings");
if (!lua_istable(L, -1)) {
printf("RenderSettings is not a table.\n");
return 0; // Error
return (halnf) lua_tonumber(state, -1);
}
// Read depth field
lua_getfield(L, -1, "depth");
if (lua_isnumber(L, -1)) {
settings.depth = (int) lua_tonumber(L, -1);
} else {
printf("RenderSettings 'depth' field is missing or not a number.\n");
lua_pop(L, 1); // Pop the 'depth' field
return 0; // Error
}
lua_pop(L, 1); // Pop the 'depth' field
// Read spray field
lua_getfield(L, -1, "spray");
if (lua_isnumber(L, -1)) {
settings.spray = (int) lua_tonumber(L, -1);
} else {
printf("RenderSettings 'spray' field is missing or not a number.\n");
lua_pop(L, 1); // Pop the 'spray' field
return 0; // Error
}
lua_pop(L, 1); // Pop the 'spray' field
// Read depth field
lua_getfield(L, -1, "multisampling");
if (lua_isnumber(L, -1)) {
settings.multisampling = (int) lua_tonumber(L, -1);
} else {
printf("RenderSettings 'depth' field is missing or not a number.\n");
lua_pop(L, 1); // Pop the 'depth' field
return 0; // Error
}
lua_pop(L, 1); // Pop the 'depth' field
return 1; // Success
}
// Function to read a Lua table representing a light
int tp::Scene::readLight(lua_State* L, tp::PointLight* light) {
light->pos = getVec3(L, "pos");
lua_getfield(L, -1, "intensity"); // Get the "intensity" field from the light table
if (!lua_isnumber(L, -1)) {
printf("Light is missing the 'intensity' field or it's not a number.\n");
lua_pop(L, 1); // Pop the 'intensity' field
return 0; // Error
}
light->intensity = lua_tonumber(L, -1);
lua_pop(L, 1); // Pop the 'intensity' field
return 1; // Success
}
bool tp::Scene::loadLuaFormat(const std::string& scenePath) {
lua_State* L = luaL_newstate();
luaL_openlibs(L);
namespace fs = std::filesystem;
fs::path fullPath(scenePath);
// Extract the filename
std::string fileName = fullPath.filename().string();
// Remove the filename from the path
fs::path directoryPath = fullPath.remove_filename();
if (luaL_dofile(L, scenePath.c_str()) != 0) {
lua_close(L);
printf("Cant open scene script.\n");
return false;
}
lua_getglobal(L, "Meshes");
if (lua_isstring(L, -1)) {
std::string meshesPath = lua_tostring(L, -1);
directoryPath /= meshesPath;
if (!loadOBJFormat(directoryPath.string())) {
printf("No 'meshes' loaded - check ur .obj path and validate content of .obj .\n");
return false;
template <ualni tSize>
Vec<halnf, tSize> readVector(lua_State* state, const char* name) {
const auto value = pushField(state, name);
if (!lua_istable(state, -1) || lua_rawlen(state, -1) != tSize) {
throw SceneLoadError(std::string(name) + " must be a list of " + std::to_string(tSize) + " numbers");
}
} else {
printf("No 'meshes' path given.\n");
return false;
}
// --- camera
{
lua_getglobal(L, "Camera");
if (!lua_istable(L, -1)) {
printf("Camera is not a table.\n");
lua_close(L);
return false;
}
Vec3F camPos = getVec3(L, "pos");
Vec3F camTarget = getVec3(L, "target");
Vec3F camUp = getVec3(L, "up");
Vec2F camSize = getVec2(L, "size");
mRenderSettings.size = camSize;
mCamera.lookAtPoint(camTarget, camPos, camUp);
mCamera.setFOV(3.14 / 4);
mCamera.setFar(100);
mCamera.setRatio((tp::halnf) camSize.y / (tp::halnf) camSize.x);
lua_pop(L, 1);
}
// ---------- LIGHTS
{
lua_getglobal(L, "Lights");
if (!lua_istable(L, -1)) {
printf("Lights is not a table.\n");
lua_close(L);
return false; // Error
}
// Read and process each light in the "Lights" table
int numLights = lua_rawlen(L, -1); // Get the number of lights in the table
for (int i = 1; i <= numLights; i++) {
lua_rawgeti(L, -1, i); // Get the i-th element (light) from the table
if (lua_istable(L, -1)) {
tp::PointLight light;
if (!readLight(L, &light)) {
printf("Cant read lights data\n");
lua_close(L);
return false; // Error
}
mLights.append(light);
Vec<halnf, tSize> out;
for (ualni index = 0; index < tSize; index++) {
const auto element = pushElement(state, (lua_Integer) index + 1);
if (!lua_isnumber(state, -1)) {
throw SceneLoadError(std::string(name) + " must be a list of numbers");
}
lua_pop(L, 1); // Pop the i-th light table
out[index] = (halnf) lua_tonumber(state, -1);
}
return out;
}
void readCamera(lua_State* state, Scene& scene) {
const auto camera = pushGlobal(state, "Camera");
requireTable(state, "Camera");
const Vec2F size = readVector<2>(state, "size");
scene.mRenderSettings.size = { (ualni) size.x, (ualni) size.y };
scene.mCamera.lookAtPoint(readVector<3>(state, "target"), readVector<3>(state, "pos"), readVector<3>(state, "up"));
scene.mCamera.setFOV((halnf) PI / 4);
scene.mCamera.setFar(100);
scene.mCamera.setRatio(size.y / size.x);
}
void readLights(lua_State* state, Scene& scene) {
const auto lights = pushGlobal(state, "Lights");
requireTable(state, "Lights");
const auto count = (lua_Integer) lua_rawlen(state, -1);
for (lua_Integer index = 1; index <= count; index++) {
const auto light = pushElement(state, index);
requireTable(state, "Lights[" + std::to_string(index) + "]");
PointLight point;
point.pos = readVector<3>(state, "pos");
point.intensity = readNumber(state, "intensity");
scene.mLights.append(point);
}
}
// ----------- settings --------------
if (!readRenderSettings(L, mRenderSettings)) {
printf("Cant Read Render Settings");
lua_close(L);
return false; // Error
void readRenderSettings(lua_State* state, RenderSettings& settings) {
const auto table = pushGlobal(state, "RenderSettings");
requireTable(state, "RenderSettings");
settings.depth = (uhalni) readNumber(state, "depth");
settings.spray = (uhalni) readNumber(state, "spray");
settings.multisampling = (ualni) readNumber(state, "multisampling");
}
lua_close(L);
return true;
std::string readMeshesPath(lua_State* state) {
const auto meshes = pushGlobal(state, "Meshes");
if (!lua_isstring(state, -1)) {
throw SceneLoadError("Meshes must be a path string");
}
return lua_tostring(state, -1);
}
}
Scene Scene::loadLua(const std::string& scenePath) {
const LuaState state(luaL_newstate(), &lua_close);
luaL_openlibs(state.get());
if (luaL_dofile(state.get(), scenePath.c_str()) != LUA_OK) {
throw SceneLoadError("cannot run scene script " + scenePath + ": " + lua_tostring(state.get(), -1));
}
Scene scene;
// Note: the meshes path is relative to the script.
scene.loadOBJ((std::filesystem::path(scenePath).parent_path() / readMeshesPath(state.get())).string());
readCamera(state.get(), scene);
readLights(state.get(), scene);
readRenderSettings(state.get(), scene.mRenderSettings);
return scene;
}

View file

@ -1,86 +1,30 @@
#include "Scene.hpp"
extern "C" {
#include "lauxlib.h"
#include "lualib.h"
}
#include "obj/OBJ_Loader.h"
#include <filesystem>
using namespace tp;
bool tp::Scene::loadOBJFormat(const std::string& objetsPath) {
using namespace tp;
objl::Loader Loader;
if (!Loader.LoadFile(objetsPath)) {
std::cout << "Failed to Load File. May have failed to find it or it was not an .obj file.\n";
return false;
void Scene::loadOBJ(const std::string& objectsPath) {
objl::Loader loader;
if (!loader.LoadFile(objectsPath)) {
throw SceneLoadError("cannot read OBJ file " + objectsPath);
}
if (loader.LoadedMeshes.empty()) {
throw SceneLoadError("no meshes in " + objectsPath);
}
for (auto& curMesh : Loader.LoadedMeshes) {
mObjects.append(Object());
auto object = &mObjects.last();
for (auto& vertex : curMesh.Vertices) {
// printf("{ %f, %f, %f }, \n", vertex.Position.X, vertex.Position.Y, vertex.Position.Z);
object->mTopology.Points.append(Vec3F{ vertex.Position.X, vertex.Position.Y, vertex.Position.Z });
object->mTopology.Normals.append(Vec3F{ vertex.Normal.X, vertex.Normal.Y, vertex.Normal.Z });
for (const auto& mesh : loader.LoadedMeshes) {
Object object;
for (const auto& vertex : mesh.Vertices) {
object.mTopology.Points.append(Vec3F{ vertex.Position.X, vertex.Position.Y, vertex.Position.Z });
object.mTopology.Normals.append(Vec3F{ vertex.Normal.X, vertex.Normal.Y, vertex.Normal.Z });
}
for (int j = 0; j < curMesh.Indices.size(); j += 3) {
auto idx1 = (uhalni) curMesh.Indices[j];
auto idx2 = (uhalni) curMesh.Indices[j + 1];
auto idx3 = (uhalni) curMesh.Indices[j + 2];
// printf("{ %i, %i, %i },\n", idx1, idx2, idx3);
object->mTopology.Indexes.append(Vec3<uhalni>{ idx1, idx2, idx3 });
for (size_t index = 0; index + 2 < mesh.Indices.size(); index += 3) {
object.mTopology.Indexes.append(
Vec3<uhalni>{ mesh.Indices[index], mesh.Indices[index + 1], mesh.Indices[index + 2] }
);
}
if (object->mTopology.Normals.size() != object->mTopology.Points.size()) {
printf("Logic error loading normals\n");
}
object->mCache.Source = &object->mTopology;
object->mCache.updateCache();
object.updateCache();
addObject(object);
}
return mObjects.size();
}
const tp::RayCastData& tp::Scene::castRay(const Ray& ray, alnf farVal) {
auto& out = mRayCastData;
out.hit = false;
out.obj = nullptr;
farVal *= farVal;
for (auto obj : mObjects) {
for (auto trig : obj->mCache.TrigCaches) {
if (trig->castRay(ray)) {
auto dist = (TrigCache::getHitPos() - ray.pos).length2();
if (farVal > dist && dist > EPSILON) {
out.trig = &trig.data();
out.hitPos = TrigCache::getHitPos();
out.obj = &obj.data();
out.hit = true;
farVal = dist;
}
}
}
}
return mRayCastData;
}
void tp::Scene::updateCache() {
for (auto obj : mObjects) {
obj->mCache.updateCache();
}
}

View file

@ -1,14 +1,41 @@
#include "Scene.hpp"
bool tp::Scene::load(const std::string& scenePath) {
try {
auto res = loadLuaFormat(scenePath);
if (!res) throw IOError("Failed loading lua script");
} catch (const IOError& err) {
printf("Failed loading scene : %s\n", err.description.c_str());
return false;
}
using namespace tp;
return true;
}
Object& Scene::addObject(Object object) {
object.mId = mNextObjectId++;
return mObjects.append(object);
}
Object* Scene::findObject(uint8 id) {
for (auto object : mObjects) {
if (object->id() == id) {
return &object.data();
}
}
return nullptr;
}
std::optional<RayHit> Scene::castRay(const Ray& ray, halnf maxDistance) const {
std::optional<RayHit> closest;
for (ualni objectIndex = 0; objectIndex < mObjects.size(); objectIndex++) {
const Object& object = mObjects[objectIndex];
const auto& trigs = object.mCache.TrigCaches;
for (ualni trigIndex = 0; trigIndex < trigs.size(); trigIndex++) {
const TrigCache& trig = trigs[trigIndex];
const auto distance = trig.castRay(ray);
if (!distance || *distance >= maxDistance) {
continue;
}
maxDistance = *distance;
closest = RayHit{ &object, &trig, ray.pos + ray.dir * *distance, *distance };
}
}
return closest;
}
void Scene::updateCache() {
for (auto object : mObjects) {
object->updateCache();
}
}

View file

@ -1,14 +1,14 @@
#pragma once
#include "Topology.hpp"
#include "Buffer2D.hpp"
#include "Camera.hpp"
#include <optional>
#include <stdexcept>
#include <string>
struct lua_State;
namespace tp {
struct RenderSettings {
uhalni depth = 2;
uhalni spray = 1;
@ -16,26 +16,21 @@ namespace tp {
Vec2<ualni> size;
};
class GPUBuffers {
public:
GPUBuffers() = default;
virtual ~GPUBuffers() = default;
virtual void drawCall() = 0;
};
// A mesh placed in the scene. mId stays the same while the object lives, even when the scene storage moves it.
class Object {
public:
Object() = default;
[[nodiscard]] uint8 id() const { return mId; }
~Object() {
delete mGUPBuffers;
};
// Rebuilds the world-space cache after mTopology changed.
void updateCache() { mCache.update(mTopology); }
public:
Topology mTopology;
TopologyCache mCache;
GPUBuffers* mGUPBuffers = nullptr;
private:
friend class Scene;
uint8 mId = 0;
};
struct PointLight {
@ -44,47 +39,42 @@ namespace tp {
halnf intensity = 1.f;
};
struct RayCastData {
Object* obj = nullptr;
TrigCache* trig = nullptr;
Vec3F hitPos = { 0, 0, 0 };
bool hit = false;
bool inv = false;
struct RayHit {
const Object* object = nullptr;
const TrigCache* trig = nullptr;
Vec3F position;
halnf distance = 0;
};
// The scene description could not be read.
class SceneLoadError : public std::runtime_error {
public:
using std::runtime_error::runtime_error;
};
class Scene {
struct IOError : public std::exception {
explicit IOError(std::string in) :
description(std::move(in)) {}
std::string description;
};
public:
Scene() = default;
// Reads a Lua scene description and the OBJ meshes it names; throws SceneLoadError.
static Scene loadLua(const std::string& scenePath);
bool load(const std::string& scenePath);
// Appends every mesh of an OBJ file; throws SceneLoadError.
void loadOBJ(const std::string& objectsPath);
bool loadLuaFormat(const std::string& scenePath);
bool loadOBJFormat(const std::string& objectsPath);
Object& addObject(Object object);
[[nodiscard]] Object* findObject(uint8 id);
const RayCastData& castRay(const Ray& ray, alnf farVal);
// Closest hit within maxDistance; uses the caches built by updateCache().
[[nodiscard]] std::optional<RayHit> castRay(const Ray& ray, halnf maxDistance) const;
void updateCache();
private:
Vec3F getVec3(lua_State* state, const char* name);
Vec2F getVec2(lua_State* state, const char* name);
int readLight(lua_State* L, tp::PointLight* light);
public:
Buffer<Object> mObjects;
Buffer<PointLight> mLights;
Camera mCamera;
RenderSettings mRenderSettings;
RayCastData mRayCastData;
private:
uint8 mNextObjectId = 1;
};
}

View file

@ -3,30 +3,11 @@
using namespace tp;
Vec3F TrigCache::gHitPos;
TrigCache::TrigCache() :
mP1(0),
mP2(0),
mP3(0) {}
TrigCache::TrigCache(ualni v1, ualni v2, ualni v3) :
mP1(v1),
mP2(v2),
mP3(v3) {}
TrigCache::TrigCache(const TrigCache& in) {
mP1 = in.mP1;
mP2 = in.mP2;
mP3 = in.mP3;
mEdgeP1P2 = in.mEdgeP1P2;
mEdgeP1P3 = in.mEdgeP1P3;
mOrigin = in.mOrigin;
mNormal = in.mNormal;
}
void TrigCache::updateCache(const Buffer<Vec3F>& points) {
mOrigin = points[mP1];
mEdgeP1P2 = points[mP2] - points[mP1];
@ -34,75 +15,51 @@ void TrigCache::updateCache(const Buffer<Vec3F>& points) {
mNormal = (points[mP2] - points[mP1]).cross(points[mP3] - points[mP1]).unitV();
}
bool TrigCache::castRay(const Ray& ray) const {
static Vec3F h, s, q;
static halnf a, f, u, v;
static halnf t;
if (ray.dir.dot(mNormal) > 0 && 0) {
return false;
}
h = ray.dir.cross(mEdgeP1P3);
a = mEdgeP1P2.dot(h);
// Moller-Trumbore intersection.
std::optional<halnf> TrigCache::castRay(const Ray& ray) const {
const Vec3F h = ray.dir.cross(mEdgeP1P3);
const halnf a = (halnf) mEdgeP1P2.dot(h);
if (a > -EPSILON && a < EPSILON) {
return false;
return std::nullopt;
}
f = 1.f / a;
s = ray.pos - mOrigin;
u = f * s.dot(h);
if (u < 0.0 || u > 1.0) {
return false;
const halnf f = 1.f / a;
const Vec3F s = ray.pos - mOrigin;
const halnf u = f * (halnf) s.dot(h);
if (u < 0.f || u > 1.f) {
return std::nullopt;
}
q = s.cross(mEdgeP1P2);
v = f * ray.dir.dot(q);
const Vec3F q = s.cross(mEdgeP1P2);
const halnf v = f * (halnf) ray.dir.dot(q);
if (v < 0.f || u + v > 1.f) {
return false;
return std::nullopt;
}
t = f * mEdgeP1P3.dot(q);
if (t > EPSILON) {
gHitPos = ray.pos + ray.dir * t;
return true;
} else {
return false;
const halnf t = f * (halnf) mEdgeP1P3.dot(q);
if (t <= EPSILON) {
return std::nullopt;
}
return t;
}
const Vec3F& TrigCache::getHitPos() { return gHitPos; }
const Vec3F& TrigCache::getNormal() const { return mNormal; }
void TopologyCache::updateCache() {
TransformedNormals.clear();
TransformedPoints.clear();
TrigCaches.clear();
if (!Source) {
return;
}
TransformedPoints.reserve(Source->Points.size());
void TopologyCache::update(const Topology& source) {
TransformedPoints.reserve(source.Points.size());
for (auto idx : IterRange(TransformedPoints.size())) {
TransformedPoints[idx] = Source->Basis.transform(Source->Points[idx]);
TransformedPoints[idx] += Source->Origin;
TransformedPoints[idx] = source.Basis.transform(source.Points[idx]) + source.Origin;
}
TransformedNormals.reserve(Source->Normals.size());
TransformedNormals.reserve(source.Normals.size());
for (auto idx : IterRange(TransformedNormals.size())) {
TransformedNormals[idx] = Source->Basis.transform(Source->Normals[idx]);
TransformedNormals[idx] = source.Basis.transform(source.Normals[idx]);
}
TrigCaches.reserve(Source->Indexes.size());
TrigCaches.reserve(source.Indexes.size());
for (auto idx : IterRange(TrigCaches.size())) {
TrigCaches[idx].mP1 = Source->Indexes[idx].x;
TrigCaches[idx].mP2 = Source->Indexes[idx].y;
TrigCaches[idx].mP3 = Source->Indexes[idx].z;
const auto& index = source.Indexes[idx];
TrigCaches[idx] = TrigCache(index.x, index.y, index.z);
TrigCaches[idx].updateCache(TransformedPoints);
}
}

View file

@ -3,29 +3,31 @@
#include "Buffer.hpp"
#include "Camera.hpp"
#include <optional>
namespace tp {
// A triangle of a TopologyCache with the edges precomputed for ray intersection.
class TrigCache {
static Vec3F gHitPos;
public:
ualni mP1, mP2, mP3;
Vec3F mEdgeP1P2, mEdgeP1P3;
ualni mP1 = 0;
ualni mP2 = 0;
ualni mP3 = 0;
Vec3F mEdgeP1P2;
Vec3F mEdgeP1P3;
Vec3F mNormal;
Vec3F mOrigin;
public:
TrigCache();
TrigCache(const TrigCache& in);
TrigCache() = default;
TrigCache(ualni v1, ualni v2, ualni v3);
public:
static const Vec3F& getHitPos();
[[nodiscard]] const Vec3F& getNormal() const;
public:
void updateCache(const Buffer<Vec3F>& points);
[[nodiscard]] bool castRay(const Ray& ray) const;
// Distance along the ray to the hit point, if the ray hits the triangle.
[[nodiscard]] std::optional<halnf> castRay(const Ray& ray) const;
};
struct Topology {
@ -37,13 +39,12 @@ namespace tp {
Buffer<Vec3<uhalni>> Indexes;
};
// World-space copy of a Topology, rebuilt by update().
struct TopologyCache {
const Topology* Source = nullptr;
Buffer<Vec3F> TransformedPoints;
Buffer<Vec3F> TransformedNormals;
Buffer<TrigCache> TrigCaches;
void updateCache();
void update(const Topology& source);
};
}

View file

@ -1,15 +1,39 @@
project(RasterRender)
### ---------------------- Externals --------------------- ###
set(BINDINGS_INCLUDE ${GLEW_INCLUDE_DIR})
set(BINDINGS_LIBS ${GLEW_LIB})
find_package(Vulkan REQUIRED)
find_program(GLSLC glslc HINTS ${Vulkan_GLSLC_EXECUTABLE} REQUIRED)
### ---------------------- Shaders --------------------- ###
set(SHADER_GEN_DIR ${CMAKE_CURRENT_BINARY_DIR}/shaders_generated)
set(SHADER_INCS "")
foreach(SHADER object.vert depth.frag solid.frag)
set(SHADER_SRC ${CMAKE_CURRENT_SOURCE_DIR}/shaders/${SHADER})
set(SHADER_INC ${SHADER_GEN_DIR}/${SHADER}.inc)
add_custom_command(
OUTPUT ${SHADER_INC}
COMMAND ${CMAKE_COMMAND} -E make_directory ${SHADER_GEN_DIR}
COMMAND ${GLSLC} -O --target-env=vulkan1.0 -mfmt=c ${SHADER_SRC} -o ${SHADER_INC}
DEPENDS ${SHADER_SRC}
COMMENT "Compiling ${SHADER} to SPIR-V"
VERBATIM
)
list(APPEND SHADER_INCS ${SHADER_INC})
endforeach()
add_custom_target(RasterRenderShaders DEPENDS ${SHADER_INCS})
### ---------------------- Static Library --------------------- ###
file(GLOB SOURCES "./private/*.cpp" "./private/*/*.cpp")
file(GLOB HEADERS "./public/*.hpp" "./public/*/*.hpp")
file(GLOB SOURCES "./private/*.cpp")
add_library(${PROJECT_NAME} STATIC ${SOURCES})
add_dependencies(${PROJECT_NAME} RasterRenderShaders)
add_library(${PROJECT_NAME} STATIC ${SOURCES} ${HEADERS})
target_include_directories(${PROJECT_NAME} PUBLIC ./public/)
target_include_directories(${PROJECT_NAME} PRIVATE ./private/ ${SHADER_GEN_DIR})
target_link_libraries(${PROJECT_NAME} PUBLIC 3DScene GraphicApplicationGpu)
target_include_directories(${PROJECT_NAME} PUBLIC ./public/ ${BINDINGS_INCLUDE} ./ext/)
target_link_libraries(${PROJECT_NAME} PUBLIC Math Connection 3DScene)
target_link_libraries(${PROJECT_NAME} PUBLIC ${BINDINGS_LIBS})
### -------------------------- Tests -------------------------- ###
if (NOT ANDROID)
file(GLOB TEST_SOURCES "./tests/*.cpp")
add_executable(Test${PROJECT_NAME} ${TEST_SOURCES})
target_link_libraries(Test${PROJECT_NAME} ${PROJECT_NAME} UnitTest++)
add_test(NAME Test${PROJECT_NAME} COMMAND Test${PROJECT_NAME})
endif()

View file

@ -1,159 +0,0 @@
#include "FrameBuffer.hpp"
#include "GraphicsApi.hpp"
#include <stdio.h>
void glerr(GLenum type) {
printf("GL ERROR - %i\n", type);
}
#define AssertGL(x) \
{ \
x; \
GLenum __gle = glGetError(); \
if (__gle != GL_NO_ERROR) glerr(__gle); \
}
using namespace tp;
RenderBuffer::RenderBuffer(const Vec2F& size) :
mSize(size), mSamples(0)
{
mDrawBuffers[0] = { GL_COLOR_ATTACHMENT0 };
// --------- texture ---------
AssertGL(glGenTextures(1, &mTextureId));
AssertGL(glBindTexture(GL_TEXTURE_2D, mTextureId));
// Give an empty image to OpenGL ( the last "0" )
AssertGL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei) size.x, (GLsizei) size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)
);
// Poor filtering. Needed
AssertGL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
AssertGL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
// --------- depth ---------
AssertGL(glGenRenderbuffers(1, &mDepthBufferID));
AssertGL(glBindRenderbuffer(GL_RENDERBUFFER, mDepthBufferID));
AssertGL(glRenderbufferStorage(GL_RENDERBUFFER, GLW_CONTEXT_DEPTH_COMPONENT, (GLsizei) size.x, (GLsizei) size.y));
// ------------ framebuffer ------------
AssertGL(glGenFramebuffers(1, &mFrameBufferID));
AssertGL(glBindFramebuffer(GL_FRAMEBUFFER, mFrameBufferID));
AssertGL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, mDepthBufferID));
// Set "renderedTexture" as our colour attachement #0
AssertGL(glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, mTextureId, 0));
// Set the list of draw buffers.
AssertGL(glDrawBuffers(1, mDrawBuffers)); // "1" is the size of DrawBuffers
ASSERT(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
RenderBuffer::RenderBuffer(const Vec2F& size, tp::uint1 samples) :
mSize(size), mSamples(samples)
{
mDrawBuffers[0] = { GL_COLOR_ATTACHMENT0 };
// ------- texture ---------
AssertGL(glGenTextures(1, &mTextureId));
AssertGL(glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, mTextureId));
#ifdef ENV_OS_ANDROID
AssertGL(
glTexStorage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, samples, GL_RGBA, (GLsizei) size.x, (GLsizei) size.y, GL_TRUE)
);
#else
AssertGL(
glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, samples, GL_RGBA, (GLsizei) size.x, (GLsizei) size.y, GL_TRUE)
);
#endif
// !?
// AssertGL(glTexParameteri(GL_TEXTURE_2D_MULTISAMPLE, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
// AssertGL(glTexParameteri(GL_TEXTURE_2D_MULTISAMPLE, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
// ------- depth -------
AssertGL(glGenRenderbuffers(1, &mDepthBufferID));
AssertGL(glBindRenderbuffer(GL_RENDERBUFFER, mDepthBufferID));
AssertGL(glRenderbufferStorageMultisample(
GL_RENDERBUFFER, samples, GLW_CONTEXT_DEPTH_COMPONENT, (GLsizei) size.x, (GLsizei) size.y
));
// ------- fbuff -------
AssertGL(glGenFramebuffers(1, &mFrameBufferID));
AssertGL(glBindFramebuffer(GL_FRAMEBUFFER, mFrameBufferID));
AssertGL(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, mTextureId, 0));
AssertGL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, mDepthBufferID));
AssertGL(glDrawBuffers(1, mDrawBuffers));
ASSERT(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
uint4 RenderBuffer::buffId() const { return mFrameBufferID; }
void RenderBuffer::beginDraw() {
AssertGL(glBindFramebuffer(GL_FRAMEBUFFER, mFrameBufferID));
setViewport({ 0.f, 0.f, mSize.x, mSize.y });
}
void RenderBuffer::setViewport(const RectF& viewport) {
AssertGL(glViewport((GLsizei) viewport.x, (GLsizei) viewport.y, (GLsizei) viewport.z, (GLsizei) viewport.w));
}
void RenderBuffer::clear() {
AssertGL(glClearColor(mClearCol.r, mClearCol.g, mClearCol.b, mClearCol.a));
AssertGL(glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT));
}
void RenderBuffer::endDraw() {
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glClearColor(0, 0, 0, 0);
glUseProgram(0);
}
RenderBuffer::~RenderBuffer() {
glDeleteFramebuffers(1, &mFrameBufferID);
glDeleteTextures(1, &mTextureId);
glDeleteRenderbuffers(1, &mDepthBufferID);
}
uint4 RenderBuffer::texId() const { return mTextureId; }
const Vec2F& RenderBuffer::getSize() const { return mSize; }
void RenderBuffer::resize(const Vec2F& size) {
if (size == mSize) return;
// Update size
mSize = size;
// Bind framebuffer
AssertGL(glBindFramebuffer(GL_FRAMEBUFFER, mFrameBufferID));
// Resize color texture
if (mSamples) {
AssertGL(glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, mTextureId));
AssertGL(glTexImage2DMultisample(
GL_TEXTURE_2D_MULTISAMPLE, mSamples, GL_RGBA, (GLsizei)size.x, (GLsizei)size.y, GL_TRUE
));
AssertGL(glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, 0));
} else {
AssertGL(glBindTexture(GL_TEXTURE_2D, mTextureId));
AssertGL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)size.x, (GLsizei)size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0));
AssertGL(glBindTexture(GL_TEXTURE_2D, 0));
}
// Resize depth renderbuffer
if (mSamples) {
AssertGL(glBindRenderbuffer(GL_RENDERBUFFER, mDepthBufferID));
AssertGL(glRenderbufferStorageMultisample(
GL_RENDERBUFFER, mSamples, GLW_CONTEXT_DEPTH_COMPONENT, (GLsizei)size.x, (GLsizei)size.y
));
} else {
AssertGL(glBindRenderbuffer(GL_RENDERBUFFER, mDepthBufferID));
AssertGL(glRenderbufferStorage(GL_RENDERBUFFER, GLW_CONTEXT_DEPTH_COMPONENT, (GLsizei)size.x, (GLsizei)size.y));
}
// Unbind framebuffer
AssertGL(glBindFramebuffer(GL_FRAMEBUFFER, 0));
}

View file

@ -1,61 +0,0 @@
#pragma once
#include "Scene.hpp"
#include "GraphicsApi.hpp"
namespace tp {
class ObjectBuffers : public GPUBuffers {
public:
ObjectBuffers(Object* object) {
mObject = (object);
auto& buff = mObject->mTopology.Points;
auto& indices = mObject->mTopology.Indexes;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(Vec3F) * buff.size(), buff.getBuff(), GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(Vec3<uhalni>) * indices.size(), indices.getBuff(), GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), nullptr);
glEnableVertexAttribArray(0);
// glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
Object* mObject = nullptr;
GLuint VAO = 0;
GLuint VBO = 0;
GLuint EBO = 0;
void drawCall() override {
auto& indices = mObject->mTopology.Indexes;
glBindVertexArray(VAO);
// glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glDrawElements(GL_TRIANGLES, indices.size() * 3, GL_UNSIGNED_INT, nullptr);
// glDrawArrays(GL_TRIANGLES, 0, sizeof(buffer) / (2 * sizeof(float)));
// glBindVertexArray(0);
}
~ObjectBuffers() {
glDeleteBuffers(1, &VBO);
glDeleteVertexArrays(1, &VAO);
}
};
}

View file

@ -0,0 +1,57 @@
#include "GpuBuffer.hpp"
#include "GpuError.hpp"
#include <cstring>
namespace tp {
GpuBuffer::GpuBuffer(GpuDevice& gpu, VkBufferUsageFlags usage, size_t capacity) :
mGpu(gpu),
mCapacity(capacity) {
const VkDevice device = mGpu.device();
const VkBufferCreateInfo bufferInfo{
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.size = capacity,
.usage = usage,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
};
requireSuccess(vkCreateBuffer(device, &bufferInfo, nullptr, &mBuffer), "vkCreateBuffer");
VkMemoryRequirements requirements{};
vkGetBufferMemoryRequirements(device, mBuffer, &requirements);
const VkMemoryAllocateInfo allocateInfo{
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.allocationSize = requirements.size,
.memoryTypeIndex = mGpu.findMemoryType(
requirements.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
),
};
const VkResult allocated = vkAllocateMemory(device, &allocateInfo, nullptr, &mMemory);
if (allocated != VK_SUCCESS) {
vkDestroyBuffer(device, mBuffer, nullptr);
throw GpuError("vkAllocateMemory", allocated);
}
vkBindBufferMemory(device, mBuffer, mMemory, 0);
}
GpuBuffer::~GpuBuffer() {
vkDestroyBuffer(mGpu.device(), mBuffer, nullptr);
vkFreeMemory(mGpu.device(), mMemory, nullptr);
}
void GpuBuffer::write(const void* data, size_t size) {
void* mapped = nullptr;
requireSuccess(vkMapMemory(mGpu.device(), mMemory, 0, size, 0, &mapped), "vkMapMemory");
std::memcpy(mapped, data, size);
vkUnmapMemory(mGpu.device(), mMemory);
}
void GpuBuffer::read(void* data, size_t size) const {
void* mapped = nullptr;
requireSuccess(vkMapMemory(mGpu.device(), mMemory, 0, size, 0, &mapped), "vkMapMemory");
std::memcpy(data, mapped, size);
vkUnmapMemory(mGpu.device(), mMemory);
}
}

View file

@ -0,0 +1,171 @@
#include "GraphicsPipeline.hpp"
#include "GpuError.hpp"
#include <array>
#include <vector>
namespace tp {
namespace {
class ShaderModules {
public:
ShaderModules(VkDevice device, std::span<const ShaderStage> stages) :
mDevice(device) {
for (const ShaderStage& stage : stages) {
const VkShaderModuleCreateInfo createInfo{
.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
.codeSize = stage.spirv.size_bytes(),
.pCode = stage.spirv.data(),
};
VkShaderModule module = VK_NULL_HANDLE;
requireSuccess(vkCreateShaderModule(device, &createInfo, nullptr, &module), "vkCreateShaderModule");
mModules.push_back(module);
mStages.push_back({
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
.stage = stage.stage,
.module = module,
.pName = "main",
});
}
}
~ShaderModules() {
for (VkShaderModule module : mModules) {
vkDestroyShaderModule(mDevice, module, nullptr);
}
}
ShaderModules(const ShaderModules&) = delete;
ShaderModules& operator=(const ShaderModules&) = delete;
[[nodiscard]] const std::vector<VkPipelineShaderStageCreateInfo>& stages() const { return mStages; }
private:
VkDevice mDevice;
std::vector<VkShaderModule> mModules;
std::vector<VkPipelineShaderStageCreateInfo> mStages;
};
}
GraphicsPipeline::GraphicsPipeline(GpuDevice& gpu, const PipelineDescription& description) :
mGpu(gpu),
mPushConstantStages(description.pushConstantStages),
mPushConstantSize(description.pushConstantSize) {
const VkDevice device = mGpu.device();
const VkPushConstantRange pushRange{
.stageFlags = description.pushConstantStages,
.offset = 0,
.size = description.pushConstantSize,
};
const VkPipelineLayoutCreateInfo layoutInfo{
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
.pushConstantRangeCount = description.pushConstantSize ? 1u : 0u,
.pPushConstantRanges = &pushRange,
};
requireSuccess(vkCreatePipelineLayout(device, &layoutInfo, nullptr, &mLayout), "vkCreatePipelineLayout");
const ShaderModules modules(device, description.stages);
const VkVertexInputBindingDescription binding{
.binding = 0,
.stride = description.vertexStride,
.inputRate = VK_VERTEX_INPUT_RATE_VERTEX,
};
const VkVertexInputAttributeDescription attribute{
.location = 0,
.binding = 0,
.format = description.vertexFormat,
.offset = 0,
};
const VkPipelineVertexInputStateCreateInfo vertexInput{
.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
.vertexBindingDescriptionCount = 1,
.pVertexBindingDescriptions = &binding,
.vertexAttributeDescriptionCount = 1,
.pVertexAttributeDescriptions = &attribute,
};
const VkPipelineInputAssemblyStateCreateInfo inputAssembly{
.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
.topology = description.topology,
};
const VkPipelineViewportStateCreateInfo viewportState{
.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
.viewportCount = 1,
.scissorCount = 1,
};
const VkPipelineRasterizationStateCreateInfo rasterization{
.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
.polygonMode = VK_POLYGON_MODE_FILL,
.cullMode = VK_CULL_MODE_NONE,
.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE,
.lineWidth = 1.0f,
};
const VkPipelineMultisampleStateCreateInfo multisample{
.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
.rasterizationSamples = description.samples,
};
const VkPipelineDepthStencilStateCreateInfo depthStencil{
.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
.depthTestEnable = VK_TRUE,
.depthWriteEnable = VK_TRUE,
.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL,
};
const VkPipelineColorBlendAttachmentState blendAttachment{
.blendEnable = VK_FALSE,
.colorWriteMask =
VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT,
};
const VkPipelineColorBlendStateCreateInfo colorBlend{
.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
.attachmentCount = 1,
.pAttachments = &blendAttachment,
};
const std::array<VkDynamicState, 2> dynamicStates = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
const VkPipelineDynamicStateCreateInfo dynamicState{
.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
.dynamicStateCount = (uint32_t) dynamicStates.size(),
.pDynamicStates = dynamicStates.data(),
};
const VkGraphicsPipelineCreateInfo pipelineInfo{
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
.stageCount = (uint32_t) modules.stages().size(),
.pStages = modules.stages().data(),
.pVertexInputState = &vertexInput,
.pInputAssemblyState = &inputAssembly,
.pViewportState = &viewportState,
.pRasterizationState = &rasterization,
.pMultisampleState = &multisample,
.pDepthStencilState = &depthStencil,
.pColorBlendState = &colorBlend,
.pDynamicState = &dynamicState,
.layout = mLayout,
.renderPass = description.renderPass,
.subpass = 0,
};
const VkResult created = vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &mPipeline);
if (created != VK_SUCCESS) {
vkDestroyPipelineLayout(device, mLayout, nullptr);
throw GpuError("vkCreateGraphicsPipelines", created);
}
}
GraphicsPipeline::~GraphicsPipeline() {
vkDestroyPipeline(mGpu.device(), mPipeline, nullptr);
vkDestroyPipelineLayout(mGpu.device(), mLayout, nullptr);
}
void GraphicsPipeline::bind(VkCommandBuffer commandBuffer) const {
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, mPipeline);
}
void GraphicsPipeline::pushBytes(VkCommandBuffer commandBuffer, const void* data, uint32_t size) const {
if (size != mPushConstantSize) {
throw GpuError("push constants of the declared size", VK_ERROR_UNKNOWN);
}
vkCmdPushConstants(commandBuffer, mLayout, mPushConstantStages, 0, size, data);
}
}

View file

@ -0,0 +1,124 @@
#include "RasterRender.hpp"
#include "ClipSpace.hpp"
#include <array>
#include <unordered_set>
namespace tp {
namespace {
const uint32_t kObjectVert[] =
#include "object.vert.inc"
;
const uint32_t kDepthFrag[] =
#include "depth.frag.inc"
;
const uint32_t kSolidFrag[] =
#include "solid.frag.inc"
;
struct ObjectConstants {
Mat4F worldToClip;
Mat4F objectToWorld;
};
static_assert(sizeof(ObjectConstants) == 2 * 16 * sizeof(float));
constexpr uint4 kSamples = 4;
Mat4F objectToWorld(const Topology& topology) {
Mat4F out = toMat4(topology.Basis);
out[0][3] = topology.Origin.x;
out[1][3] = topology.Origin.y;
out[2][3] = topology.Origin.z;
return out.transpose();
}
GraphicsPipeline createPipeline(GpuDevice& gpu, const RenderTarget& target, std::span<const uint32_t> fragment) {
const std::array<ShaderStage, 2> stages = {
ShaderStage{ VK_SHADER_STAGE_VERTEX_BIT, kObjectVert },
ShaderStage{ VK_SHADER_STAGE_FRAGMENT_BIT, fragment },
};
return GraphicsPipeline(
gpu,
{
.stages = stages,
.vertexStride = sizeof(Vec3F),
.vertexFormat = VK_FORMAT_R32G32B32_SFLOAT,
.pushConstantStages = VK_SHADER_STAGE_VERTEX_BIT,
.pushConstantSize = sizeof(ObjectConstants),
.renderPass = target.renderPass(),
.samples = target.samples(),
}
);
}
}
RasterRender::RasterRender(GpuDevice& gpu, Vec2<uint4> size) :
mGpu(gpu),
mTarget(gpu, size, kSamples),
mDepthPipeline(createPipeline(gpu, mTarget, kDepthFrag)),
mSolidPipeline(createPipeline(gpu, mTarget, kSolidFrag)) {}
RasterRender::~RasterRender() = default;
void RasterRender::forgetObject(uint8 objectId) { mMeshes.erase(objectId); }
void RasterRender::syncMeshes(const Scene& scene) {
std::unordered_set<uint8> alive;
for (ualni index = 0; index < scene.mObjects.size(); index++) {
const Object& object = scene.mObjects[index];
alive.insert(object.id());
if (mMeshes.contains(object.id())) {
continue;
}
const auto& points = object.mTopology.Points;
const auto& indexes = object.mTopology.Indexes;
ObjectMesh mesh;
mesh.indexCount = (uint32_t) indexes.size() * 3;
if (mesh.indexCount) {
mesh.vertices = std::make_unique<GpuBuffer>(mGpu, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, sizeof(Vec3F) * points.size());
mesh.vertices->write(points.getBuff(), sizeof(Vec3F) * points.size());
mesh.indices = std::make_unique<GpuBuffer>(mGpu, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, sizeof(Vec3<uhalni>) * indexes.size());
mesh.indices->write(indexes.getBuff(), sizeof(Vec3<uhalni>) * indexes.size());
}
mMeshes.emplace(object.id(), std::move(mesh));
}
std::erase_if(mMeshes, [&alive](const auto& entry) { return !alive.contains(entry.first); });
}
void RasterRender::drawObject(
VkCommandBuffer commandBuffer, const GraphicsPipeline& pipeline, const Mat4F& worldToClip, const Object& object
) {
const ObjectMesh& mesh = mMeshes.at(object.id());
if (!mesh.indexCount) {
return;
}
pipeline.push(commandBuffer, ObjectConstants{ worldToClip, objectToWorld(object.mTopology) });
const VkBuffer vertices = mesh.vertices->handle();
const VkDeviceSize offset = 0;
vkCmdBindVertexBuffers(commandBuffer, 0, 1, &vertices, &offset);
vkCmdBindIndexBuffer(commandBuffer, mesh.indices->handle(), 0, VK_INDEX_TYPE_UINT32);
vkCmdDrawIndexed(commandBuffer, mesh.indexCount, 1, 0, 0, 0);
}
void RasterRender::render(const Scene& scene, const Object* highlighted) {
syncMeshes(scene);
const Mat4F worldToClip = vulkanWorldToClip(scene.mCamera);
mTarget.render(RGBA(0.f), [&](VkCommandBuffer commandBuffer) {
mDepthPipeline.bind(commandBuffer);
for (ualni index = 0; index < scene.mObjects.size(); index++) {
drawObject(commandBuffer, mDepthPipeline, worldToClip, scene.mObjects[index]);
}
if (highlighted) {
mSolidPipeline.bind(commandBuffer);
drawObject(commandBuffer, mSolidPipeline, worldToClip, *highlighted);
}
});
}
}

View file

@ -1,111 +0,0 @@
#include "RasterRender.hpp"
#include "GPUBuffers.hpp"
using namespace tp;
RasterRender::RasterRender() {
mDefaultShader.load("rsc/shaders/default.vert", nullptr, "rsc/shaders/default.frag", true);
mSolidShader.load("rsc/shaders/default.vert", nullptr, "rsc/shaders/solid.frag", true);
}
RasterRender::~RasterRender() {}
void RasterRender::resize(const Vec2<tp::ualni>& size) {
auto sizeF = Vec2F(size.x, size.y);
mRenderBuffer.resize(sizeF);
mTempBuffer.resize(sizeF);
}
uint4 RasterRender::getRenderBufferID() { return mRenderBuffer.texId(); }
Vec2F RasterRender::getBufferSize() { return mRenderBuffer.getSize(); }
void RasterRender::bindCameraShaderAttributes(const Mat4F& cameraMat) {
static auto camera = (GLint) mDefaultShader.getu("Camera");
glUniformMatrix4fv(camera, 1, true, &cameraMat[0][0]);
}
void RasterRender::bindObjectShaderAttributes(const Object& object) {
static auto origin = (GLint) mDefaultShader.getu("Origin");
static auto basis = (GLint) mDefaultShader.getu("Basis");
Mat4F basisMat = toMat4(object.mTopology.Basis);
Vec4F originPoint = toVec4(object.mTopology.Origin);
basisMat = basisMat.transpose();
glUniform4fv(origin, 1, &originPoint[0]);
glUniformMatrix4fv(basis, 1, false, &basisMat[0][0]);
}
void RasterRender::renderDefault(const Scene& geometry) {
mRenderBuffer.mClearCol = { 0.0f, 0.0f, 0.0f, 0.f };
mRenderBuffer.beginDraw();
mRenderBuffer.clear();
mDefaultShader.bind();
Mat4F cameraMat = geometry.mCamera.calculateTransformationMatrix();
glEnable(GL_DEPTH_TEST);
// glPolygonMode(GL_FRONT, GL_LINE);
// glPolygonMode(GL_BACK, GL_LINE);
// auto rotator = Mat3F::rotatorZ(alnf(0.001));
bindCameraShaderAttributes(cameraMat);
for (auto object : geometry.mObjects) {
// object->mTopology.Origin += 0.01;
// object->mTopology.Basis = rotator * object->mTopology.Basis;
bindObjectShaderAttributes(object.data());
object->mGUPBuffers->drawCall();
}
mDefaultShader.unbind();
glDisable(GL_DEPTH_TEST);
// glPolygonMode(GL_FRONT, GL_FILL);
// glPolygonMode(GL_BACK, GL_FILL);
mRenderBuffer.endDraw();
}
void RasterRender::renderOutline(const Camera& camera, const Object& object) {
mRenderBuffer.beginDraw();
// glEnable(GL_STENCIL_TEST);
mSolidShader.bind();
bindCameraShaderAttributes(camera.calculateTransformationMatrix());
bindObjectShaderAttributes(object);
object.mGUPBuffers->drawCall();
mSolidShader.unbind();
// glDisable(GL_STENCIL_TEST);
mRenderBuffer.endDraw();
}
void RasterRender::beginRender(const Scene& geometry, const Vec2<tp::ualni>& size) {
resize(size);
for (auto object : geometry.mObjects) {
if (!object->mGUPBuffers) {
object->mGUPBuffers = new ObjectBuffers(&object.data());
}
}
}
void RasterRender::endRender() {
// pass
}
RenderBuffer* RasterRender::getRenderBuffer() { return &mRenderBuffer; }

View file

@ -0,0 +1,399 @@
#include "RenderTarget.hpp"
#include "GpuBuffer.hpp"
#include "GpuError.hpp"
#include <algorithm>
#include <array>
namespace tp {
namespace {
constexpr VkFormat kColorFormat = VK_FORMAT_R8G8B8A8_UNORM;
VkFormat selectDepthFormat(VkPhysicalDevice device) {
for (VkFormat format : { VK_FORMAT_D32_SFLOAT, VK_FORMAT_D24_UNORM_S8_UINT, VK_FORMAT_D16_UNORM }) {
VkFormatProperties properties{};
vkGetPhysicalDeviceFormatProperties(device, format, &properties);
if (properties.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) {
return format;
}
}
throw GpuError("selecting a depth format", VK_ERROR_FORMAT_NOT_SUPPORTED);
}
// The largest supported sample count not above the requested one.
VkSampleCountFlagBits selectSamples(VkPhysicalDevice device, uint4 requested) {
VkPhysicalDeviceProperties properties{};
vkGetPhysicalDeviceProperties(device, &properties);
const VkSampleCountFlags supported =
properties.limits.framebufferColorSampleCounts & properties.limits.framebufferDepthSampleCounts;
for (VkSampleCountFlagBits samples :
{ VK_SAMPLE_COUNT_8_BIT, VK_SAMPLE_COUNT_4_BIT, VK_SAMPLE_COUNT_2_BIT }) {
if ((uint4) samples <= requested && (supported & samples)) {
return samples;
}
}
return VK_SAMPLE_COUNT_1_BIT;
}
VkExtent2D toExtent(Vec2<uint4> size) { return { std::max(size.x, 1u), std::max(size.y, 1u) }; }
void imageBarrier(
VkCommandBuffer commandBuffer,
VkImage image,
VkImageLayout from,
VkImageLayout to,
VkAccessFlags srcAccess,
VkAccessFlags dstAccess,
VkPipelineStageFlags srcStage,
VkPipelineStageFlags dstStage
) {
const VkImageMemoryBarrier barrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.srcAccessMask = srcAccess,
.dstAccessMask = dstAccess,
.oldLayout = from,
.newLayout = to,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = image,
.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 },
};
vkCmdPipelineBarrier(commandBuffer, srcStage, dstStage, 0, 0, nullptr, 0, nullptr, 1, &barrier);
}
}
RenderTarget::RenderTarget(GpuDevice& gpu, Vec2<uint4> size, uint4 samples) :
mGpu(gpu),
mExtent(toExtent(size)),
mSamples(selectSamples(gpu.physicalDevice(), samples)),
mDepthFormat(selectDepthFormat(gpu.physicalDevice())) {
const VkSamplerCreateInfo samplerInfo{
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
.magFilter = VK_FILTER_LINEAR,
.minFilter = VK_FILTER_LINEAR,
.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST,
.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
};
requireSuccess(vkCreateSampler(mGpu.device(), &samplerInfo, nullptr, &mSampler), "vkCreateSampler");
createRenderPass();
createImages();
mRegistryId = mGpu.renderTargets().registerTarget({});
publish();
}
RenderTarget::~RenderTarget() {
mGpu.renderTargets().unregisterTarget(mRegistryId);
mGpu.waitIdle();
destroyImages();
vkDestroyRenderPass(mGpu.device(), mRenderPass, nullptr);
vkDestroySampler(mGpu.device(), mSampler, nullptr);
}
void RenderTarget::createRenderPass() {
const bool multisampled = mSamples != VK_SAMPLE_COUNT_1_BIT;
// Note: a resolved or single-sample color image ends the pass ready for Canvas sampling.
const VkImageLayout colorFinal =
multisampled ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
std::vector<VkAttachmentDescription> attachments = {
{
.format = kColorFormat,
.samples = mSamples,
.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
.storeOp = multisampled ? VK_ATTACHMENT_STORE_OP_DONT_CARE : VK_ATTACHMENT_STORE_OP_STORE,
.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.finalLayout = colorFinal,
},
{
.format = mDepthFormat,
.samples = mSamples,
.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
},
};
if (multisampled) {
attachments.push_back({
.format = kColorFormat,
.samples = VK_SAMPLE_COUNT_1_BIT,
.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
});
}
const VkAttachmentReference colorRef{ 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
const VkAttachmentReference depthRef{ 1, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL };
const VkAttachmentReference resolveRef{ 2, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
const VkSubpassDescription subpass{
.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
.colorAttachmentCount = 1,
.pColorAttachments = &colorRef,
.pResolveAttachments = multisampled ? &resolveRef : nullptr,
.pDepthStencilAttachment = &depthRef,
};
// Note: orders this pass after earlier Canvas sampling and before later sampling of the result.
const std::array<VkSubpassDependency, 2> dependencies = {
VkSubpassDependency{
.srcSubpass = VK_SUBPASS_EXTERNAL,
.dstSubpass = 0,
.srcStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
.dstStageMask =
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT,
.srcAccessMask = VK_ACCESS_SHADER_READ_BIT,
.dstAccessMask =
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
},
VkSubpassDependency{
.srcSubpass = 0,
.dstSubpass = VK_SUBPASS_EXTERNAL,
.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
.dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
},
};
const VkRenderPassCreateInfo createInfo{
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
.attachmentCount = (uint32_t) attachments.size(),
.pAttachments = attachments.data(),
.subpassCount = 1,
.pSubpasses = &subpass,
.dependencyCount = (uint32_t) dependencies.size(),
.pDependencies = dependencies.data(),
};
requireSuccess(vkCreateRenderPass(mGpu.device(), &createInfo, nullptr, &mRenderPass), "vkCreateRenderPass");
}
RenderTarget::Image RenderTarget::createImage(
VkFormat format, VkImageUsageFlags usage, VkSampleCountFlagBits samples, VkImageAspectFlags aspect
) const {
const VkDevice device = mGpu.device();
Image out;
const VkImageCreateInfo imageInfo{
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.imageType = VK_IMAGE_TYPE_2D,
.format = format,
.extent = { mExtent.width, mExtent.height, 1 },
.mipLevels = 1,
.arrayLayers = 1,
.samples = samples,
.tiling = VK_IMAGE_TILING_OPTIMAL,
.usage = usage,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
};
requireSuccess(vkCreateImage(device, &imageInfo, nullptr, &out.image), "vkCreateImage");
VkMemoryRequirements requirements{};
vkGetImageMemoryRequirements(device, out.image, &requirements);
const VkMemoryAllocateInfo allocateInfo{
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.allocationSize = requirements.size,
.memoryTypeIndex = mGpu.findMemoryType(requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT),
};
requireSuccess(vkAllocateMemory(device, &allocateInfo, nullptr, &out.memory), "vkAllocateMemory");
vkBindImageMemory(device, out.image, out.memory, 0);
const VkImageViewCreateInfo viewInfo{
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.image = out.image,
.viewType = VK_IMAGE_VIEW_TYPE_2D,
.format = format,
.subresourceRange = { aspect, 0, 1, 0, 1 },
};
requireSuccess(vkCreateImageView(device, &viewInfo, nullptr, &out.view), "vkCreateImageView");
return out;
}
void RenderTarget::destroyImage(Image& image) const {
vkDestroyImageView(mGpu.device(), image.view, nullptr);
vkDestroyImage(mGpu.device(), image.image, nullptr);
vkFreeMemory(mGpu.device(), image.memory, nullptr);
image = {};
}
void RenderTarget::createImages() {
const bool multisampled = mSamples != VK_SAMPLE_COUNT_1_BIT;
const VkImageUsageFlags resolvedUsage =
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
VK_IMAGE_USAGE_TRANSFER_DST_BIT;
mColor = createImage(
kColorFormat,
multisampled ? VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT : resolvedUsage,
mSamples,
VK_IMAGE_ASPECT_COLOR_BIT
);
mDepth = createImage(mDepthFormat, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, mSamples, VK_IMAGE_ASPECT_DEPTH_BIT);
std::vector<VkImageView> views = { mColor.view, mDepth.view };
if (multisampled) {
mResolve = createImage(kColorFormat, resolvedUsage, VK_SAMPLE_COUNT_1_BIT, VK_IMAGE_ASPECT_COLOR_BIT);
views.push_back(mResolve.view);
}
const VkFramebufferCreateInfo framebufferInfo{
.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
.renderPass = mRenderPass,
.attachmentCount = (uint32_t) views.size(),
.pAttachments = views.data(),
.width = mExtent.width,
.height = mExtent.height,
.layers = 1,
};
requireSuccess(vkCreateFramebuffer(mGpu.device(), &framebufferInfo, nullptr, &mFramebuffer), "vkCreateFramebuffer");
// Note: the resolved image is sampled before the first render, so it starts cleared in the sampling layout.
const VkCommandBuffer commandBuffer = mGpu.beginOneShotCommands();
imageBarrier(
commandBuffer,
resolved().image,
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
0,
VK_ACCESS_TRANSFER_WRITE_BIT,
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT
);
const VkClearColorValue transparent{};
const VkImageSubresourceRange range{ VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
vkCmdClearColorImage(
commandBuffer, resolved().image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &transparent, 1, &range
);
imageBarrier(
commandBuffer,
resolved().image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
VK_ACCESS_TRANSFER_WRITE_BIT,
VK_ACCESS_SHADER_READ_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT
);
mGpu.endOneShotCommands(commandBuffer);
}
void RenderTarget::destroyImages() {
vkDestroyFramebuffer(mGpu.device(), mFramebuffer, nullptr);
mFramebuffer = VK_NULL_HANDLE;
destroyImage(mColor);
destroyImage(mDepth);
if (mResolve.image) {
destroyImage(mResolve);
}
}
void RenderTarget::publish() {
const Image& image = resolved();
mGpu.renderTargets().updateTarget(
mRegistryId,
{
.view = image.view,
.sampler = mSampler,
.width = (int) mExtent.width,
.height = (int) mExtent.height,
.image = image.image,
}
);
}
void RenderTarget::resize(Vec2<uint4> size) {
const VkExtent2D next = toExtent(size);
if (next.width == mExtent.width && next.height == mExtent.height) {
return;
}
mGpu.waitIdle();
destroyImages();
mExtent = next;
createImages();
publish();
}
void RenderTarget::render(const RGBA& clearColor, const std::function<void(VkCommandBuffer)>& draw) {
const VkCommandBuffer commandBuffer = mGpu.beginOneShotCommands();
std::array<VkClearValue, 3> clears{};
clears[0].color = { { clearColor.r, clearColor.g, clearColor.b, clearColor.a } };
clears[1].depthStencil = { 1.0f, 0 };
const VkRenderPassBeginInfo beginInfo{
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
.renderPass = mRenderPass,
.framebuffer = mFramebuffer,
.renderArea = { { 0, 0 }, mExtent },
.clearValueCount = (uint32_t) clears.size(),
.pClearValues = clears.data(),
};
vkCmdBeginRenderPass(commandBuffer, &beginInfo, VK_SUBPASS_CONTENTS_INLINE);
const VkViewport viewport{
.x = 0,
.y = 0,
.width = (float) mExtent.width,
.height = (float) mExtent.height,
.minDepth = 0,
.maxDepth = 1,
};
vkCmdSetViewport(commandBuffer, 0, 1, &viewport);
const VkRect2D scissor{ { 0, 0 }, mExtent };
vkCmdSetScissor(commandBuffer, 0, 1, &scissor);
draw(commandBuffer);
vkCmdEndRenderPass(commandBuffer);
mGpu.endOneShotCommands(commandBuffer);
}
std::vector<uint32_t> RenderTarget::readPixels() {
const size_t count = (size_t) mExtent.width * mExtent.height;
GpuBuffer staging(mGpu, VK_BUFFER_USAGE_TRANSFER_DST_BIT, count * sizeof(uint32_t));
const VkCommandBuffer commandBuffer = mGpu.beginOneShotCommands();
imageBarrier(
commandBuffer,
resolved().image,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
VK_ACCESS_TRANSFER_READ_BIT,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT
);
const VkBufferImageCopy region{
.imageSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 },
.imageExtent = { mExtent.width, mExtent.height, 1 },
};
vkCmdCopyImageToBuffer(
commandBuffer, resolved().image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, staging.handle(), 1, &region
);
imageBarrier(
commandBuffer,
resolved().image,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
VK_ACCESS_TRANSFER_READ_BIT,
VK_ACCESS_SHADER_READ_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT
);
mGpu.endOneShotCommands(commandBuffer);
std::vector<uint32_t> pixels(count);
staging.read(pixels.data(), pixels.size() * sizeof(uint32_t));
return pixels;
}
}

View file

@ -1,162 +0,0 @@
#include "Buffer.hpp"
#include "LocalConnection.hpp"
#include "Shader.hpp"
#include "GraphicsApi.hpp"
#include <cstdio>
using namespace tp;
RenderShader::RenderShader() {
programm = 0;
VertexShaderID = 0;
FragmentShaderID = 0;
GeometryShaderID = 0;
}
void RenderShader::vert_bind_source(const char* vert_src) {
VertexShaderID = glCreateShader(GL_VERTEX_SHADER);
compile_shader(vert_src, VertexShaderID);
}
void RenderShader::frag_bind_source(const char* frag_src) {
FragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);
compile_shader(frag_src, FragmentShaderID);
}
void RenderShader::geom_bind_source(const char* geom_src) {
GeometryShaderID = glCreateShader(GL_GEOMETRY_SHADER);
compile_shader(geom_src, GeometryShaderID);
}
void RenderShader::compile() {
GLint Result = GL_FALSE;
int InfoLogLength;
programm = glCreateProgram();
glAttachShader(programm, VertexShaderID);
if (GeometryShaderID) glAttachShader(programm, GeometryShaderID);
glAttachShader(programm, FragmentShaderID);
glLinkProgram(programm);
// Check the program
glGetProgramiv(programm, GL_LINK_STATUS, &Result);
glGetProgramiv(programm, GL_INFO_LOG_LENGTH, &InfoLogLength);
if (InfoLogLength > 0) {
Buffer<char> ProgramErrorMessage(InfoLogLength + 1);
glGetProgramInfoLog(programm, InfoLogLength, NULL, &ProgramErrorMessage[0]);
printf("%s\n", &ProgramErrorMessage[0]);
}
glDetachShader(programm, VertexShaderID);
glDetachShader(programm, FragmentShaderID);
if (GeometryShaderID) glDetachShader(programm, GeometryShaderID);
glDeleteShader(VertexShaderID);
glDeleteShader(FragmentShaderID);
if (GeometryShaderID) glDeleteShader(GeometryShaderID);
}
bool RenderShader::compile_shader(const char* ShaderCode, uint4 ShaderID) {
GLint Result = GL_FALSE;
int InfoLogLength;
char const* SourcePointer = ShaderCode;
glShaderSource(ShaderID, 1, &SourcePointer, NULL);
glCompileShader(ShaderID);
// Check Shader
glGetShaderiv(ShaderID, GL_COMPILE_STATUS, &Result);
glGetShaderiv(ShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);
if (InfoLogLength > 0) {
Buffer<char> VertexShaderErrorMessage(InfoLogLength + 1);
glGetShaderInfoLog(ShaderID, InfoLogLength, NULL, &VertexShaderErrorMessage[0]);
printf("%s\n", &VertexShaderErrorMessage[0]);
}
return Result;
}
void RenderShader::load(const char* pvert, const char* pgeom, const char* pfrag, bool paths) {
// Create the shaders
VertexShaderID = glCreateShader(GL_VERTEX_SHADER);
FragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);
if (pgeom) GeometryShaderID = glCreateShader(GL_GEOMETRY_SHADER);
else GeometryShaderID = 0;
GLint Result = GL_FALSE;
int InfoLogLength = 0;
if (paths) {
std::string content;
auto loadFile = [&](const char* path) {
LocalConnection file;
if (!file.connect(LocalConnection::Location(path), LocalConnection::Type(true))) {
content = "";
return false;
}
const auto size = file.size();
auto tmp = new char[size + 1];
tmp[size] = 0;
file.readBytes(tmp, size);
content = tmp;
delete[] tmp;
return true;
};
printf("Compiling shader : %s\n", pvert);
if (loadFile(pvert)) {
compile_shader(content.c_str(), VertexShaderID);
}
if (GeometryShaderID) {
printf("Compiling shader : %s\n", pgeom);
if (loadFile(pgeom)) {
compile_shader(content.c_str(), GeometryShaderID);
}
}
printf("Compiling shader : %s\n", pfrag);
if (loadFile(pfrag)) {
compile_shader(content.c_str(), FragmentShaderID);
}
}
else {
compile_shader(pvert, VertexShaderID);
if (GeometryShaderID) {
compile_shader(pgeom, GeometryShaderID);
}
compile_shader(pfrag, FragmentShaderID);
}
compile();
}
RenderShader::RenderShader(const char* vert, const char* geom, const char* frag, bool paths) {
load(vert, geom, frag, paths);
}
void RenderShader::bind() {
glUseProgram(programm);
}
GLuint RenderShader::getu(const char* uid) {
return glGetUniformLocation(programm, uid);
}
void RenderShader::unbind() {
glUseProgram(0);
}
RenderShader::~RenderShader() {
glDeleteProgram(programm);
}

View file

@ -1,205 +0,0 @@
#include "Texture.hpp"
#include "Map.hpp"
#include "Shader.hpp"
#include "GraphicsApi.hpp"
const char* texture_vertex =
#ifdef ENV_OS_ANDROID
"#version 300 es\n"
"precision mediump float;\n"
#else
"#version 330 core\n"
#endif
"layout(location = 0) in vec3 vPos;\n"
"out vec2 UV;\n"
"void main() {\n"
" gl_Position = vec4(vPos, 1);\n"
" UV = (vPos.xy + vec2(1, 1)) / 2.0;\n"
"}\n";
const char* texture_fragment =
#ifdef ENV_OS_ANDROID
"#version 300 es\n"
"precision mediump float;\n"
#else
"#version 330 core\n"
#endif
"in vec2 UV;\n"
"out vec4 color;\n"
"uniform sampler2D renderedTexture;\n"
"uniform float time;\n"
"void main() {\n"
" vec4 texColor = texture(renderedTexture, UV);\n"
" if (texColor.a < 0.2)\n"
" discard;\n"
" color = texColor;\n"
"}\n";
using namespace tp;
GLuint RenderTexture::getid() { return id; }
RenderTexture::RenderTexture() {
glGenTextures(1, &id);
glBindTexture(GL_TEXTURE_2D, id);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, 0);
}
RenderTexture::~RenderTexture() { glDeleteTextures(1, &id); }
void RenderTexture::update(const Buffer2D<RGBA>& buff) {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)buff.size().x, (GLsizei)buff.size().y, 0, GL_RGBA, GL_FLOAT, buff.getBuff());
}
void RenderTexture::draw(const GLuint& out) {
draw_texture(out, id);
}
struct texture_drawer_data {
Map<std::string, GLuint> textures;
GLuint quad_VertexArrayID;
GLuint quad_vertexbuffer;
GLuint texID;
RenderShader shader;
const GLfloat g_quad_vertex_buffer_data[18] = {
-1.0f, -1.0f, 0.0f, 1.0f, -1.0f, 0.0f, -1.0f, 1.0f, 0.0f,
-1.0f, 1.0f, 0.0f, 1.0f, -1.0f, 0.0f, 1.0f, 1.0f, 0.0f,
};
texture_drawer_data() : shader(texture_vertex, nullptr, texture_fragment, false) {
// The fullscreen quad's FBO
glGenVertexArrays(1, &quad_VertexArrayID);
glBindVertexArray(quad_VertexArrayID);
glGenBuffers(1, &quad_vertexbuffer);
glBindBuffer(GL_ARRAY_BUFFER, quad_vertexbuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(g_quad_vertex_buffer_data),
g_quad_vertex_buffer_data, GL_STATIC_DRAW);
texID = shader.getu("renderedTexture");
}
~texture_drawer_data() {
glDeleteBuffers(1, &quad_vertexbuffer);
glDeleteVertexArrays(1, &quad_VertexArrayID);
for (auto tex : textures) {
glDeleteTextures(1, &tex->val);
}
}
};
texture_drawer_data* texdd = NULL;
void RenderTexture::init() {
if (!texdd) texdd = new texture_drawer_data();
}
void RenderTexture::deinit() {
if (texdd) delete texdd;
texdd = NULL;
}
void RenderTexture::draw_texture(uint4 out, uint4 in) {
ASSERT(in);
// Render to the screen
glBindFramebuffer(GL_FRAMEBUFFER, out);
// Use our shader
texdd->shader.bind();
// Bind our texture in Texture Unit 0
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, in);
// Set our "renderedTexture" sampler to use Texture Unit 0
glUniform1i(texdd->texID, 0);
// glUniformMatrix4fv(texdd->rect_mat, 1, GL_FALSE, &tmat[0][0]);
// 1rst attribute buffer : vertices
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, texdd->quad_vertexbuffer);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
// Draw the triangles. 2*3 indices starting at 0 -> 2 triangles
glDrawArrays(GL_TRIANGLES, 0, 6);
glDisableVertexAttribArray(0);
texdd->shader.unbind();
}
GLuint load_texture(const std::string& name) {
GLuint tex_2d = 0;
ASSERT(0 && "incomplete compilation - no SOIL support added");
if (0) {
//auto document = lunasvg::Document::loadFromFile("tiger.svg");
//auto bitmap = document->renderToBitmap();
}
else {
/*
tex_2d = SOIL_load_OGL_texture(
name.cstr(), SOIL_LOAD_RGBA, SOIL_CREATE_NEW_ID,
SOIL_FLAG_MIPMAPS | SOIL_FLAG_INVERT_Y | SOIL_FLAG_NTSC_SAFE_RGB |
SOIL_FLAG_COMPRESS_TO_DXT);
if (0 == tex_2d) {
printf("SOIL loading error: '%s'\n", SOIL_last_result());
}
*/
}
return tex_2d;
}
GLuint RenderTexture::get_tex(const char* TexId) {
GLuint out = 0;
auto idx = texdd->textures.presents(TexId);
if (idx) {
out = texdd->textures.get(TexId);
}
else {
out = load_texture(TexId);
texdd->textures.put(TexId, out);
}
return out;
}
void RenderTexture::drawCurcle(Vec2F pos, double radius, RGBA col) {
#ifndef ENV_OS_ANDROID
static alni precision = 40;
glColor4f(col.r, col.g, col.b, col.a);
double twicePi = 2.0 * 3.142;
glBegin(GL_TRIANGLE_FAN); // BEGIN CIRCLE
glVertex2f(pos.x, pos.y); // center of circle
for (alni i = 0; i <= precision; i++) {
glVertex2f((GLfloat)(pos.x + (radius * cos(i * twicePi / precision))),
(GLfloat)(pos.y + (radius * sin(i * twicePi / precision))));
}
glEnd(); // END
#endif
}

View file

@ -0,0 +1,22 @@
#pragma once
#include "Camera.hpp"
namespace tp {
// Camera matrices produce OpenGL clip depth in [-w, w]; Vulkan clips depth to [0, w].
// Note: Y is kept, so image row 0 is clip y = -1, the same memory layout the GL framebuffers had.
inline Mat4F vulkanDepthCorrection() {
Mat4F correction;
correction[0] = Vec4F(1, 0, 0, 0);
correction[1] = Vec4F(0, 1, 0, 0);
correction[2] = Vec4F(0, 0, 0.5f, 0.5f);
correction[3] = Vec4F(0, 0, 0, 1);
return correction;
}
// World to Vulkan clip space, transposed into the column-major layout GLSL reads.
inline Mat4F vulkanWorldToClip(const Camera& camera) {
return (vulkanDepthCorrection() * camera.calculateTransformationMatrix()).transpose();
}
}

View file

@ -1,37 +0,0 @@
#pragma once
#include "Rect.hpp"
#include "Color.hpp"
namespace tp {
class RenderBuffer {
public:
explicit RenderBuffer(const Vec2F& size = { 10, 10 });
RenderBuffer(const Vec2F& size, tp::uint1 samples);
~RenderBuffer();
void beginDraw();
void setViewport(const RectF& viewport);
void clear();
void endDraw();
[[nodiscard]] uint4 texId() const;
[[nodiscard]] uint4 buffId() const;
void resize(const Vec2F& size);
[[nodiscard]] const Vec2F& getSize() const;
public:
RGBA mClearCol = 0.f;
private:
uint4 mFrameBufferID = 0; // regroups 0, 1, or more textures, and 0 or 1 depth buffer.
uint4 mTextureId = 0; // texture we're going to render to ( colour attachement #0 )
uint4 mDepthBufferID = 0;
uint4 mDrawBuffers[1] = { 0 };
Vec2F mSize;
ualni mSamples = 0;
};
};

View file

@ -0,0 +1,33 @@
#pragma once
#include "GpuDevice.hpp"
#include <vulkan/vulkan.h>
#include <cstddef>
namespace tp {
// A host-visible Vulkan buffer the CPU writes or reads directly.
class GpuBuffer {
public:
GpuBuffer(GpuDevice& gpu, VkBufferUsageFlags usage, size_t capacity);
~GpuBuffer();
GpuBuffer(const GpuBuffer&) = delete;
GpuBuffer& operator=(const GpuBuffer&) = delete;
[[nodiscard]] VkBuffer handle() const { return mBuffer; }
[[nodiscard]] size_t capacity() const { return mCapacity; }
// Note: the caller keeps the GPU off this buffer while writing (one-shot submits wait for completion).
void write(const void* data, size_t size);
void read(void* data, size_t size) const;
private:
GpuDevice& mGpu;
size_t mCapacity;
VkBuffer mBuffer = VK_NULL_HANDLE;
VkDeviceMemory mMemory = VK_NULL_HANDLE;
};
}

View file

@ -1,19 +0,0 @@
#pragma once
#include "Environment.hpp"
#ifdef ENV_OS_ANDROID
#include <EGL/egl.h>
#include <GLES3/gl32.h>
#else
#include "GL/glew.h"
#endif
#ifdef ENV_OS_ANDROID
#define GLW_CONTEXT_DEPTH_BITS 16
#define GLW_CONTEXT_DEPTH_COMPONENT GL_DEPTH_COMPONENT16
#else
#define GLW_CONTEXT_DEPTH_BITS 32
#define GLW_CONTEXT_DEPTH_COMPONENT GL_DEPTH_COMPONENT32
#endif

View file

@ -0,0 +1,56 @@
#pragma once
#include "GpuDevice.hpp"
#include <vulkan/vulkan.h>
#include <cstdint>
#include <span>
#include <type_traits>
namespace tp {
struct ShaderStage {
VkShaderStageFlagBits stage;
std::span<const uint32_t> spirv;
};
// Fixed-function state of a pipeline that draws one interleaved vertex buffer with depth testing.
struct PipelineDescription {
std::span<const ShaderStage> stages;
uint32_t vertexStride = 0;
VkFormat vertexFormat = VK_FORMAT_R32G32B32_SFLOAT;
VkPrimitiveTopology topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
VkShaderStageFlags pushConstantStages = VK_SHADER_STAGE_VERTEX_BIT;
uint32_t pushConstantSize = 0;
VkRenderPass renderPass = VK_NULL_HANDLE;
VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT;
};
class GraphicsPipeline {
public:
GraphicsPipeline(GpuDevice& gpu, const PipelineDescription& description);
~GraphicsPipeline();
GraphicsPipeline(const GraphicsPipeline&) = delete;
GraphicsPipeline& operator=(const GraphicsPipeline&) = delete;
void bind(VkCommandBuffer commandBuffer) const;
// Note: tConstants must match the shader's push-constant block byte for byte and have standard layout.
template <typename tConstants>
void push(VkCommandBuffer commandBuffer, const tConstants& constants) const {
static_assert(std::is_standard_layout_v<tConstants>);
pushBytes(commandBuffer, &constants, sizeof(tConstants));
}
private:
void pushBytes(VkCommandBuffer commandBuffer, const void* data, uint32_t size) const;
GpuDevice& mGpu;
VkShaderStageFlags mPushConstantStages;
uint32_t mPushConstantSize;
VkPipelineLayout mLayout = VK_NULL_HANDLE;
VkPipeline mPipeline = VK_NULL_HANDLE;
};
}

View file

@ -1,38 +1,46 @@
#pragma once
#include "Scene.hpp"
#include "Rect.hpp"
#include "RenderTarget.hpp"
#include "GraphicsPipeline.hpp"
#include "GpuBuffer.hpp"
#include "FrameBuffer.hpp"
#include "Shader.hpp"
#include <memory>
#include <unordered_map>
namespace tp {
// Draws a Scene into an offscreen RenderTarget: depth-shaded objects plus an optional solid highlight.
class RasterRender {
public:
RasterRender();
explicit RasterRender(GpuDevice& gpu, Vec2<uint4> size = { 1, 1 });
~RasterRender();
void beginRender(const Scene& geometry, const Vec2<ualni>& size);
void endRender();
RasterRender(const RasterRender&) = delete;
RasterRender& operator=(const RasterRender&) = delete;
void renderDefault(const Scene& geometry);
void renderOutline(const Camera& camera, const Object& object);
[[nodiscard]] RenderTarget& target() { return mTarget; }
[[nodiscard]] size_t uploadedMeshes() const { return mMeshes.size(); }
void resize(const Vec2<ualni>& size);
uint4 getRenderBufferID();
RenderBuffer* getRenderBuffer();
Vec2F getBufferSize();
// Uploads meshes it has not seen, drops meshes of removed objects, then renders.
// Note: a changed mTopology of an existing object needs forgetObject() to upload again.
void render(const Scene& scene, const Object* highlighted = nullptr);
void forgetObject(uint8 objectId);
private:
void bindObjectShaderAttributes(const Object& object);
void bindCameraShaderAttributes(const Mat4F& cameraMat);
struct ObjectMesh {
std::unique_ptr<GpuBuffer> vertices;
std::unique_ptr<GpuBuffer> indices;
uint32_t indexCount = 0;
};
private:
RenderBuffer mRenderBuffer;
RenderBuffer mTempBuffer;
void syncMeshes(const Scene& scene);
void drawObject(VkCommandBuffer commandBuffer, const GraphicsPipeline& pipeline, const Mat4F& worldToClip, const Object& object);
RenderShader mDefaultShader;
RenderShader mSolidShader;
GpuDevice& mGpu;
RenderTarget mTarget;
GraphicsPipeline mDepthPipeline;
GraphicsPipeline mSolidPipeline;
std::unordered_map<uint8, ObjectMesh> mMeshes;
};
}
}

View file

@ -0,0 +1,71 @@
#pragma once
#include "GpuDevice.hpp"
#include "Color.hpp"
#include "Vec.hpp"
#include <vulkan/vulkan.h>
#include <cstdint>
#include <functional>
#include <vector>
namespace tp {
// Offscreen color and depth images that one render pass draws into; the resolved color image is
// registered in the device's RenderTargetRegistry so a Canvas can draw it.
class RenderTarget {
public:
RenderTarget(GpuDevice& gpu, Vec2<uint4> size, uint4 samples);
~RenderTarget();
RenderTarget(const RenderTarget&) = delete;
RenderTarget& operator=(const RenderTarget&) = delete;
[[nodiscard]] uint32_t registryId() const { return mRegistryId; }
[[nodiscard]] Vec2<uint4> size() const { return { mExtent.width, mExtent.height }; }
[[nodiscard]] VkRenderPass renderPass() const { return mRenderPass; }
[[nodiscard]] VkSampleCountFlagBits samples() const { return mSamples; }
// Waits for the GPU and recreates the images; Canvas images made from this target must be updated.
void resize(Vec2<uint4> size);
// Records one cleared render pass, submits it and waits for completion.
void render(const RGBA& clearColor, const std::function<void(VkCommandBuffer)>& draw);
// The resolved color image as RGBA8 rows, top row first.
[[nodiscard]] std::vector<uint32_t> readPixels();
private:
struct Image {
VkImage image = VK_NULL_HANDLE;
VkDeviceMemory memory = VK_NULL_HANDLE;
VkImageView view = VK_NULL_HANDLE;
};
[[nodiscard]] Image createImage(
VkFormat format, VkImageUsageFlags usage, VkSampleCountFlagBits samples, VkImageAspectFlags aspect
) const;
void destroyImage(Image& image) const;
void createRenderPass();
void createImages();
void destroyImages();
void publish();
[[nodiscard]] const Image& resolved() const { return mResolve.image ? mResolve : mColor; }
GpuDevice& mGpu;
VkExtent2D mExtent{};
VkSampleCountFlagBits mSamples = VK_SAMPLE_COUNT_1_BIT;
VkFormat mDepthFormat = VK_FORMAT_UNDEFINED;
VkRenderPass mRenderPass = VK_NULL_HANDLE;
VkSampler mSampler = VK_NULL_HANDLE;
Image mColor;
Image mDepth;
Image mResolve;
VkFramebuffer mFramebuffer = VK_NULL_HANDLE;
uint32_t mRegistryId = 0;
};
}

View file

@ -1,32 +0,0 @@
#pragma once
namespace tp {
class RenderShader {
public:
RenderShader();
RenderShader(const char* vertid, const char* geomid, const char* fragid, bool paths = true);
~RenderShader();
void vert_bind_source(const char* vert_src);
void frag_bind_source(const char* frag_src);
void geom_bind_source(const char* geom_src);
void compile();
void bind();
uint4 getu(const char* uid);
void unbind();
void load(const char* vert, const char* geom, const char* frag, bool paths);
private:
bool compile_shader(const char* ShaderCode, uint4 ShaderID);
private:
uint4 programm;
uint4 VertexShaderID;
uint4 FragmentShaderID;
uint4 GeometryShaderID;
};
};

View file

@ -1,29 +0,0 @@
#pragma once
#include "Buffer2D.hpp"
#include "Color.hpp"
#include "Vec.hpp"
namespace tp {
class RenderTexture {
public:
RenderTexture();
~RenderTexture();
uint4 getid();
void update(const Buffer2D<RGBA>& buff);
void draw(const uint4& out = 0);
public:
static void init();
static void deinit();
static void draw_texture(uint4 out, uint4 in);
static uint4 get_tex(const char* TexId);
static void drawCurcle(Vec2F pos, double radius, RGBA col);
private:
uint4 id;
};
};

View file

@ -1,7 +0,0 @@
#version 330 core
out vec4 FragColor;
void main() {
FragColor = vec4(gl_FragCoord.z, gl_FragCoord.z, gl_FragCoord.z, 1.f);
}

View file

@ -1,13 +0,0 @@
#version 330 core
layout(location = 0) in vec3 Point;
uniform vec4 Origin;
uniform mat4 Basis;
uniform mat4 Camera;
void main() {
// vec4 transformed = vec4(Point.xyz, 1.0);
vec4 transformed = (Basis * vec4(Point.xyz, 1.0)) + Origin;
gl_Position = Camera * transformed;
}

View file

@ -1,7 +0,0 @@
#version 330 core
out vec4 FragColor;
void main() {
FragColor = vec4(1, 0, 0, 1.f);
}

View file

@ -0,0 +1,7 @@
#version 450
layout(location = 0) out vec4 color;
void main() {
color = vec4(vec3(gl_FragCoord.z), 1.0);
}

View file

@ -0,0 +1,12 @@
#version 450
layout(location = 0) in vec3 point;
layout(push_constant) uniform Constants {
mat4 worldToClip;
mat4 objectToWorld;
} constants;
void main() {
gl_Position = constants.worldToClip * constants.objectToWorld * vec4(point, 1.0);
}

View file

@ -0,0 +1,7 @@
#version 450
layout(location = 0) out vec4 color;
void main() {
color = vec4(1.0, 0.0, 0.0, 1.0);
}

View file

@ -0,0 +1,100 @@
#include "RasterRender.hpp"
#include "UnitTest++/UnitTest++.h"
using namespace tp;
namespace {
constexpr uint4 kSize = 64;
// A triangle around the origin, facing a camera on +X.
Object triangle(Vec3F origin) {
Object object;
object.mTopology.Points = { { 0, -1, -1 }, { 0, 1, -1 }, { 0, 0, 1 } };
object.mTopology.Normals = { { 1, 0, 0 }, { 1, 0, 0 }, { 1, 0, 0 } };
object.mTopology.Indexes = { { 0, 1, 2 } };
object.mTopology.Origin = origin;
object.updateCache();
return object;
}
Scene sceneWith(Vec3F origin) {
Scene scene;
scene.mCamera.lookAtPoint({ 0, 0, 0 }, { 4, 0, 0 }, { 0, 0, 1 });
scene.addObject(triangle(origin));
return scene;
}
struct Fixture {
// Note: GpuDevice always enables the swapchain extension, which requires the surface instance extension.
GpuDevice gpu{ { VK_KHR_SURFACE_EXTENSION_NAME } };
RasterRender render{ gpu, { kSize, kSize } };
};
uint32_t pixel(const std::vector<uint32_t>& pixels, uint4 x, uint4 y) { return pixels[y * kSize + x]; }
uint8_t channel(uint32_t pixel, int index) { return (uint8_t) (pixel >> (index * 8)); }
}
SUITE(RasterRender) {
TEST_FIXTURE(Fixture, DrawsDepthShadedObjectsAndClearsTheRest) {
const Scene scene = sceneWith({ 0, 0, 0 });
render.render(scene);
const auto pixels = render.target().readPixels();
const uint32_t center = pixel(pixels, kSize / 2, kSize / 2);
CHECK(channel(center, 0) > 0);
CHECK_EQUAL(channel(center, 0), channel(center, 1));
CHECK_EQUAL(255, channel(center, 3));
CHECK_EQUAL(0u, pixel(pixels, 1, 1));
}
TEST_FIXTURE(Fixture, RowsFollowCameraProjectionAndObjectOrigin) {
const Scene scene = sceneWith({ 0, 0, 1.4f });
render.render(scene);
const auto pixels = render.target().readPixels();
// Row 0 is projected y = -1, the layout Camera::project and the old GL framebuffers use.
const Vec2F projected = scene.mCamera.project(Vec3F(0, 0, 1.4f));
const auto row = (uint4) ((projected.y + 1) / 2 * kSize);
const auto mirroredRow = kSize - 1 - row;
CHECK_EQUAL(255, channel(pixel(pixels, kSize / 2, row), 3));
CHECK_EQUAL(0u, pixel(pixels, kSize / 2, mirroredRow));
}
TEST_FIXTURE(Fixture, HighlightDrawsSolidRed) {
const Scene scene = sceneWith({ 0, 0, 0 });
render.render(scene, &scene.mObjects[0]);
const uint32_t center = pixel(render.target().readPixels(), kSize / 2, kSize / 2);
CHECK_EQUAL(255, channel(center, 0));
CHECK_EQUAL(0, channel(center, 1));
CHECK_EQUAL(0, channel(center, 2));
}
TEST_FIXTURE(Fixture, ResizeKeepsTheRegistryEntryCurrent) {
render.target().resize({ 32, 16 });
RenderTargetEntry entry;
CHECK(gpu.renderTargets().resolve(render.target().registryId(), entry));
CHECK_EQUAL(32, entry.width);
CHECK_EQUAL(16, entry.height);
CHECK(entry.view != VK_NULL_HANDLE);
}
TEST_FIXTURE(Fixture, UploadsEachObjectOnceAndReleasesRemovedOnes) {
Scene scene = sceneWith({ 0, 0, 0 });
render.render(scene);
render.render(scene);
CHECK_EQUAL(1u, render.uploadedMeshes());
scene.mObjects.clear();
render.render(scene);
CHECK_EQUAL(0u, render.uploadedMeshes());
}
}
int main() { return UnitTest::RunAllTests(); }

View file

@ -53,11 +53,13 @@ void printStatus(const halnf* percentage) {
void renderCommand(const std::string& scenePath) {
Scene scene;
RenderSettings& settings = scene.mRenderSettings;
if (!scene.load(scenePath)) {
try {
scene = Scene::loadLua(scenePath);
} catch (const SceneLoadError& error) {
std::cout << "Failed loading scene: " << error.what() << std::endl;
return;
}
const RenderSettings& settings = scene.mRenderSettings;
RayTracer::OutputBuffers output;
RayTracer rayt;

View file

@ -9,69 +9,9 @@
#include <cstdio>
/*
if (1) {
const auto& points = castData.obj->mCache.TransformedPoints;
const auto& normals = castData.obj->mCache.TransformedNormals;
const auto trig = castData.trig;
const auto& n1 = normals[trig->mP1];
const auto& n2 = normals[trig->mP2];
const auto& n3 = normals[trig->mP3];
auto v0 = points[trig->mP1];
auto v1 = points[trig->mP2];
auto v2 = points[trig->mP3];
// Calculate barycentric coordinates
Vec3F barycentric;
// Calculate the area of the triangle
auto areaABC = (halnf) (v1 - v0).cross(v2 - v0).length();
auto areaPBC = (halnf) (v1 - castData.hitPos).cross(v2 - castData.hitPos).length();
auto areaPCA = (halnf) (v2 - castData.hitPos).cross(v0 - castData.hitPos).length();
// Calculate the barycentric coordinates
barycentric.x = areaPBC / areaABC;
barycentric.y = areaPCA / areaABC;
barycentric.z = 1.0f - barycentric.x - barycentric.y;
// Interpolate the normal using barycentric coordinates
normal = n1 * barycentric.x + n2 * barycentric.y + n3 * barycentric.z;
}
*/
using namespace tp;
// TODO : de-duplicate in Scene?
void RayTracer::castRay(const Ray& ray, RayCastData& out, alnf farVal) {
out.hit = false;
out.obj = nullptr;
farVal *= farVal;
for (auto obj : mScene->mObjects) {
for (auto trig : obj->mCache.TrigCaches) {
if (trig->castRay(ray)) {
// printf("Hit\n");
auto dist = (trig->getHitPos() - ray.pos).length2();
if (farVal > dist && dist > EPSILON) {
out.trig = &trig.data();
out.hitPos = trig->getHitPos();
out.obj = &obj.data();
out.hit = true;
farVal = dist;
}
}
}
}
}
void RayTracer::cycle(const RayCastData& castData, LightData& out, uhalni depth) {
void RayTracer::cycle(const RayHit& castData, LightData& out, uhalni depth) {
if (depth) {
depth--;
@ -82,7 +22,6 @@ void RayTracer::cycle(const RayCastData& castData, LightData& out, uhalni depth)
const auto delta2 = normal.cross(delta1);
for (auto idx : IterRange(mSettings.spray)) {
RayCastData materialCastData;
LightData lightData;
auto d1 = ((halnf) randomFloat() - 0.5f) * 2;
@ -90,9 +29,9 @@ void RayTracer::cycle(const RayCastData& castData, LightData& out, uhalni depth)
auto sprayNormal = (normal + delta1 * d1 + delta2 * d2).normalize();
castRay({ sprayNormal, castData.hitPos }, materialCastData, mScene->mCamera.getFar());
if (materialCastData.hit) {
cycle(materialCastData, lightData, depth);
const auto bounce = mScene->castRay({ sprayNormal, castData.position }, mScene->mCamera.getFar());
if (bounce) {
cycle(*bounce, lightData, depth);
out.intensity += lightData.intensity * 0.2;
}
}
@ -100,18 +39,15 @@ void RayTracer::cycle(const RayCastData& castData, LightData& out, uhalni depth)
// cast for light
for (auto light : mScene->mLights) {
RayCastData lightCastData;
auto dir = light->pos - castData.hitPos;
auto dir = light->pos - castData.position;
auto length = (halnf) dir.length();
Ray lightRay = { dir.unitV(), castData.hitPos };
Ray lightRay = { dir.unitV(), castData.position };
if (lightRay.dir.dot(castData.trig->mNormal) < 0) {
continue;
}
castRay(lightRay, lightCastData, length);
if (lightCastData.hit) {
if (mScene->castRay(lightRay, length)) {
continue;
}
@ -138,8 +74,6 @@ void RayTracer::render(const Scene& scene, OutputBuffers& out, const RenderSetti
const auto up = (planeRightBottom - planeRightTop);
const auto right = planeRightTop - planeLeftTop;
RayCastData castData;
Ray ray = { { 0, 0, 0 }, pos };
Vec3F iterPoint = { 0, 0, 0 };
@ -184,21 +118,21 @@ void RayTracer::render(const Scene& scene, OutputBuffers& out, const RenderSetti
iterPoint = planeLeftTop + ((deltaX * (halnf) (i + randX)) + (deltaY * (halnf) (j + randY)));
ray.dir = (iterPoint - pos).unitV();
castRay(ray, castData, mScene->mCamera.getFar());
const auto castData = mScene->castRay(ray, mScene->mCamera.getFar());
halni albedoColor = abs(hash((ualni) castData.obj));
halni albedoColor = abs(hash((alni) (castData ? castData->object->id() : 0)));
halnf albedoColorR = float((albedoColor & 0x00000011) % 155) + 100;
halnf albedoColorG = float((albedoColor & 0x00001100) % 155) + 100;
halnf albedoColorB = float((albedoColor & 0x00110000) % 155) + 100;
out.albedo.set({ i, j }, { albedoColorR, albedoColorG, albedoColorB, 1.f });
if (castData.hit) {
if (castData) {
LightData lightData;
cycle(castData, lightData, mSettings.depth);
cycle(*castData, lightData, mSettings.depth);
const auto normal = castData.trig->getNormal();
const auto depth = (halnf) (castData.hitPos - ray.pos).length();
const auto normal = castData->trig->getNormal();
const auto depth = castData->distance;
lightData.intensity = clamp(lightData.intensity, 0.f, 1.f);
RGBA col = { lightData.intensity, lightData.intensity, lightData.intensity, 1.f };

View file

@ -2,6 +2,7 @@
#pragma once
#include "Scene.hpp"
#include "Buffer2D.hpp"
#include "Color.hpp"
#include "Module.hpp"
#include "Vec.hpp"
@ -35,8 +36,7 @@ namespace tp {
};
private:
void castRay(const Ray& ray, RayCastData& out, alnf far);
void cycle(const RayCastData& ray, LightData& out, uhalni depth);
void cycle(const RayHit& hit, LightData& out, uhalni depth);
private:
RenderSettings mSettings;

View file

@ -58,8 +58,7 @@ SUITE(RayTracer) {
scene.mLights.append({ { 0, 0, 1.1f }, 1.f, 0.3f });
scene.mObjects.append(Object());
auto& object = scene.mObjects.last();
Object object;
object.mTopology.Points = {
{ 1.000000, 0.000000, 0.000000 },
@ -77,8 +76,8 @@ SUITE(RayTracer) {
{ 0, 1, 2 },
};
object.mCache.Source = &object.mTopology;
object.mCache.updateCache();
object.updateCache();
scene.addObject(object);
RenderSettings settings = {
0,

View file

@ -1,22 +1,46 @@
project(Sketch3D)
### ---------------------- Externals --------------------- ###
set(BINDINGS_INCLUDE ../Externals/glfw/include)
set(BINDINGS_LIBS glfw Imgui)
find_package(Vulkan REQUIRED)
find_program(GLSLC glslc HINTS ${Vulkan_GLSLC_EXECUTABLE} REQUIRED)
### ---------------------- Static Library --------------------- ###
file(GLOB SOURCES "./private/*.cpp" "./private/*/*.cpp")
file(GLOB HEADERS "./public/*.hpp" "./public/*/*.hpp")
### ---------------------- Shaders --------------------- ###
set(SHADER_GEN_DIR ${CMAKE_CURRENT_BINARY_DIR}/shaders_generated)
set(SHADER_INCS "")
foreach(SHADER stroke.vert stroke.geom stroke.frag)
set(SHADER_SRC ${CMAKE_CURRENT_SOURCE_DIR}/shaders/${SHADER})
set(SHADER_INC ${SHADER_GEN_DIR}/${SHADER}.inc)
add_custom_command(
OUTPUT ${SHADER_INC}
COMMAND ${CMAKE_COMMAND} -E make_directory ${SHADER_GEN_DIR}
COMMAND ${GLSLC} -O --target-env=vulkan1.0 -mfmt=c ${SHADER_SRC} -o ${SHADER_INC}
DEPENDS ${SHADER_SRC}
COMMENT "Compiling ${SHADER} to SPIR-V"
VERBATIM
)
list(APPEND SHADER_INCS ${SHADER_INC})
endforeach()
add_custom_target(Sketch3DShaders DEPENDS ${SHADER_INCS})
add_library(${PROJECT_NAME} STATIC ${SOURCES} ${HEADERS})
### ---------------------- Libraries --------------------- ###
# Strokes, brushes and the stroke renderer.
add_library(${PROJECT_NAME} STATIC ./private/Sketch3D.cpp ./public/Sketch3D.hpp)
add_dependencies(${PROJECT_NAME} Sketch3DShaders)
target_include_directories(${PROJECT_NAME} PUBLIC ./public/)
target_include_directories(${PROJECT_NAME} PRIVATE ${SHADER_GEN_DIR})
target_link_libraries(${PROJECT_NAME} PUBLIC Math Containers RasterRender)
target_include_directories(${PROJECT_NAME} PUBLIC ./public/ ${BINDINGS_INCLUDE} ./ext/)
target_link_libraries(${PROJECT_NAME} PUBLIC GraphicApplication Connection Widgets Math RasterRender)
target_link_libraries(${PROJECT_NAME} PUBLIC ${BINDINGS_LIBS})
# The widgets of the sketch application.
add_library(${PROJECT_NAME}Gui STATIC ./private/SketchGUI.cpp ./public/SketchGUI.hpp)
target_link_libraries(${PROJECT_NAME}Gui PUBLIC ${PROJECT_NAME} Widgets)
### -------------------------- Applications -------------------------- ###
add_executable(Sketch3DApp ./applications/Entry.cpp)
target_link_libraries(Sketch3DApp ${PROJECT_NAME})
target_link_libraries(Sketch3DApp ${PROJECT_NAME}Gui)
graphic_application_resources(Sketch3DApp)
file(COPY "rsc" DESTINATION "${CMAKE_BINARY_DIR}/${PROJECT_NAME}/")
### -------------------------- Tests -------------------------- ###
if (NOT ANDROID)
add_executable(Test${PROJECT_NAME} ./tests/RendererTests.cpp)
target_link_libraries(Test${PROJECT_NAME} ${PROJECT_NAME} UnitTest++)
add_test(NAME Test${PROJECT_NAME} COMMAND Test${PROJECT_NAME})
endif()

View file

@ -11,7 +11,7 @@ public:
Sketch3DApplication() {
setRoot(&mGui);
mGui.createRenderWidget(&canvas(), { 2560, 1440 });
mGui.createRenderWidget(application().gpu(), &canvas(), { 2560, 1440 });
mGui.setProject(&mSketch);
}

View file

@ -1,26 +1,63 @@
#include "Sketch3D.hpp"
#include <array>
using namespace tp;
StrokeGPUHandles::StrokeGPUHandles() {
glGenVertexArrays(1, &VertexArrayID);
glBindVertexArray(VertexArrayID);
namespace {
glGenBuffers(1, &vertexbuffer);
const uint32_t kStrokeVert[] =
#include "stroke.vert.inc"
;
const uint32_t kStrokeGeom[] =
#include "stroke.geom.inc"
;
const uint32_t kStrokeFrag[] =
#include "stroke.frag.inc"
;
struct StrokeConstants {
Mat4F mvp;
Vec4F color;
Vec4F background;
float ratio = 0;
float target = 0;
float padding[2] = {};
};
static_assert(sizeof(StrokeConstants) == (16 + 4 + 4 + 4) * sizeof(float));
static_assert(sizeof(StrokePoint) == 4 * sizeof(float));
// Note: matches the old GL layout (row 0 at clip y = -1); only depth is remapped to Vulkan's [0, w] in the shader.
constexpr uint4 kSamples = 4;
GraphicsPipeline createStrokePipeline(GpuDevice& gpu, const RenderTarget& target) {
const std::array<ShaderStage, 3> stages = {
ShaderStage{ VK_SHADER_STAGE_VERTEX_BIT, kStrokeVert },
ShaderStage{ VK_SHADER_STAGE_GEOMETRY_BIT, kStrokeGeom },
ShaderStage{ VK_SHADER_STAGE_FRAGMENT_BIT, kStrokeFrag },
};
return GraphicsPipeline(
gpu,
{
.stages = stages,
.vertexStride = sizeof(StrokePoint),
.vertexFormat = VK_FORMAT_R32G32B32A32_SFLOAT,
.topology = VK_PRIMITIVE_TOPOLOGY_LINE_STRIP,
.pushConstantStages = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_GEOMETRY_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
.pushConstantSize = sizeof(StrokeConstants),
.renderPass = target.renderPass(),
.samples = target.samples(),
}
);
}
uint64_t gNextStrokeVersion = 1;
}
void StrokeGPUHandles::sendDataToGPU(Buffer<StrokePoint>* mPoints) {
glBindVertexArray(VertexArrayID);
vbo_len = mPoints->size();
glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(StrokePoint) * vbo_len, mPoints->getBuff(), GL_STATIC_COPY);
}
StrokeGPUHandles::~StrokeGPUHandles() {
glDeleteBuffers(1, &vertexbuffer);
glDeleteVertexArrays(1, &VertexArrayID);
}
void Stroke::updateGpuBuffers() { mVersion = gNextStrokeVersion++; }
Stroke::Stroke() = default;
@ -32,8 +69,6 @@ void Stroke::setColor(const RGBA& col) { mColor = col; }
const RGBA& Stroke::getColor() const { return mColor; }
void Stroke::updateGpuBuffers() { mGPUHandles.sendDataToGPU(&mPoints); }
void Stroke::denoisePos(halni passes) {
for (auto pass : IterRange(passes)) {
for (auto pi : IterRange(mPoints.size() - 2)) {
@ -347,115 +382,84 @@ Project::~Project() {
}
}
Renderer::Renderer(Vec2F size) :
mBuffer(size, 4),
mBufferDowncast(size),
mShader("rsc/shaders/stroke.vert", "rsc/shaders/stroke.geom", "rsc/shaders/stroke.frag") {
mMaxSize = size;
Renderer::Renderer(GpuDevice& gpu, Vec2<uint4> maxSize) :
mGpu(gpu),
mMaxSize(maxSize),
mTarget(gpu, maxSize, kSamples),
mPipeline(createStrokePipeline(gpu, mTarget)) {}
mMatrixUniform = mShader.getu("MVP");
mColorUniform = mShader.getu("Color");
mRatioUniform = mShader.getu("Ratio");
mTargetUniform = mShader.getu("Target");
mBGColUniform = mShader.getu("BGCol");
mBuffer.mClearCol = 0.f;
mBufferDowncast.mClearCol = 0.f;
}
Renderer::~Renderer() = default;
void Renderer::renderToTexture(const Project* project, Vec2F size) {
size.clamp({ 1, 1 }, mMaxSize);
size.clamp({ 1, 1 }, { (halnf) mMaxSize.x, (halnf) mMaxSize.y });
mTarget.resize({ (uint4) size.x, (uint4) size.y });
mClearColor = project->mBackgroundColor;
mUsedThisFrame.clear();
setViewport({ 0, 0, size.x, size.y });
setClearCol(project->mBackgroundColor);
renderBegin();
mTarget.render(mClearColor, [&](VkCommandBuffer commandBuffer) {
mCommands = commandBuffer;
mPipeline.bind(commandBuffer);
for (auto lay : project->mLayers) {
if (lay.data()->enabled) {
for (auto str : lay.data()->strokes) {
drawStroke(str.data(), &project->mCamera);
for (auto layer : project->mLayers) {
if (!layer.data()->enabled) {
continue;
}
for (auto stroke : layer.data()->strokes) {
drawStroke(stroke.data(), &project->mCamera);
}
}
}
auto idx = project->mBrushes.presents(project->mActiveBrush);
if (idx) {
auto brush = project->mBrushes.getSlotVal(idx);
brush->draw(this, &project->mCamera);
}
if (auto index = project->mBrushes.presents(project->mActiveBrush)) {
project->mBrushes.getSlotVal(index)->draw(this, &project->mCamera);
}
mCommands = VK_NULL_HANDLE;
});
renderEnd();
releaseUnusedMeshes();
}
void Renderer::renderBegin() {
mBuffer.beginDraw();
mBuffer.clear();
const Renderer::StrokeMesh* Renderer::meshOf(const Stroke* stroke) {
mUsedThisFrame.insert(stroke);
StrokeMesh& mesh = mMeshes[stroke];
if (mesh.version == stroke->version() && mesh.vertices) {
return &mesh;
}
const auto& points = stroke->getPoints();
const size_t bytes = sizeof(StrokePoint) * points.size();
if (!mesh.vertices || mesh.vertices->capacity() < bytes) {
mesh.vertices = std::make_unique<GpuBuffer>(mGpu, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, bytes);
}
mesh.vertices->write(points.getBuff(), bytes);
mesh.pointCount = (uint32_t) points.size();
mesh.version = stroke->version();
return &mesh;
}
void Renderer::setViewport(RectF viewport) { mBuffer.setViewport(viewport); }
void Renderer::releaseUnusedMeshes() {
std::erase_if(mMeshes, [this](const auto& entry) { return !mUsedThisFrame.contains(entry.first); });
}
void Renderer::drawStroke(const Stroke* str, const Camera* camera) {
// return;
void Renderer::drawStroke(const Stroke* stroke, const Camera* camera) {
// Note: strokes are only drawable once prepared (updateGpuBuffers) and while renderToTexture records.
if (!mCommands || stroke->version() == 0 || stroke->getPoints().size() < 2) {
return;
}
const StrokeMesh* mesh = meshOf(stroke);
glEnable(GL_DEPTH_TEST);
glEnable(GL_MULTISAMPLE);
GLint val;
glGetIntegerv(GL_SAMPLES, &val);
glBindVertexArray(str->mGPUHandles.VertexArrayID);
mShader.bind();
auto cam_mat = camera->calculateTransformationMatrix().transpose();
glUniformMatrix4fv(mMatrixUniform, 1, GL_FALSE, &cam_mat[0][0]);
glUniform4fv(mColorUniform, 1, &str->mColor.r);
glUniform4fv(mBGColUniform, 1, &mBuffer.mClearCol.r);
auto ratio = camera->getRatio();
glUniform1fv(mRatioUniform, 1, &ratio);
auto target = halnf(
const auto& color = stroke->getColor();
StrokeConstants constants;
constants.mvp = camera->calculateTransformationMatrix().transpose();
constants.color = Vec4F(color.r, color.g, color.b, color.a);
constants.background = Vec4F(mClearColor.r, mClearColor.g, mClearColor.b, mClearColor.a);
constants.ratio = camera->getRatio();
constants.target = halnf(
((camera->getTarget() - camera->getPos()).length() - camera->getNear()) / (camera->getFar() - camera->getNear())
);
mPipeline.push(mCommands, constants);
glUniform1fv(mTargetUniform, 1, &target);
// 1st attribute buffer : vertices
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, str->mGPUHandles.vertexbuffer);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, (void*) 0);
glDrawArrays(GL_LINE_STRIP, 0, str->mGPUHandles.vbo_len);
glDisableVertexAttribArray(0);
mShader.unbind();
const VkBuffer vertices = mesh->vertices->handle();
const VkDeviceSize offset = 0;
vkCmdBindVertexBuffers(mCommands, 0, 1, &vertices, &offset);
vkCmdDraw(mCommands, mesh->pointCount, 1, 0, 0);
}
void Renderer::renderEnd() {
mBuffer.endDraw();
mBufferDowncast.beginDraw();
auto size = mBufferDowncast.getSize();
glBindFramebuffer(GL_READ_FRAMEBUFFER, mBuffer.buffId());
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mBufferDowncast.buffId());
mBufferDowncast.clear();
glBlitFramebuffer(0, 0, size.x, size.y, 0, 0, size.x, size.y, GL_COLOR_BUFFER_BIT, GL_NEAREST);
mBufferDowncast.endDraw();
}
uhalni Renderer::getTextudeId() { return mBufferDowncast.texId(); }
RenderBuffer* Renderer::getBuff() { return &mBufferDowncast; }
void Renderer::setClearCol(RGBA col) {
mBuffer.mClearCol = col;
mBufferDowncast.mClearCol = col;
}
Renderer::~Renderer() {}

View file

@ -3,9 +3,9 @@
using namespace tp;
SketchRenderWidget::SketchRenderWidget(Canvas& canvas, Vec2F renderResolution) :
mRenderer(renderResolution) {
mImage = canvas.createImageFromRenderTarget(mRenderer.getBuff()->texId());
SketchRenderWidget::SketchRenderWidget(GpuDevice& gpu, Canvas& canvas, Vec2F renderResolution) :
mRenderer(gpu, { (uint4) renderResolution.x, (uint4) renderResolution.y }) {
mImage = canvas.createImageFromRenderTarget(mRenderer.target().registryId());
mCanvas = &canvas;
setSizePolicy(SizePolicy::Expanding, SizePolicy::Expanding);
@ -17,6 +17,8 @@ void SketchRenderWidget::draw(Canvas& canvas) {
auto area = getArea().relative();
if (mProject) {
mRenderer.renderToTexture(mProject, area.size);
// Note: resizing recreates the target images, so the Canvas image is pointed at them every frame.
canvas.updateImageFromRenderTarget(mImage, mRenderer.target().registryId());
canvas.drawImage(area, mImage, 0, 1, 12);
}
}
@ -150,8 +152,8 @@ SketchGUI::SketchGUI() :
mSelectEraser.setText("Eraser TODO");
}
void SketchGUI::createRenderWidget(Canvas* canvas, const Vec2F& renderResolution) {
mRenderWidget = new SketchRenderWidget(*canvas, renderResolution);
void SketchGUI::createRenderWidget(GpuDevice& gpu, Canvas* canvas, const Vec2F& renderResolution) {
mRenderWidget = new SketchRenderWidget(gpu, *canvas, renderResolution);
mViewport.setRenderWidget(mRenderWidget);
mViewport.setToolbarWidget(&mToolbar);

View file

@ -6,9 +6,13 @@
#include "Topology.hpp"
#include "Color.hpp"
#include "GraphicsApi.hpp"
#include "FrameBuffer.hpp"
#include "Shader.hpp"
#include "RenderTarget.hpp"
#include "GraphicsPipeline.hpp"
#include "GpuBuffer.hpp"
#include <memory>
#include <unordered_map>
#include <unordered_set>
namespace tp {
@ -22,44 +26,37 @@ namespace tp {
halnf thickness = 0;
};
struct StrokeGPUHandles {
GLuint VertexArrayID = 0;
GLuint vertexbuffer = 0;
GLuint vbo_len = 0;
StrokeGPUHandles();
void sendDataToGPU(Buffer<StrokePoint>* mPoints);
~StrokeGPUHandles();
};
// Draws a Project's strokes into an offscreen RenderTarget.
class Renderer {
public:
Renderer(Vec2F size); // max render size
Renderer(GpuDevice& gpu, Vec2<uint4> maxSize);
~Renderer();
void renderToTexture(const Project* project, Vec2F size); // needed size
[[nodiscard]] RenderTarget& target() { return mTarget; }
void renderBegin();
void setViewport(RectF viewport);
void drawStroke(const Stroke* str, const Camera* camera);
void renderEnd();
void setClearCol(RGBA col);
uhalni getTextudeId();
RenderBuffer* getBuff();
// Renders at `size`, clamped to the maximum size given at construction.
void renderToTexture(const Project* project, Vec2F size);
void drawStroke(const Stroke* stroke, const Camera* camera);
private:
RenderBuffer mBufferDowncast;
RenderBuffer mBuffer;
RenderShader mShader;
struct StrokeMesh {
std::unique_ptr<GpuBuffer> vertices;
uint32_t pointCount = 0;
uint64_t version = 0;
};
// shader uniforms
GLuint mMatrixUniform = 0;
GLuint mColorUniform = 0;
GLuint mRatioUniform = 0;
GLuint mTargetUniform = 0;
GLuint mBGColUniform = 0;
[[nodiscard]] const StrokeMesh* meshOf(const Stroke* stroke);
void releaseUnusedMeshes();
Vec2F mMaxSize;
GpuDevice& mGpu;
Vec2<uint4> mMaxSize;
RenderTarget mTarget;
GraphicsPipeline mPipeline;
std::unordered_map<const Stroke*, StrokeMesh> mMeshes;
std::unordered_set<const Stroke*> mUsedThisFrame;
VkCommandBuffer mCommands = VK_NULL_HANDLE;
RGBA mClearColor;
};
class Stroke {
@ -69,7 +66,10 @@ namespace tp {
void denoiseThickness(halni passes);
void compress(halnf factor);
void subdiv(halnf precition, const Camera* cam, halni passes = 1);
// Marks the points as changed so renderers upload them again.
void updateGpuBuffers();
// Unique across all strokes, so a renderer never mistakes a new stroke at a reused address for an old one.
[[nodiscard]] uint64_t version() const { return mVersion; }
Buffer<StrokePoint>& getPoints();
const Buffer<StrokePoint>& getPoints() const;
void setColor(const RGBA& col);
@ -79,8 +79,7 @@ namespace tp {
Buffer<StrokePoint> mPoints;
RGBA mColor;
friend Renderer;
StrokeGPUHandles mGPUHandles;
uint64_t mVersion = 0;
};
struct Layer {

View file

@ -10,7 +10,7 @@ namespace tp {
class SketchRenderWidget : public Widget {
public:
SketchRenderWidget(Canvas& canvas, Vec2F renderResolution);
SketchRenderWidget(GpuDevice& gpu, Canvas& canvas, Vec2F renderResolution);
~SketchRenderWidget() override;
void draw(Canvas& canvas) override;
@ -61,7 +61,7 @@ namespace tp {
class SketchGUI : public DockWidget {
public:
SketchGUI();
void createRenderWidget(Canvas* canvas, const Vec2F& renderResolution);
void createRenderWidget(GpuDevice& gpu, Canvas* canvas, const Vec2F& renderResolution);
void setProject(Project* project);
~SketchGUI() override;

View file

@ -1,15 +0,0 @@
#version 330 core
in vec2 UV;
out vec4 color;
uniform sampler2D renderedTexture;
void main(){
vec4 texColor = texture(renderedTexture, UV);
if(texColor.a < 0.2)
discard;
color = texColor;
}

View file

@ -1,12 +0,0 @@
#version 330 core
// Input vertex data, different for all executions of this shader.
layout(location = 0) in vec3 vertexPosition_modelspace;
// Output data ; will be interpolated for each fragment.
out vec2 UV;
void main(){
gl_Position = vec4(vertexPosition_modelspace.x, vertexPosition_modelspace.y, -0.99, 1);
UV = (vertexPosition_modelspace.xy + vec2(1, 1)) / 2.0;
}

View file

@ -1,28 +0,0 @@
#version 330 core
uniform vec4 Color;
layout(location = 0) out vec4 color;
uniform float Target;
uniform vec4 BGCol;
#define A 0.1f
void main() {
float x = gl_FragCoord.z;
float val = A;
float f = Target * 2;
if (x < f) {
val = ((A - 1) / f) * x + 1;
}
val = clamp(val, A, 1);
color = vec4((Color.xyz - BGCol.xyz) * val + BGCol.xyz, 1.f);
}

View file

@ -1,170 +0,0 @@
#version 330 core
#define PI 3.14
layout (lines) in;
layout (triangle_strip, max_vertices = 16) out;
in float thickness[];
uniform float Ratio;
void main2() {
float r = sqrt(Ratio);
vec4 l1 = gl_in[0].gl_Position;
vec4 l2 = gl_in[1].gl_Position;
vec2 l1p = l1.xy / l1.w;
vec2 l2p = l2.xy / l2.w;
float t1 = thickness[0] / l1.w;
float t2 = thickness[1] / l2.w;
vec2 l = normalize(vec2(l2p.x / r, l2p.y * r) - vec2(l1p.x / r, l1p.y * r));
vec2 tmp = vec2(-l.y, l.x);
vec2 tmp_poj = vec2(tmp.x * r, tmp.y / r);
vec2 l_poj = vec2(l.x * r, l.y / r);
vec2 p1 = l1p.xy + (tmp_poj * t1);
vec2 p2 = l1p.xy - (tmp_poj * t1);
vec2 p3 = l2p.xy + (tmp_poj * t2);
vec2 p4 = l2p.xy - (tmp_poj * t2);
gl_Position = vec4(p1.x, p1.y , l1.z, 1);
EmitVertex();
gl_Position = vec4(p2.x, p2.y, l1.z, 1);
EmitVertex();
gl_Position = vec4(p3.x, p3.y, l2.z, 1);
EmitVertex();
gl_Position = vec4(p4.x, p4.y, l2.z, 1);
EmitVertex();
EndPrimitive();
p1 = l1p.xy + (tmp_poj * t1);
p2 = l1p.xy - (tmp_poj * t1);
p3 = l1p.xy + (tmp_poj * t1) - (l_poj * t1) / 2.f;
p4 = l1p.xy - (tmp_poj * t1) - (l_poj * t1) / 2.f;
vec2 p5 = l1p.xy + (tmp_poj * t1) / 2.f - (l_poj * t1);
vec2 p6 = l1p.xy - (tmp_poj * t1) / 2.f - (l_poj * t1);
gl_Position = vec4(p1.x, p1.y, l1.z, 1);
EmitVertex();
gl_Position = vec4(p2.x, p2.y, l1.z, 1);
EmitVertex();
gl_Position = vec4(p3.x, p3.y, l1.z, 1);
EmitVertex();
gl_Position = vec4(p4.x, p4.y, l1.z, 1);
EmitVertex();
gl_Position = vec4(p5.x, p5.y, l1.z, 1);
EmitVertex();
gl_Position = vec4(p6.x, p6.y, l1.z, 1);
EmitVertex();
EndPrimitive();
p1 = l2p.xy + (tmp_poj * t2);
p2 = l2p.xy - (tmp_poj * t2);
p3 = l2p.xy + (tmp_poj * t2) + (l_poj * t2) / 2.f;
p4 = l2p.xy - (tmp_poj * t2) + (l_poj * t2) / 2.f;
p5 = l2p.xy + (tmp_poj * t2) / 2.f + (l_poj * t2);
p6 = l2p.xy - (tmp_poj * t2) / 2.f + (l_poj * t2);
gl_Position = vec4(p1.x, p1.y, l2.z, 1);
EmitVertex();
gl_Position = vec4(p2.x, p2.y, l2.z, 1);
EmitVertex();
gl_Position = vec4(p3.x, p3.y, l2.z, 1);
EmitVertex();
gl_Position = vec4(p4.x, p4.y, l2.z, 1);
EmitVertex();
gl_Position = vec4(p5.x, p5.y, l2.z, 1);
EmitVertex();
gl_Position = vec4(p6.x, p6.y, l2.z, 1);
EmitVertex();
EndPrimitive();
}
void main() {
float r = sqrt(Ratio);
vec4 l1 = gl_in[0].gl_Position;
vec4 l2 = gl_in[1].gl_Position;
vec2 l1p = l1.xy / l1.w;
vec2 l2p = l2.xy / l2.w;
float t1 = thickness[0] / l1.w;
float t2 = thickness[1] / l2.w;
vec2 l = normalize(vec2(l2p.x / r, l2p.y * r) - vec2(l1p.x / r, l1p.y * r));
vec2 tmp = vec2(-l.y, l.x);
vec2 tmp_poj = vec2(tmp.x * r, tmp.y / r);
vec2 l_poj = vec2(l.x * r, l.y / r);
vec2 p1 = l1p.xy + (tmp_poj * t1);
vec2 p2 = l1p.xy - (tmp_poj * t1);
vec2 p3 = l2p.xy + (tmp_poj * t2);
vec2 p4 = l2p.xy - (tmp_poj * t2);
gl_Position = vec4(p1.x * l1.w, p1.y * l1.w, l1.z, l1.w);
EmitVertex();
gl_Position = vec4(p2.x * l1.w, p2.y * l1.w, l1.z, l1.w);
EmitVertex();
gl_Position = vec4(p3.x * l2.w, p3.y * l2.w, l2.z, l2.w);
EmitVertex();
gl_Position = vec4(p4.x * l2.w, p4.y * l2.w, l2.z, l2.w);
EmitVertex();
EndPrimitive();
p1 = l1p.xy + (tmp_poj * t1);
p2 = l1p.xy - (tmp_poj * t1);
p3 = l1p.xy + (tmp_poj * t1) - (l_poj * t1) / 2.f;
p4 = l1p.xy - (tmp_poj * t1) - (l_poj * t1) / 2.f;
vec2 p5 = l1p.xy + (tmp_poj * t1) / 2.f - (l_poj * t1);
vec2 p6 = l1p.xy - (tmp_poj * t1) / 2.f - (l_poj * t1);
gl_Position = vec4(p1.x * l1.w, p1.y * l1.w, l1.z, 1 * l1.w);
EmitVertex();
gl_Position = vec4(p2.x * l1.w, p2.y * l1.w, l1.z, 1 * l1.w);
EmitVertex();
gl_Position = vec4(p3.x * l1.w, p3.y * l1.w, l1.z, 1 * l1.w);
EmitVertex();
gl_Position = vec4(p4.x * l1.w, p4.y * l1.w, l1.z, 1 * l1.w);
EmitVertex();
gl_Position = vec4(p5.x * l1.w, p5.y * l1.w, l1.z, 1 * l1.w);
EmitVertex();
gl_Position = vec4(p6.x * l1.w, p6.y * l1.w, l1.z, 1 * l1.w);
EmitVertex();
EndPrimitive();
p1 = l2p.xy + (tmp_poj * t2);
p2 = l2p.xy - (tmp_poj * t2);
p3 = l2p.xy + (tmp_poj * t2) + (l_poj * t2) / 2.f;
p4 = l2p.xy - (tmp_poj * t2) + (l_poj * t2) / 2.f;
p5 = l2p.xy + (tmp_poj * t2) / 2.f + (l_poj * t2);
p6 = l2p.xy - (tmp_poj * t2) / 2.f + (l_poj * t2);
gl_Position = vec4(p1.x * l2.w, p1.y * l2.w, l2.z, 1 * l2.w);
EmitVertex();
gl_Position = vec4(p2.x * l2.w, p2.y * l2.w, l2.z, 1 * l2.w);
EmitVertex();
gl_Position = vec4(p3.x * l2.w, p3.y * l2.w, l2.z, 1 * l2.w);
EmitVertex();
gl_Position = vec4(p4.x * l2.w, p4.y * l2.w, l2.z, 1 * l2.w);
EmitVertex();
gl_Position = vec4(p5.x * l2.w, p5.y * l2.w, l2.z, 1 * l2.w);
EmitVertex();
gl_Position = vec4(p6.x * l2.w, p6.y * l2.w, l2.z, 1 * l2.w);
EmitVertex();
EndPrimitive();
}

View file

@ -1,16 +0,0 @@
#version 330 core
layout(location = 0) in vec4 point;
out float thickness;
uniform mat4 MVP;
void main() {
thickness = point.w;
// gl_Position = MVP * vec4(point.xyz, 1);
vec4 vec = MVP * vec4(point.xyz, 1);
vec.z *= vec.w;
gl_Position = vec;
}

View file

@ -0,0 +1,28 @@
#version 450
layout(location = 0) out vec4 color;
layout(push_constant) uniform PushConstants {
mat4 MVP;
vec4 Color;
vec4 BGCol;
float Ratio;
float Target;
} pc;
#define A 0.1
void main() {
float x = gl_FragCoord.z;
float val = A;
float f = pc.Target * 2.0;
if (x < f) {
val = ((A - 1.0) / f) * x + 1.0;
}
val = clamp(val, A, 1.0);
color = vec4((pc.Color.xyz - pc.BGCol.xyz) * val + pc.BGCol.xyz, 1.0);
}

View file

@ -0,0 +1,89 @@
#version 450
layout(lines) in;
layout(triangle_strip, max_vertices = 16) out;
layout(location = 0) in float thickness[];
layout(push_constant) uniform PushConstants {
mat4 MVP;
vec4 Color;
vec4 BGCol;
float Ratio;
float Target;
} pc;
void main() {
float r = sqrt(pc.Ratio);
vec4 l1 = gl_in[0].gl_Position;
vec4 l2 = gl_in[1].gl_Position;
vec2 l1p = l1.xy / l1.w;
vec2 l2p = l2.xy / l2.w;
float t1 = thickness[0] / l1.w;
float t2 = thickness[1] / l2.w;
vec2 l = normalize(vec2(l2p.x / r, l2p.y * r) - vec2(l1p.x / r, l1p.y * r));
vec2 tmp = vec2(-l.y, l.x);
vec2 tmp_poj = vec2(tmp.x * r, tmp.y / r);
vec2 l_poj = vec2(l.x * r, l.y / r);
vec2 p1 = l1p.xy + (tmp_poj * t1);
vec2 p2 = l1p.xy - (tmp_poj * t1);
vec2 p3 = l2p.xy + (tmp_poj * t2);
vec2 p4 = l2p.xy - (tmp_poj * t2);
gl_Position = vec4(p1.x * l1.w, p1.y * l1.w, l1.z, l1.w);
EmitVertex();
gl_Position = vec4(p2.x * l1.w, p2.y * l1.w, l1.z, l1.w);
EmitVertex();
gl_Position = vec4(p3.x * l2.w, p3.y * l2.w, l2.z, l2.w);
EmitVertex();
gl_Position = vec4(p4.x * l2.w, p4.y * l2.w, l2.z, l2.w);
EmitVertex();
EndPrimitive();
p1 = l1p.xy + (tmp_poj * t1);
p2 = l1p.xy - (tmp_poj * t1);
p3 = l1p.xy + (tmp_poj * t1) - (l_poj * t1) / 2.0;
p4 = l1p.xy - (tmp_poj * t1) - (l_poj * t1) / 2.0;
vec2 p5 = l1p.xy + (tmp_poj * t1) / 2.0 - (l_poj * t1);
vec2 p6 = l1p.xy - (tmp_poj * t1) / 2.0 - (l_poj * t1);
gl_Position = vec4(p1.x * l1.w, p1.y * l1.w, l1.z, l1.w);
EmitVertex();
gl_Position = vec4(p2.x * l1.w, p2.y * l1.w, l1.z, l1.w);
EmitVertex();
gl_Position = vec4(p3.x * l1.w, p3.y * l1.w, l1.z, l1.w);
EmitVertex();
gl_Position = vec4(p4.x * l1.w, p4.y * l1.w, l1.z, l1.w);
EmitVertex();
gl_Position = vec4(p5.x * l1.w, p5.y * l1.w, l1.z, l1.w);
EmitVertex();
gl_Position = vec4(p6.x * l1.w, p6.y * l1.w, l1.z, l1.w);
EmitVertex();
EndPrimitive();
p1 = l2p.xy + (tmp_poj * t2);
p2 = l2p.xy - (tmp_poj * t2);
p3 = l2p.xy + (tmp_poj * t2) + (l_poj * t2) / 2.0;
p4 = l2p.xy - (tmp_poj * t2) + (l_poj * t2) / 2.0;
p5 = l2p.xy + (tmp_poj * t2) / 2.0 + (l_poj * t2);
p6 = l2p.xy - (tmp_poj * t2) / 2.0 + (l_poj * t2);
gl_Position = vec4(p1.x * l2.w, p1.y * l2.w, l2.z, l2.w);
EmitVertex();
gl_Position = vec4(p2.x * l2.w, p2.y * l2.w, l2.z, l2.w);
EmitVertex();
gl_Position = vec4(p3.x * l2.w, p3.y * l2.w, l2.z, l2.w);
EmitVertex();
gl_Position = vec4(p4.x * l2.w, p4.y * l2.w, l2.z, l2.w);
EmitVertex();
gl_Position = vec4(p5.x * l2.w, p5.y * l2.w, l2.z, l2.w);
EmitVertex();
gl_Position = vec4(p6.x * l2.w, p6.y * l2.w, l2.z, l2.w);
EmitVertex();
EndPrimitive();
}

View file

@ -0,0 +1,23 @@
#version 450
layout(location = 0) in vec4 point;
layout(location = 0) out float thickness;
layout(push_constant) uniform PushConstants {
mat4 MVP;
vec4 Color;
vec4 BGCol;
float Ratio;
float Target;
} pc;
void main() {
thickness = point.w;
vec4 clip = pc.MVP * vec4(point.xyz, 1.0);
// Note: depth is kept linear (z * w, divided back by w) and remapped from OpenGL [-w, w] to Vulkan [0, w].
clip.z *= clip.w;
clip.z = (clip.z + clip.w) * 0.5;
gl_Position = clip;
}

View file

@ -0,0 +1,58 @@
#include "Sketch3D.hpp"
#include "UnitTest++/UnitTest++.h"
using namespace tp;
namespace {
constexpr uint4 kSize = 64;
struct Fixture {
// Note: GpuDevice always enables the swapchain extension, which requires the surface instance extension.
GpuDevice gpu{ { VK_KHR_SURFACE_EXTENSION_NAME } };
Renderer renderer{ gpu, { kSize, kSize } };
Project project;
Fixture() { project.mBackgroundColor = RGBA(0.f, 0.f, 0.f, 1.f); }
Stroke* addStroke(halnf thickness) {
auto stroke = new Stroke();
stroke->setColor(RGBA(1.f, 1.f, 1.f, 1.f));
stroke->getPoints().append({ project.mCamera.project(Vec2F(-0.5f, 0.f)), thickness });
stroke->getPoints().append({ project.mCamera.project(Vec2F(0.5f, 0.f)), thickness });
stroke->updateGpuBuffers();
project.mLayers[0]->strokes.pushBack(stroke);
return stroke;
}
uint32_t centerPixel() {
renderer.renderToTexture(&project, { kSize, kSize });
return renderer.target().readPixels()[kSize / 2 * kSize + kSize / 2];
}
};
uint8_t red(uint32_t pixel) { return (uint8_t) pixel; }
}
SUITE(Sketch3D) {
TEST_FIXTURE(Fixture, DrawsAStrokeInItsColorOverTheBackground) {
CHECK_EQUAL(0, red(centerPixel()));
addStroke(0.05f);
CHECK(red(centerPixel()) > 100);
}
TEST_FIXTURE(Fixture, RedrawsAfterPointsChange) {
Stroke* stroke = addStroke(0.05f);
CHECK(red(centerPixel()) > 100);
for (ualni index = 0; index < stroke->getPoints().size(); index++) {
stroke->getPoints()[index].pos.z += 10;
}
stroke->updateGpuBuffers();
CHECK_EQUAL(0, red(centerPixel()));
}
}
int main() { return UnitTest::RunAllTests(); }