From 8dac345afec8ce7cf61aa8714fb3739212f12e11 Mon Sep 17 00:00:00 2001 From: agent Date: Thu, 29 Oct 2026 12:51:39 +0300 Subject: [PATCH 1/3] Math: stateless triangle intersection and source-based topology cache - TrigCache::castRay returns std::optional for the hit distance instead of writing a static hit position through static scratch variables, so ray casts are reentrant. - TopologyCache::update takes its source Topology instead of holding a pointer that dangled whenever a Buffer moved the objects it referred to. --- 3DEditor/private/Editor.cpp | 10 +- 3DEditor/public/Editor.hpp | 3 +- 3DScene/private/LuaFormat.cpp | 309 ++++++++++---------------- 3DScene/private/OBJFormat.cpp | 94 ++------ 3DScene/private/Scene.cpp | 49 +++- 3DScene/public/Scene.hpp | 74 +++--- Math/private/Topology.cpp | 93 +++----- Math/public/Topology.hpp | 27 +-- RasterRender/private/FrameBuffer.cpp | 159 ------------- RasterRender/private/GPUBuffers.hpp | 61 ----- RasterRender/private/Render.cpp | 111 --------- RasterRender/private/Shader.cpp | 162 -------------- RasterRender/private/Texture.cpp | 205 ----------------- RasterRender/public/FrameBuffer.hpp | 37 --- RasterRender/public/GraphicsApi.hpp | 19 -- RasterRender/public/RasterRender.hpp | 38 ---- RasterRender/public/Shader.hpp | 32 --- RasterRender/public/Texture.hpp | 29 --- RasterRender/rsc/shaders/default.frag | 7 - RasterRender/rsc/shaders/default.vert | 13 -- RasterRender/rsc/shaders/solid.frag | 7 - RayTracer/applications/Rayt.cpp | 8 +- RayTracer/private/RayTracer.cpp | 92 ++------ RayTracer/public/RayTracer.hpp | 4 +- RayTracer/tests/Test.cpp | 7 +- Sketch3D/rsc/shaders/Texture.frag | 15 -- Sketch3D/rsc/shaders/Texture.vert | 12 - Sketch3D/rsc/shaders/stroke.frag | 28 --- Sketch3D/rsc/shaders/stroke.geom | 170 -------------- Sketch3D/rsc/shaders/stroke.vert | 16 -- 30 files changed, 270 insertions(+), 1621 deletions(-) delete mode 100644 RasterRender/private/FrameBuffer.cpp delete mode 100644 RasterRender/private/GPUBuffers.hpp delete mode 100644 RasterRender/private/Render.cpp delete mode 100644 RasterRender/private/Shader.cpp delete mode 100644 RasterRender/private/Texture.cpp delete mode 100644 RasterRender/public/FrameBuffer.hpp delete mode 100644 RasterRender/public/GraphicsApi.hpp delete mode 100644 RasterRender/public/RasterRender.hpp delete mode 100644 RasterRender/public/Shader.hpp delete mode 100644 RasterRender/public/Texture.hpp delete mode 100644 RasterRender/rsc/shaders/default.frag delete mode 100644 RasterRender/rsc/shaders/default.vert delete mode 100644 RasterRender/rsc/shaders/solid.frag delete mode 100644 Sketch3D/rsc/shaders/Texture.frag delete mode 100644 Sketch3D/rsc/shaders/Texture.vert delete mode 100644 Sketch3D/rsc/shaders/stroke.frag delete mode 100644 Sketch3D/rsc/shaders/stroke.geom delete mode 100644 Sketch3D/rsc/shaders/stroke.vert diff --git a/3DEditor/private/Editor.cpp b/3DEditor/private/Editor.cpp index 9641bf87..b430548c 100644 --- a/3DEditor/private/Editor.cpp +++ b/3DEditor/private/Editor.cpp @@ -26,7 +26,7 @@ void Editor::renderViewport() { case RenderType::RASTER: mRasterRenderer.beginRender(mScene, mScene.mRenderSettings.size); mRasterRenderer.renderDefault(mScene); - if (mActive) mRasterRenderer.renderOutline(mScene.mCamera, *mActive); + if (auto active = getActiveObject()) mRasterRenderer.renderOutline(mScene.mCamera, *active); mRasterRenderer.endRender(); break; @@ -56,7 +56,7 @@ uint4 Editor::getViewportTexID() { } void Editor::loadDefaults() { - mScene.load("rsc/scene/script.lua"); + mScene = Scene::loadLua("rsc/scene/script.lua"); mResetCamera = mScene.mCamera; } @@ -131,10 +131,10 @@ void Editor::selectObject(const Vec2F& screenPos) { 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 hit = mScene.castRay(ray, 1000); + mActiveId = hit ? hit->object->id() : 0; } -Object* Editor::getActiveObject() { return mActive; } +Object* Editor::getActiveObject() { return mScene.findObject(mActiveId); } Scene* Editor::getScene() { return &mScene; } \ No newline at end of file diff --git a/3DEditor/public/Editor.hpp b/3DEditor/public/Editor.hpp index a4c3b0f3..7340db61 100644 --- a/3DEditor/public/Editor.hpp +++ b/3DEditor/public/Editor.hpp @@ -50,6 +50,7 @@ namespace tp { RayTracer::OutputBuffers mPathTracerBuffers; uint4 mPathRenderTexture = 0; - Object* mActive = nullptr; + // Note: 0 means nothing is selected. + uint8 mActiveId = 0; }; } \ No newline at end of file diff --git a/3DScene/private/LuaFormat.cpp b/3DScene/private/LuaFormat.cpp index acf4c9ae..0eaeda4e 100644 --- a/3DScene/private/LuaFormat.cpp +++ b/3DScene/private/LuaFormat.cpp @@ -1,4 +1,3 @@ - #include "Scene.hpp" extern "C" { @@ -7,217 +6,133 @@ extern "C" { } #include -#include +#include -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; + + // 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 + Vec 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 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; } diff --git a/3DScene/private/OBJFormat.cpp b/3DScene/private/OBJFormat.cpp index b97b87f5..10d902ed 100644 --- a/3DScene/private/OBJFormat.cpp +++ b/3DScene/private/OBJFormat.cpp @@ -1,86 +1,30 @@ - #include "Scene.hpp" -extern "C" { -#include "lauxlib.h" -#include "lualib.h" -} - #include "obj/OBJ_Loader.h" -#include +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{ idx1, idx2, idx3 }); + for (size_t index = 0; index + 2 < mesh.Indices.size(); index += 3) { + object.mTopology.Indexes.append( + Vec3{ 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(); - } -} \ No newline at end of file diff --git a/3DScene/private/Scene.cpp b/3DScene/private/Scene.cpp index 6ef03999..29e660aa 100644 --- a/3DScene/private/Scene.cpp +++ b/3DScene/private/Scene.cpp @@ -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; -} \ No newline at end of file +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 Scene::castRay(const Ray& ray, halnf maxDistance) const { + std::optional 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(); + } +} diff --git a/3DScene/public/Scene.hpp b/3DScene/public/Scene.hpp index ac872f5b..e2588d4f 100644 --- a/3DScene/public/Scene.hpp +++ b/3DScene/public/Scene.hpp @@ -1,14 +1,14 @@ #pragma once #include "Topology.hpp" -#include "Buffer2D.hpp" #include "Camera.hpp" +#include +#include #include -struct lua_State; - namespace tp { + struct RenderSettings { uhalni depth = 2; uhalni spray = 1; @@ -16,26 +16,21 @@ namespace tp { Vec2 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 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 mObjects; Buffer mLights; Camera mCamera; - RenderSettings mRenderSettings; - RayCastData mRayCastData; + private: + uint8 mNextObjectId = 1; }; } diff --git a/Math/private/Topology.cpp b/Math/private/Topology.cpp index 96af391d..971fbb05 100644 --- a/Math/private/Topology.cpp +++ b/Math/private/Topology.cpp @@ -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& points) { mOrigin = points[mP1]; mEdgeP1P2 = points[mP2] - points[mP1]; @@ -34,75 +15,51 @@ void TrigCache::updateCache(const Buffer& 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 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); } } diff --git a/Math/public/Topology.hpp b/Math/public/Topology.hpp index 31efc108..fd4da884 100644 --- a/Math/public/Topology.hpp +++ b/Math/public/Topology.hpp @@ -3,29 +3,31 @@ #include "Buffer.hpp" #include "Camera.hpp" +#include + 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& 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 castRay(const Ray& ray) const; }; struct Topology { @@ -37,13 +39,12 @@ namespace tp { Buffer> Indexes; }; + // World-space copy of a Topology, rebuilt by update(). struct TopologyCache { - const Topology* Source = nullptr; - Buffer TransformedPoints; Buffer TransformedNormals; Buffer TrigCaches; - void updateCache(); + void update(const Topology& source); }; } diff --git a/RasterRender/private/FrameBuffer.cpp b/RasterRender/private/FrameBuffer.cpp deleted file mode 100644 index 0520051f..00000000 --- a/RasterRender/private/FrameBuffer.cpp +++ /dev/null @@ -1,159 +0,0 @@ - -#include "FrameBuffer.hpp" -#include "GraphicsApi.hpp" - -#include - -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)); -} diff --git a/RasterRender/private/GPUBuffers.hpp b/RasterRender/private/GPUBuffers.hpp deleted file mode 100644 index f90ff4de..00000000 --- a/RasterRender/private/GPUBuffers.hpp +++ /dev/null @@ -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) * 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); - } - }; - -} \ No newline at end of file diff --git a/RasterRender/private/Render.cpp b/RasterRender/private/Render.cpp deleted file mode 100644 index 9b6da2ce..00000000 --- a/RasterRender/private/Render.cpp +++ /dev/null @@ -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& 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& 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; } diff --git a/RasterRender/private/Shader.cpp b/RasterRender/private/Shader.cpp deleted file mode 100644 index 0bb8af39..00000000 --- a/RasterRender/private/Shader.cpp +++ /dev/null @@ -1,162 +0,0 @@ - - -#include "Buffer.hpp" -#include "LocalConnection.hpp" -#include "Shader.hpp" -#include "GraphicsApi.hpp" - -#include - -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 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 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); -} \ No newline at end of file diff --git a/RasterRender/private/Texture.cpp b/RasterRender/private/Texture.cpp deleted file mode 100644 index 55fcbd76..00000000 --- a/RasterRender/private/Texture.cpp +++ /dev/null @@ -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& 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 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 -} - - diff --git a/RasterRender/public/FrameBuffer.hpp b/RasterRender/public/FrameBuffer.hpp deleted file mode 100644 index 8c20aebf..00000000 --- a/RasterRender/public/FrameBuffer.hpp +++ /dev/null @@ -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; - }; -}; \ No newline at end of file diff --git a/RasterRender/public/GraphicsApi.hpp b/RasterRender/public/GraphicsApi.hpp deleted file mode 100644 index 79e2d39f..00000000 --- a/RasterRender/public/GraphicsApi.hpp +++ /dev/null @@ -1,19 +0,0 @@ - -#pragma once - -#include "Environment.hpp" - -#ifdef ENV_OS_ANDROID -#include -#include -#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 \ No newline at end of file diff --git a/RasterRender/public/RasterRender.hpp b/RasterRender/public/RasterRender.hpp deleted file mode 100644 index 4824c936..00000000 --- a/RasterRender/public/RasterRender.hpp +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once - -#include "Scene.hpp" -#include "Rect.hpp" - -#include "FrameBuffer.hpp" -#include "Shader.hpp" - -namespace tp { - class RasterRender { - public: - RasterRender(); - ~RasterRender(); - - void beginRender(const Scene& geometry, const Vec2& size); - void endRender(); - - void renderDefault(const Scene& geometry); - void renderOutline(const Camera& camera, const Object& object); - - void resize(const Vec2& size); - - uint4 getRenderBufferID(); - RenderBuffer* getRenderBuffer(); - Vec2F getBufferSize(); - - private: - void bindObjectShaderAttributes(const Object& object); - void bindCameraShaderAttributes(const Mat4F& cameraMat); - - private: - RenderBuffer mRenderBuffer; - RenderBuffer mTempBuffer; - - RenderShader mDefaultShader; - RenderShader mSolidShader; - }; -} \ No newline at end of file diff --git a/RasterRender/public/Shader.hpp b/RasterRender/public/Shader.hpp deleted file mode 100644 index 253cdbae..00000000 --- a/RasterRender/public/Shader.hpp +++ /dev/null @@ -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; - }; -}; \ No newline at end of file diff --git a/RasterRender/public/Texture.hpp b/RasterRender/public/Texture.hpp deleted file mode 100644 index 71ee943a..00000000 --- a/RasterRender/public/Texture.hpp +++ /dev/null @@ -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& 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; - }; -}; \ No newline at end of file diff --git a/RasterRender/rsc/shaders/default.frag b/RasterRender/rsc/shaders/default.frag deleted file mode 100644 index e142a372..00000000 --- a/RasterRender/rsc/shaders/default.frag +++ /dev/null @@ -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); -} \ No newline at end of file diff --git a/RasterRender/rsc/shaders/default.vert b/RasterRender/rsc/shaders/default.vert deleted file mode 100644 index 1d325ed1..00000000 --- a/RasterRender/rsc/shaders/default.vert +++ /dev/null @@ -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; -} \ No newline at end of file diff --git a/RasterRender/rsc/shaders/solid.frag b/RasterRender/rsc/shaders/solid.frag deleted file mode 100644 index 31e99ca1..00000000 --- a/RasterRender/rsc/shaders/solid.frag +++ /dev/null @@ -1,7 +0,0 @@ -#version 330 core - -out vec4 FragColor; - -void main() { - FragColor = vec4(1, 0, 0, 1.f); -} \ No newline at end of file diff --git a/RayTracer/applications/Rayt.cpp b/RayTracer/applications/Rayt.cpp index a5c40a29..6abc08c7 100644 --- a/RayTracer/applications/Rayt.cpp +++ b/RayTracer/applications/Rayt.cpp @@ -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; diff --git a/RayTracer/private/RayTracer.cpp b/RayTracer/private/RayTracer.cpp index b7b85f07..a08a9a25 100644 --- a/RayTracer/private/RayTracer.cpp +++ b/RayTracer/private/RayTracer.cpp @@ -9,69 +9,9 @@ #include -/* - 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 }; diff --git a/RayTracer/public/RayTracer.hpp b/RayTracer/public/RayTracer.hpp index a21964a2..856c1020 100644 --- a/RayTracer/public/RayTracer.hpp +++ b/RayTracer/public/RayTracer.hpp @@ -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; diff --git a/RayTracer/tests/Test.cpp b/RayTracer/tests/Test.cpp index 8d37d54f..31f38c97 100644 --- a/RayTracer/tests/Test.cpp +++ b/RayTracer/tests/Test.cpp @@ -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, diff --git a/Sketch3D/rsc/shaders/Texture.frag b/Sketch3D/rsc/shaders/Texture.frag deleted file mode 100644 index c72e4c94..00000000 --- a/Sketch3D/rsc/shaders/Texture.frag +++ /dev/null @@ -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; -} diff --git a/Sketch3D/rsc/shaders/Texture.vert b/Sketch3D/rsc/shaders/Texture.vert deleted file mode 100644 index 77000762..00000000 --- a/Sketch3D/rsc/shaders/Texture.vert +++ /dev/null @@ -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; -} \ No newline at end of file diff --git a/Sketch3D/rsc/shaders/stroke.frag b/Sketch3D/rsc/shaders/stroke.frag deleted file mode 100644 index 02572906..00000000 --- a/Sketch3D/rsc/shaders/stroke.frag +++ /dev/null @@ -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); -} \ No newline at end of file diff --git a/Sketch3D/rsc/shaders/stroke.geom b/Sketch3D/rsc/shaders/stroke.geom deleted file mode 100644 index 04199b25..00000000 --- a/Sketch3D/rsc/shaders/stroke.geom +++ /dev/null @@ -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(); -} diff --git a/Sketch3D/rsc/shaders/stroke.vert b/Sketch3D/rsc/shaders/stroke.vert deleted file mode 100644 index 220e25e6..00000000 --- a/Sketch3D/rsc/shaders/stroke.vert +++ /dev/null @@ -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; -} \ No newline at end of file -- 2.49.1 From ec8593355d28213c6eb3acbd2e581c93c36cbee3 Mon Sep 17 00:00:00 2001 From: agent Date: Thu, 29 Oct 2026 12:51:47 +0300 Subject: [PATCH 2/3] 3DScene, RasterRender: Vulkan scene renderer with typed ray hits and a throwing loader 3DScene: - Scene::castRay is const and returns std::optional; RayTracer reuses it instead of its own copy of the loop. - Scene::loadLua throws SceneLoadError with the failing field; Lua stack values are popped by scope guards and the state is closed on every path. - Objects get a stable id and are added through addObject/findObject. RasterRender: - Replaces the OpenGL renderer with a Vulkan one on GpuDevice. - RenderTarget: offscreen color/depth images with optional MSAA, registered in the device RenderTargetRegistry for Canvas sampling, plus a pixel readback. - GpuBuffer and GraphicsPipeline: reusable host-visible buffers and a pipeline builder shared by the scene and sketch renderers. - The scene holds no GPU state; RasterRender caches one mesh per object id and drops meshes of removed objects. The highlight is drawn in the same pass so it no longer clears the scene. - Tests render headless and read back pixels to verify depth shading, row orientation against Camera::project, the highlight, resize and mesh release. --- RasterRender/CMakeLists.txt | 42 ++- RasterRender/private/GpuBuffer.cpp | 57 ++++ RasterRender/private/GraphicsPipeline.cpp | 171 ++++++++++ RasterRender/private/RasterRender.cpp | 124 +++++++ RasterRender/private/RenderTarget.cpp | 399 ++++++++++++++++++++++ RasterRender/public/ClipSpace.hpp | 22 ++ RasterRender/public/GpuBuffer.hpp | 33 ++ RasterRender/public/GraphicsPipeline.hpp | 56 +++ RasterRender/public/RasterRender.hpp | 46 +++ RasterRender/public/RenderTarget.hpp | 71 ++++ RasterRender/shaders/depth.frag | 7 + RasterRender/shaders/object.vert | 12 + RasterRender/shaders/solid.frag | 7 + RasterRender/tests/RasterRenderTests.cpp | 100 ++++++ 14 files changed, 1138 insertions(+), 9 deletions(-) create mode 100644 RasterRender/private/GpuBuffer.cpp create mode 100644 RasterRender/private/GraphicsPipeline.cpp create mode 100644 RasterRender/private/RasterRender.cpp create mode 100644 RasterRender/private/RenderTarget.cpp create mode 100644 RasterRender/public/ClipSpace.hpp create mode 100644 RasterRender/public/GpuBuffer.hpp create mode 100644 RasterRender/public/GraphicsPipeline.hpp create mode 100644 RasterRender/public/RasterRender.hpp create mode 100644 RasterRender/public/RenderTarget.hpp create mode 100644 RasterRender/shaders/depth.frag create mode 100644 RasterRender/shaders/object.vert create mode 100644 RasterRender/shaders/solid.frag create mode 100644 RasterRender/tests/RasterRenderTests.cpp diff --git a/RasterRender/CMakeLists.txt b/RasterRender/CMakeLists.txt index c79d8ef5..b210093b 100644 --- a/RasterRender/CMakeLists.txt +++ b/RasterRender/CMakeLists.txt @@ -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}) \ No newline at end of file +### -------------------------- 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() diff --git a/RasterRender/private/GpuBuffer.cpp b/RasterRender/private/GpuBuffer.cpp new file mode 100644 index 00000000..c37055c2 --- /dev/null +++ b/RasterRender/private/GpuBuffer.cpp @@ -0,0 +1,57 @@ +#include "GpuBuffer.hpp" + +#include "GpuError.hpp" + +#include + +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); + } +} diff --git a/RasterRender/private/GraphicsPipeline.cpp b/RasterRender/private/GraphicsPipeline.cpp new file mode 100644 index 00000000..ce8ef563 --- /dev/null +++ b/RasterRender/private/GraphicsPipeline.cpp @@ -0,0 +1,171 @@ +#include "GraphicsPipeline.hpp" + +#include "GpuError.hpp" + +#include +#include + +namespace tp { + + namespace { + + class ShaderModules { + public: + ShaderModules(VkDevice device, std::span 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& stages() const { return mStages; } + + private: + VkDevice mDevice; + std::vector mModules; + std::vector 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 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); + } +} diff --git a/RasterRender/private/RasterRender.cpp b/RasterRender/private/RasterRender.cpp new file mode 100644 index 00000000..fc2674a1 --- /dev/null +++ b/RasterRender/private/RasterRender.cpp @@ -0,0 +1,124 @@ +#include "RasterRender.hpp" + +#include "ClipSpace.hpp" + +#include +#include + +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 fragment) { + const std::array 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 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 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(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(mGpu, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, sizeof(Vec3) * indexes.size()); + mesh.indices->write(indexes.getBuff(), sizeof(Vec3) * 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); + } + }); + } +} diff --git a/RasterRender/private/RenderTarget.cpp b/RasterRender/private/RenderTarget.cpp new file mode 100644 index 00000000..025e1599 --- /dev/null +++ b/RasterRender/private/RenderTarget.cpp @@ -0,0 +1,399 @@ +#include "RenderTarget.hpp" + +#include "GpuBuffer.hpp" +#include "GpuError.hpp" + +#include +#include + +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 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 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 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 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 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 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& draw) { + const VkCommandBuffer commandBuffer = mGpu.beginOneShotCommands(); + + std::array 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 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, ®ion + ); + 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 pixels(count); + staging.read(pixels.data(), pixels.size() * sizeof(uint32_t)); + return pixels; + } +} diff --git a/RasterRender/public/ClipSpace.hpp b/RasterRender/public/ClipSpace.hpp new file mode 100644 index 00000000..2c5d1a34 --- /dev/null +++ b/RasterRender/public/ClipSpace.hpp @@ -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(); + } +} diff --git a/RasterRender/public/GpuBuffer.hpp b/RasterRender/public/GpuBuffer.hpp new file mode 100644 index 00000000..3b7a422e --- /dev/null +++ b/RasterRender/public/GpuBuffer.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include "GpuDevice.hpp" + +#include + +#include + +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; + }; +} diff --git a/RasterRender/public/GraphicsPipeline.hpp b/RasterRender/public/GraphicsPipeline.hpp new file mode 100644 index 00000000..614c713b --- /dev/null +++ b/RasterRender/public/GraphicsPipeline.hpp @@ -0,0 +1,56 @@ +#pragma once + +#include "GpuDevice.hpp" + +#include + +#include +#include +#include + +namespace tp { + + struct ShaderStage { + VkShaderStageFlagBits stage; + std::span spirv; + }; + + // Fixed-function state of a pipeline that draws one interleaved vertex buffer with depth testing. + struct PipelineDescription { + std::span 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 + void push(VkCommandBuffer commandBuffer, const tConstants& constants) const { + static_assert(std::is_standard_layout_v); + 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; + }; +} diff --git a/RasterRender/public/RasterRender.hpp b/RasterRender/public/RasterRender.hpp new file mode 100644 index 00000000..bb34b71b --- /dev/null +++ b/RasterRender/public/RasterRender.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include "Scene.hpp" +#include "RenderTarget.hpp" +#include "GraphicsPipeline.hpp" +#include "GpuBuffer.hpp" + +#include +#include + +namespace tp { + + // Draws a Scene into an offscreen RenderTarget: depth-shaded objects plus an optional solid highlight. + class RasterRender { + public: + explicit RasterRender(GpuDevice& gpu, Vec2 size = { 1, 1 }); + ~RasterRender(); + + RasterRender(const RasterRender&) = delete; + RasterRender& operator=(const RasterRender&) = delete; + + [[nodiscard]] RenderTarget& target() { return mTarget; } + [[nodiscard]] size_t uploadedMeshes() const { return mMeshes.size(); } + + // 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: + struct ObjectMesh { + std::unique_ptr vertices; + std::unique_ptr indices; + uint32_t indexCount = 0; + }; + + void syncMeshes(const Scene& scene); + void drawObject(VkCommandBuffer commandBuffer, const GraphicsPipeline& pipeline, const Mat4F& worldToClip, const Object& object); + + GpuDevice& mGpu; + RenderTarget mTarget; + GraphicsPipeline mDepthPipeline; + GraphicsPipeline mSolidPipeline; + std::unordered_map mMeshes; + }; +} diff --git a/RasterRender/public/RenderTarget.hpp b/RasterRender/public/RenderTarget.hpp new file mode 100644 index 00000000..5bb506a5 --- /dev/null +++ b/RasterRender/public/RenderTarget.hpp @@ -0,0 +1,71 @@ +#pragma once + +#include "GpuDevice.hpp" +#include "Color.hpp" +#include "Vec.hpp" + +#include + +#include +#include +#include + +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 size, uint4 samples); + ~RenderTarget(); + + RenderTarget(const RenderTarget&) = delete; + RenderTarget& operator=(const RenderTarget&) = delete; + + [[nodiscard]] uint32_t registryId() const { return mRegistryId; } + [[nodiscard]] Vec2 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 size); + + // Records one cleared render pass, submits it and waits for completion. + void render(const RGBA& clearColor, const std::function& draw); + + // The resolved color image as RGBA8 rows, top row first. + [[nodiscard]] std::vector 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; + }; +} diff --git a/RasterRender/shaders/depth.frag b/RasterRender/shaders/depth.frag new file mode 100644 index 00000000..a4f1e7cb --- /dev/null +++ b/RasterRender/shaders/depth.frag @@ -0,0 +1,7 @@ +#version 450 + +layout(location = 0) out vec4 color; + +void main() { + color = vec4(vec3(gl_FragCoord.z), 1.0); +} diff --git a/RasterRender/shaders/object.vert b/RasterRender/shaders/object.vert new file mode 100644 index 00000000..4854ef25 --- /dev/null +++ b/RasterRender/shaders/object.vert @@ -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); +} diff --git a/RasterRender/shaders/solid.frag b/RasterRender/shaders/solid.frag new file mode 100644 index 00000000..c9819fe2 --- /dev/null +++ b/RasterRender/shaders/solid.frag @@ -0,0 +1,7 @@ +#version 450 + +layout(location = 0) out vec4 color; + +void main() { + color = vec4(1.0, 0.0, 0.0, 1.0); +} diff --git a/RasterRender/tests/RasterRenderTests.cpp b/RasterRender/tests/RasterRenderTests.cpp new file mode 100644 index 00000000..5e1e5532 --- /dev/null +++ b/RasterRender/tests/RasterRenderTests.cpp @@ -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& 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(); } -- 2.49.1 From f0283e054f9cb0c22e539821f1962b3ca33ffbf2 Mon Sep 17 00:00:00 2001 From: agent Date: Thu, 29 Oct 2026 12:51:55 +0300 Subject: [PATCH 3/3] 3DEditor, Sketch3D: render through RasterRender - The editor drives a RasterRender from the application GpuDevice and shows the path-traced frame through a Canvas RGBA image instead of a GL texture. - Sketch3D renders strokes with a geometry-shader pipeline on a shared RenderTarget, caching one vertex buffer per stroke version, and splits the renderer from the widgets so it can be tested headless. - Both apps pass a device to the widget that renders into the Canvas. --- 3DEditor/CMakeLists.txt | 5 +- 3DEditor/applications/Entry.cpp | 4 +- 3DEditor/private/Editor.cpp | 132 +++++------------- 3DEditor/public/Editor.hpp | 25 ++-- 3DEditor/public/EditorWidget.hpp | 57 +++++--- Sketch3D/CMakeLists.txt | 48 +++++-- Sketch3D/applications/Entry.cpp | 2 +- Sketch3D/private/Sketch3D.cpp | 224 ++++++++++++++++--------------- Sketch3D/private/SketchGUI.cpp | 12 +- Sketch3D/public/Sketch3D.hpp | 67 +++++---- Sketch3D/public/SketchGUI.hpp | 4 +- Sketch3D/shaders/stroke.frag | 28 ++++ Sketch3D/shaders/stroke.geom | 89 ++++++++++++ Sketch3D/shaders/stroke.vert | 23 ++++ Sketch3D/tests/RendererTests.cpp | 58 ++++++++ 15 files changed, 484 insertions(+), 294 deletions(-) create mode 100644 Sketch3D/shaders/stroke.frag create mode 100644 Sketch3D/shaders/stroke.geom create mode 100644 Sketch3D/shaders/stroke.vert create mode 100644 Sketch3D/tests/RendererTests.cpp diff --git a/3DEditor/CMakeLists.txt b/3DEditor/CMakeLists.txt index 54adad14..bd87d17e 100644 --- a/3DEditor/CMakeLists.txt +++ b/3DEditor/CMakeLists.txt @@ -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}/") diff --git a/3DEditor/applications/Entry.cpp b/3DEditor/applications/Entry.cpp index 6867377b..ae402ecc 100644 --- a/3DEditor/applications/Entry.cpp +++ b/3DEditor/applications/Entry.cpp @@ -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; } diff --git a/3DEditor/private/Editor.cpp b/3DEditor/private/Editor.cpp index b430548c..372b4783 100644 --- a/3DEditor/private/Editor.cpp +++ b/3DEditor/private/Editor.cpp @@ -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 (auto active = getActiveObject()) mRasterRenderer.renderOutline(mScene.mCamera, *active); - 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 = 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() ); - - const auto hit = mScene.castRay(ray, 1000); + 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 mScene.findObject(mActiveId); } -Scene* Editor::getScene() { return &mScene; } \ No newline at end of file +Scene* Editor::getScene() { return &mScene; } diff --git a/3DEditor/public/Editor.hpp b/3DEditor/public/Editor.hpp index 7340db61..923cd740 100644 --- a/3DEditor/public/Editor.hpp +++ b/3DEditor/public/Editor.hpp @@ -3,25 +3,33 @@ #include "RayTracer.hpp" #include "RasterRender.hpp" +#include + 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 pathImageSize() const { return mPathImageSize; } + [[nodiscard]] const std::vector& 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,9 +56,10 @@ namespace tp { RayTracer mPathRenderer; RayTracer::OutputBuffers mPathTracerBuffers; - uint4 mPathRenderTexture = 0; + Vec2 mPathImageSize = { 0, 0 }; + std::vector mPathImage; // Note: 0 means nothing is selected. uint8 mActiveId = 0; }; -} \ No newline at end of file +} diff --git a/3DEditor/public/EditorWidget.hpp b/3DEditor/public/EditorWidget.hpp index 48b4568a..6a225605 100644 --- a/3DEditor/public/EditorWidget.hpp +++ b/3DEditor/public/EditorWidget.hpp @@ -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 mPathImageSize = { 0, 0 }; }; class EditorWidget : public DockWidget { public: EditorWidget(Canvas* canvas, Editor* editor) : - mViewport(canvas, editor) { + mViewport(*canvas, *editor) { mEditor = editor; mPanel.setText("Controls"); diff --git a/Sketch3D/CMakeLists.txt b/Sketch3D/CMakeLists.txt index a28c3dc8..c2da2f12 100644 --- a/Sketch3D/CMakeLists.txt +++ b/Sketch3D/CMakeLists.txt @@ -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() diff --git a/Sketch3D/applications/Entry.cpp b/Sketch3D/applications/Entry.cpp index e2e02740..a3edc1f3 100644 --- a/Sketch3D/applications/Entry.cpp +++ b/Sketch3D/applications/Entry.cpp @@ -11,7 +11,7 @@ public: Sketch3DApplication() { setRoot(&mGui); - mGui.createRenderWidget(&canvas(), { 2560, 1440 }); + mGui.createRenderWidget(application().gpu(), &canvas(), { 2560, 1440 }); mGui.setProject(&mSketch); } diff --git a/Sketch3D/private/Sketch3D.cpp b/Sketch3D/private/Sketch3D.cpp index 183036bf..cf66eb52 100644 --- a/Sketch3D/private/Sketch3D.cpp +++ b/Sketch3D/private/Sketch3D.cpp @@ -1,26 +1,63 @@ #include "Sketch3D.hpp" +#include + 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 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* 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 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(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() {} diff --git a/Sketch3D/private/SketchGUI.cpp b/Sketch3D/private/SketchGUI.cpp index 30763cec..ad2e31c7 100644 --- a/Sketch3D/private/SketchGUI.cpp +++ b/Sketch3D/private/SketchGUI.cpp @@ -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); diff --git a/Sketch3D/public/Sketch3D.hpp b/Sketch3D/public/Sketch3D.hpp index c2d546b6..c912c9ee 100644 --- a/Sketch3D/public/Sketch3D.hpp +++ b/Sketch3D/public/Sketch3D.hpp @@ -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 +#include +#include 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* mPoints); - ~StrokeGPUHandles(); - }; - + // Draws a Project's strokes into an offscreen RenderTarget. class Renderer { public: - Renderer(Vec2F size); // max render size + Renderer(GpuDevice& gpu, Vec2 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 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 mMaxSize; + RenderTarget mTarget; + GraphicsPipeline mPipeline; + std::unordered_map mMeshes; + std::unordered_set 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& getPoints(); const Buffer& getPoints() const; void setColor(const RGBA& col); @@ -79,8 +79,7 @@ namespace tp { Buffer mPoints; RGBA mColor; - friend Renderer; - StrokeGPUHandles mGPUHandles; + uint64_t mVersion = 0; }; struct Layer { diff --git a/Sketch3D/public/SketchGUI.hpp b/Sketch3D/public/SketchGUI.hpp index b232a8e2..5b8e521c 100644 --- a/Sketch3D/public/SketchGUI.hpp +++ b/Sketch3D/public/SketchGUI.hpp @@ -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; diff --git a/Sketch3D/shaders/stroke.frag b/Sketch3D/shaders/stroke.frag new file mode 100644 index 00000000..506ec4f3 --- /dev/null +++ b/Sketch3D/shaders/stroke.frag @@ -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); +} diff --git a/Sketch3D/shaders/stroke.geom b/Sketch3D/shaders/stroke.geom new file mode 100644 index 00000000..4e836659 --- /dev/null +++ b/Sketch3D/shaders/stroke.geom @@ -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(); +} diff --git a/Sketch3D/shaders/stroke.vert b/Sketch3D/shaders/stroke.vert new file mode 100644 index 00000000..8871a8b7 --- /dev/null +++ b/Sketch3D/shaders/stroke.vert @@ -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; +} diff --git a/Sketch3D/tests/RendererTests.cpp b/Sketch3D/tests/RendererTests.cpp new file mode 100644 index 00000000..f3a31a38 --- /dev/null +++ b/Sketch3D/tests/RendererTests.cpp @@ -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(); } -- 2.49.1