3DScene, RasterRender: Vulkan scene renderer, typed ray hits, throwing loader #10
56 changed files with 1873 additions and 1905 deletions
|
|
@ -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}/")
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
|
|
|||
|
|
@ -1,140 +1,74 @@
|
|||
|
||||
#include "Editor.hpp"
|
||||
|
||||
#include "GraphicsApi.hpp"
|
||||
|
||||
using namespace tp;
|
||||
|
||||
Editor::Editor() {
|
||||
|
||||
{ // create path tracer gpu texture
|
||||
glGenTextures(1, &mPathRenderTexture);
|
||||
glBindTexture(GL_TEXTURE_2D, mPathRenderTexture);
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, 100, 100, 0, GL_RGBA, GL_FLOAT, nullptr);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
namespace {
|
||||
uint32_t packColor(const RGBA& color) {
|
||||
const auto byte = [](halnf value) { return (uint32_t) (clamp(value, 0.f, 1.f) * 255.f + 0.5f); };
|
||||
return byte(color.r) | (byte(color.g) << 8) | (byte(color.b) << 16) | (byte(color.a) << 24);
|
||||
}
|
||||
}
|
||||
|
||||
void Editor::renderViewport() {
|
||||
switch (mRenderType) {
|
||||
case RenderType::RASTER:
|
||||
mRasterRenderer.beginRender(mScene, mScene.mRenderSettings.size);
|
||||
mRasterRenderer.renderDefault(mScene);
|
||||
if (mActive) mRasterRenderer.renderOutline(mScene.mCamera, *mActive);
|
||||
mRasterRenderer.endRender();
|
||||
break;
|
||||
|
||||
case RenderType::PATH_TRACER:
|
||||
{
|
||||
// TODO : include in render viewport method
|
||||
// renderPathFrame();
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint4 Editor::getViewportTexID() {
|
||||
switch (mRenderType) {
|
||||
case RenderType::PATH_TRACER:
|
||||
return mPathRenderTexture;
|
||||
|
||||
case RenderType::RASTER:
|
||||
return mRasterRenderer.getRenderBufferID();
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
Editor::Editor(GpuDevice& gpu) :
|
||||
mRasterRenderer(gpu) {}
|
||||
|
||||
void Editor::loadDefaults() {
|
||||
mScene.load("rsc/scene/script.lua");
|
||||
mScene = Scene::loadLua("rsc/scene/script.lua");
|
||||
mResetCamera = mScene.mCamera;
|
||||
}
|
||||
|
||||
void Editor::setRenderType(RenderType type) { mRenderType = type; }
|
||||
|
||||
void Editor::setViewportSize(const Vec2F& size) {
|
||||
if (size.x <= 0 || size.y <= 0) return;
|
||||
|
||||
// TODO remove
|
||||
// mScene.mCamera.rotate(0.01f, 0.0);
|
||||
|
||||
mScene.mRenderSettings.size = size;
|
||||
|
||||
mRasterRenderer.getRenderBuffer()->resize(size);
|
||||
if (size.x < 1 || size.y < 1) {
|
||||
return;
|
||||
}
|
||||
mScene.mRenderSettings.size = { (ualni) size.x, (ualni) size.y };
|
||||
mRasterRenderer.target().resize({ (uint4) size.x, (uint4) size.y });
|
||||
mScene.mCamera.setRatio(size.y / size.x);
|
||||
}
|
||||
|
||||
Editor::~Editor() {
|
||||
glDeleteTextures(1, &mPathRenderTexture);
|
||||
}
|
||||
void Editor::renderRaster() { mRasterRenderer.render(mScene, getActiveObject()); }
|
||||
|
||||
void Editor::renderPathFrame() {
|
||||
mScene.updateCache();
|
||||
mPathRenderer.render(mScene, mPathTracerBuffers, mScene.mRenderSettings);
|
||||
sendBuffersToGPU();
|
||||
}
|
||||
|
||||
void Editor::sendBuffersToGPU() {
|
||||
glBindTexture(GL_TEXTURE_2D, mPathRenderTexture);
|
||||
const auto size = mPathTracerBuffers.color.size();
|
||||
glTexImage2D(
|
||||
GL_TEXTURE_2D,
|
||||
0,
|
||||
GL_RGBA32F,
|
||||
(GLsizei) size.x,
|
||||
(GLsizei) size.y,
|
||||
0,
|
||||
GL_RGBA,
|
||||
GL_FLOAT,
|
||||
mPathTracerBuffers.color.getBuff()
|
||||
);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
convertPathImage();
|
||||
}
|
||||
|
||||
void Editor::denoisePathRenderBuffers() {
|
||||
denoise();
|
||||
sendBuffersToGPU();
|
||||
convertPathImage();
|
||||
}
|
||||
|
||||
void Editor::setRenderType(Editor::RenderType type) {
|
||||
mRenderType = type;
|
||||
// Note: rows keep the tracer order, which matches the raster target layout.
|
||||
void Editor::convertPathImage() {
|
||||
const auto size = mPathTracerBuffers.color.size();
|
||||
mPathImageSize = { (uint4) size.x, (uint4) size.y };
|
||||
mPathImage.resize((size_t) size.x * size.y);
|
||||
for (Index y = 0; y < size.y; y++) {
|
||||
for (Index x = 0; x < size.x; x++) {
|
||||
mPathImage[y * size.x + x] = packColor(mPathTracerBuffers.color.get({ x, y }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Editor::navigationOrbit(const Vec2F& delta) {
|
||||
mScene.mCamera.rotate(delta.x, delta.y);
|
||||
}
|
||||
void Editor::navigationOrbit(const Vec2F& delta) { mScene.mCamera.rotate(delta.x, delta.y); }
|
||||
|
||||
void Editor::navigationPan(const Vec2F& pos, const Vec2F& prevPos) {
|
||||
mScene.mCamera.move(pos, prevPos);
|
||||
}
|
||||
void Editor::navigationPan(const Vec2F& pos, const Vec2F& prevPos) { mScene.mCamera.move(pos, prevPos); }
|
||||
|
||||
void Editor::navigationZoom(halnf factor) {
|
||||
mScene.mCamera.zoom(factor);
|
||||
}
|
||||
void Editor::navigationZoom(halnf factor) { mScene.mCamera.zoom(factor); }
|
||||
|
||||
void Editor::navigationReset() {
|
||||
mScene.mCamera = mResetCamera;
|
||||
// mScene.mCamera.lookAtPoint({ 0, 0, 0 }, { 1, 5, 1 }, { 0, 0, 1 });
|
||||
}
|
||||
void Editor::navigationReset() { mScene.mCamera = mResetCamera; }
|
||||
|
||||
void Editor::selectObject(const Vec2F& screenPos) {
|
||||
mScene.updateCache();
|
||||
|
||||
auto dir = (mScene.mCamera.project(screenPos) - mScene.mCamera.getPos()).normalize();
|
||||
Ray ray = Ray( dir, mScene.mCamera.getPos() );
|
||||
|
||||
auto rayCastData = mScene.castRay(ray, 1000);
|
||||
mActive = rayCastData.obj;
|
||||
const auto dir = (mScene.mCamera.project(screenPos) - mScene.mCamera.getPos()).normalize();
|
||||
const auto hit = mScene.castRay(Ray(dir, mScene.mCamera.getPos()), 1000);
|
||||
mActiveId = hit ? hit->object->id() : 0;
|
||||
}
|
||||
|
||||
Object* Editor::getActiveObject() { return mActive; }
|
||||
Object* Editor::getActiveObject() { return mScene.findObject(mActiveId); }
|
||||
|
||||
Scene* Editor::getScene() { return &mScene; }
|
||||
|
|
@ -3,25 +3,33 @@
|
|||
#include "RayTracer.hpp"
|
||||
#include "RasterRender.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace tp {
|
||||
|
||||
class Editor {
|
||||
public:
|
||||
enum class RenderType { RASTER, PATH_TRACER };
|
||||
|
||||
public:
|
||||
Editor();
|
||||
~Editor();
|
||||
explicit Editor(GpuDevice& gpu);
|
||||
|
||||
void loadDefaults();
|
||||
|
||||
uint4 getViewportTexID();
|
||||
[[nodiscard]] RenderType renderType() const { return mRenderType; }
|
||||
void setRenderType(RenderType type);
|
||||
|
||||
void setViewportSize(const Vec2F& size);
|
||||
void renderViewport();
|
||||
|
||||
// Raster mode: renders into the RasterRender target whose registry id is rasterTargetId().
|
||||
void renderRaster();
|
||||
[[nodiscard]] uint32_t rasterTargetId() { return mRasterRenderer.target().registryId(); }
|
||||
|
||||
// Path tracer mode: the last traced image as RGBA8 rows in the raster target's row order.
|
||||
void renderPathFrame();
|
||||
void setRenderType(RenderType type);
|
||||
void denoisePathRenderBuffers();
|
||||
[[nodiscard]] Vec2<uint4> pathImageSize() const { return mPathImageSize; }
|
||||
[[nodiscard]] const std::vector<uint32_t>& pathImage() const { return mPathImage; }
|
||||
|
||||
void navigationOrbit(const Vec2F& delta);
|
||||
void navigationPan(const Vec2F& pos, const Vec2F& prevPos);
|
||||
|
|
@ -34,7 +42,7 @@ namespace tp {
|
|||
Scene* getScene();
|
||||
|
||||
private:
|
||||
void sendBuffersToGPU();
|
||||
void convertPathImage();
|
||||
void denoise();
|
||||
|
||||
private:
|
||||
|
|
@ -48,8 +56,10 @@ namespace tp {
|
|||
RayTracer mPathRenderer;
|
||||
|
||||
RayTracer::OutputBuffers mPathTracerBuffers;
|
||||
uint4 mPathRenderTexture = 0;
|
||||
Vec2<uint4> mPathImageSize = { 0, 0 };
|
||||
std::vector<uint32_t> mPathImage;
|
||||
|
||||
Object* mActive = nullptr;
|
||||
// Note: 0 means nothing is selected.
|
||||
uint8 mActiveId = 0;
|
||||
};
|
||||
}
|
||||
|
|
@ -8,36 +8,57 @@ namespace tp {
|
|||
|
||||
class ViewportWidget : public Widget {
|
||||
public:
|
||||
explicit ViewportWidget(Canvas* canvas, Editor* editor) {
|
||||
mEditor = editor;
|
||||
mImage = canvas->createImageFromRenderTarget(mEditor->getViewportTexID());
|
||||
mCanvas = canvas;
|
||||
ViewportWidget(Canvas& canvas, Editor& editor) :
|
||||
mEditor(editor),
|
||||
mCanvas(canvas),
|
||||
mRasterImage(canvas.createImageFromRenderTarget(editor.rasterTargetId())) {}
|
||||
|
||||
~ViewportWidget() override {
|
||||
mCanvas.deleteImage(mRasterImage);
|
||||
mCanvas.deleteImage(mPathImage);
|
||||
}
|
||||
|
||||
~ViewportWidget() override { mCanvas->deleteImage(mImage); }
|
||||
|
||||
void process(const InputView& events) override {
|
||||
mEditor->setViewportSize(getAreaT().size);
|
||||
}
|
||||
void process(const InputView& events) override { mEditor.setViewportSize(getAreaT().size); }
|
||||
|
||||
void draw(Canvas& canvas) override {
|
||||
mEditor->renderViewport();
|
||||
|
||||
canvas.updateImageFromRenderTarget(mImage, mEditor->getViewportTexID());
|
||||
canvas.drawImage(getArea().relative(), mImage, PI);
|
||||
if (mEditor.renderType() == Editor::RenderType::PATH_TRACER) {
|
||||
syncPathImage(canvas);
|
||||
canvas.drawImage(getArea().relative(), mPathImage, kImageAngle);
|
||||
return;
|
||||
}
|
||||
mEditor.renderRaster();
|
||||
// Note: resizing recreates the target images, so the Canvas image is pointed at them every frame.
|
||||
canvas.updateImageFromRenderTarget(mRasterImage, mEditor.rasterTargetId());
|
||||
canvas.drawImage(getArea().relative(), mRasterImage, kImageAngle);
|
||||
}
|
||||
|
||||
public:
|
||||
Editor* mEditor;
|
||||
private:
|
||||
// Note: both images store row 0 at the camera's bottom edge (y = -1); a half turn shows them upright.
|
||||
static constexpr halnf kImageAngle = (halnf) PI;
|
||||
|
||||
Canvas* mCanvas = nullptr;
|
||||
ImageHandle mImage;
|
||||
void syncPathImage(Canvas& canvas) {
|
||||
const auto size = mEditor.pathImageSize();
|
||||
const auto* pixels = (const unsigned char*) mEditor.pathImage().data();
|
||||
if (mPathImage.isValid() && size == mPathImageSize) {
|
||||
canvas.updateImageRGBA(mPathImage, pixels);
|
||||
return;
|
||||
}
|
||||
canvas.deleteImage(mPathImage);
|
||||
mPathImage = size.x && size.y ? canvas.createImageFromMemoryRGBA((int) size.x, (int) size.y, pixels) : ImageHandle();
|
||||
mPathImageSize = size;
|
||||
}
|
||||
|
||||
Editor& mEditor;
|
||||
Canvas& mCanvas;
|
||||
ImageHandle mRasterImage;
|
||||
ImageHandle mPathImage;
|
||||
Vec2<uint4> mPathImageSize = { 0, 0 };
|
||||
};
|
||||
|
||||
class EditorWidget : public DockWidget {
|
||||
public:
|
||||
EditorWidget(Canvas* canvas, Editor* editor) :
|
||||
mViewport(canvas, editor) {
|
||||
mViewport(*canvas, *editor) {
|
||||
mEditor = editor;
|
||||
|
||||
mPanel.setText("Controls");
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
#include "Scene.hpp"
|
||||
|
||||
extern "C" {
|
||||
|
|
@ -7,217 +6,133 @@ extern "C" {
|
|||
}
|
||||
|
||||
#include <filesystem>
|
||||
#include <utility>
|
||||
#include <memory>
|
||||
|
||||
tp::Vec3F tp::Scene::getVec3(lua_State* state, const char* name) {
|
||||
int stackSize = lua_gettop(state);
|
||||
using namespace tp;
|
||||
|
||||
// Access the "pos" field and validate it
|
||||
namespace {
|
||||
|
||||
using LuaState = std::unique_ptr<lua_State, decltype(&lua_close)>;
|
||||
|
||||
// Pops the value pushed just before it when the scope ends.
|
||||
class PushedValue {
|
||||
public:
|
||||
explicit PushedValue(lua_State* state) :
|
||||
mState(state) {}
|
||||
~PushedValue() { lua_pop(mState, 1); }
|
||||
|
||||
PushedValue(const PushedValue&) = delete;
|
||||
PushedValue& operator=(const PushedValue&) = delete;
|
||||
|
||||
private:
|
||||
lua_State* mState;
|
||||
};
|
||||
|
||||
PushedValue pushGlobal(lua_State* state, const char* name) {
|
||||
lua_getglobal(state, name);
|
||||
return PushedValue(state);
|
||||
}
|
||||
|
||||
PushedValue pushField(lua_State* state, const char* name) {
|
||||
lua_getfield(state, -1, name);
|
||||
if (!lua_istable(state, -1) || lua_rawlen(state, -1) != 3) {
|
||||
throw IOError(std::string("Invalid vec3 named ") + 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");
|
||||
}
|
||||
lua_pop(state, 1); // pop number
|
||||
}
|
||||
lua_pop(state, 1); // pop vec3
|
||||
|
||||
ASSERT(stackSize == lua_gettop(state))
|
||||
|
||||
return { pos[0], pos[1], pos[2] };
|
||||
PushedValue pushElement(lua_State* state, lua_Integer index) {
|
||||
lua_rawgeti(state, -1, index);
|
||||
return PushedValue(state);
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
} 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);
|
||||
}
|
||||
lua_pop(L, 1); // Pop the i-th light table
|
||||
void requireTable(lua_State* state, const std::string& name) {
|
||||
if (!lua_istable(state, -1)) {
|
||||
throw SceneLoadError(name + " must be a table");
|
||||
}
|
||||
}
|
||||
|
||||
// ----------- settings --------------
|
||||
if (!readRenderSettings(L, mRenderSettings)) {
|
||||
printf("Cant Read Render Settings");
|
||||
lua_close(L);
|
||||
return false; // Error
|
||||
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");
|
||||
}
|
||||
return (halnf) lua_tonumber(state, -1);
|
||||
}
|
||||
|
||||
lua_close(L);
|
||||
|
||||
return true;
|
||||
template <ualni tSize>
|
||||
Vec<halnf, tSize> readVector(lua_State* state, const char* name) {
|
||||
const auto value = pushField(state, name);
|
||||
if (!lua_istable(state, -1) || lua_rawlen(state, -1) != tSize) {
|
||||
throw SceneLoadError(std::string(name) + " must be a list of " + std::to_string(tSize) + " numbers");
|
||||
}
|
||||
Vec<halnf, tSize> out;
|
||||
for (ualni index = 0; index < tSize; index++) {
|
||||
const auto element = pushElement(state, (lua_Integer) index + 1);
|
||||
if (!lua_isnumber(state, -1)) {
|
||||
throw SceneLoadError(std::string(name) + " must be a list of numbers");
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,86 +1,30 @@
|
|||
|
||||
#include "Scene.hpp"
|
||||
|
||||
extern "C" {
|
||||
#include "lauxlib.h"
|
||||
#include "lualib.h"
|
||||
}
|
||||
|
||||
#include "obj/OBJ_Loader.h"
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
bool tp::Scene::loadOBJFormat(const std::string& objetsPath) {
|
||||
using namespace tp;
|
||||
|
||||
objl::Loader Loader;
|
||||
|
||||
if (!Loader.LoadFile(objetsPath)) {
|
||||
std::cout << "Failed to Load File. May have failed to find it or it was not an .obj file.\n";
|
||||
return false;
|
||||
void Scene::loadOBJ(const std::string& objectsPath) {
|
||||
objl::Loader loader;
|
||||
if (!loader.LoadFile(objectsPath)) {
|
||||
throw SceneLoadError("cannot read OBJ file " + objectsPath);
|
||||
}
|
||||
if (loader.LoadedMeshes.empty()) {
|
||||
throw SceneLoadError("no meshes in " + objectsPath);
|
||||
}
|
||||
|
||||
for (auto& curMesh : Loader.LoadedMeshes) {
|
||||
mObjects.append(Object());
|
||||
|
||||
auto object = &mObjects.last();
|
||||
|
||||
for (auto& vertex : curMesh.Vertices) {
|
||||
// printf("{ %f, %f, %f }, \n", vertex.Position.X, vertex.Position.Y, vertex.Position.Z);
|
||||
object->mTopology.Points.append(Vec3F{ vertex.Position.X, vertex.Position.Y, vertex.Position.Z });
|
||||
object->mTopology.Normals.append(Vec3F{ vertex.Normal.X, vertex.Normal.Y, vertex.Normal.Z });
|
||||
for (const auto& mesh : loader.LoadedMeshes) {
|
||||
Object object;
|
||||
for (const auto& vertex : mesh.Vertices) {
|
||||
object.mTopology.Points.append(Vec3F{ vertex.Position.X, vertex.Position.Y, vertex.Position.Z });
|
||||
object.mTopology.Normals.append(Vec3F{ vertex.Normal.X, vertex.Normal.Y, vertex.Normal.Z });
|
||||
}
|
||||
|
||||
for (int j = 0; j < curMesh.Indices.size(); j += 3) {
|
||||
auto idx1 = (uhalni) curMesh.Indices[j];
|
||||
auto idx2 = (uhalni) curMesh.Indices[j + 1];
|
||||
auto idx3 = (uhalni) curMesh.Indices[j + 2];
|
||||
// printf("{ %i, %i, %i },\n", idx1, idx2, idx3);
|
||||
object->mTopology.Indexes.append(Vec3<uhalni>{ idx1, idx2, idx3 });
|
||||
for (size_t index = 0; index + 2 < mesh.Indices.size(); index += 3) {
|
||||
object.mTopology.Indexes.append(
|
||||
Vec3<uhalni>{ mesh.Indices[index], mesh.Indices[index + 1], mesh.Indices[index + 2] }
|
||||
);
|
||||
}
|
||||
|
||||
if (object->mTopology.Normals.size() != object->mTopology.Points.size()) {
|
||||
printf("Logic error loading normals\n");
|
||||
}
|
||||
|
||||
object->mCache.Source = &object->mTopology;
|
||||
object->mCache.updateCache();
|
||||
}
|
||||
|
||||
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();
|
||||
object.updateCache();
|
||||
addObject(object);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
Object& Scene::addObject(Object object) {
|
||||
object.mId = mNextObjectId++;
|
||||
return mObjects.append(object);
|
||||
}
|
||||
|
||||
return true;
|
||||
Object* Scene::findObject(uint8 id) {
|
||||
for (auto object : mObjects) {
|
||||
if (object->id() == id) {
|
||||
return &object.data();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::optional<RayHit> Scene::castRay(const Ray& ray, halnf maxDistance) const {
|
||||
std::optional<RayHit> closest;
|
||||
for (ualni objectIndex = 0; objectIndex < mObjects.size(); objectIndex++) {
|
||||
const Object& object = mObjects[objectIndex];
|
||||
const auto& trigs = object.mCache.TrigCaches;
|
||||
for (ualni trigIndex = 0; trigIndex < trigs.size(); trigIndex++) {
|
||||
const TrigCache& trig = trigs[trigIndex];
|
||||
const auto distance = trig.castRay(ray);
|
||||
if (!distance || *distance >= maxDistance) {
|
||||
continue;
|
||||
}
|
||||
maxDistance = *distance;
|
||||
closest = RayHit{ &object, &trig, ray.pos + ray.dir * *distance, *distance };
|
||||
}
|
||||
}
|
||||
return closest;
|
||||
}
|
||||
|
||||
void Scene::updateCache() {
|
||||
for (auto object : mObjects) {
|
||||
object->updateCache();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,14 @@
|
|||
#pragma once
|
||||
|
||||
#include "Topology.hpp"
|
||||
#include "Buffer2D.hpp"
|
||||
#include "Camera.hpp"
|
||||
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
struct lua_State;
|
||||
|
||||
namespace tp {
|
||||
|
||||
struct RenderSettings {
|
||||
uhalni depth = 2;
|
||||
uhalni spray = 1;
|
||||
|
|
@ -16,26 +16,21 @@ namespace tp {
|
|||
Vec2<ualni> size;
|
||||
};
|
||||
|
||||
class GPUBuffers {
|
||||
public:
|
||||
GPUBuffers() = default;
|
||||
virtual ~GPUBuffers() = default;
|
||||
|
||||
virtual void drawCall() = 0;
|
||||
};
|
||||
|
||||
// A mesh placed in the scene. mId stays the same while the object lives, even when the scene storage moves it.
|
||||
class Object {
|
||||
public:
|
||||
Object() = default;
|
||||
[[nodiscard]] uint8 id() const { return mId; }
|
||||
|
||||
~Object() {
|
||||
delete mGUPBuffers;
|
||||
};
|
||||
// Rebuilds the world-space cache after mTopology changed.
|
||||
void updateCache() { mCache.update(mTopology); }
|
||||
|
||||
public:
|
||||
Topology mTopology;
|
||||
TopologyCache mCache;
|
||||
GPUBuffers* mGUPBuffers = nullptr;
|
||||
|
||||
private:
|
||||
friend class Scene;
|
||||
uint8 mId = 0;
|
||||
};
|
||||
|
||||
struct PointLight {
|
||||
|
|
@ -44,47 +39,42 @@ namespace tp {
|
|||
halnf intensity = 1.f;
|
||||
};
|
||||
|
||||
struct RayCastData {
|
||||
Object* obj = nullptr;
|
||||
TrigCache* trig = nullptr;
|
||||
Vec3F hitPos = { 0, 0, 0 };
|
||||
bool hit = false;
|
||||
bool inv = false;
|
||||
struct RayHit {
|
||||
const Object* object = nullptr;
|
||||
const TrigCache* trig = nullptr;
|
||||
Vec3F position;
|
||||
halnf distance = 0;
|
||||
};
|
||||
|
||||
// The scene description could not be read.
|
||||
class SceneLoadError : public std::runtime_error {
|
||||
public:
|
||||
using std::runtime_error::runtime_error;
|
||||
};
|
||||
|
||||
class Scene {
|
||||
struct IOError : public std::exception {
|
||||
explicit IOError(std::string in) :
|
||||
description(std::move(in)) {}
|
||||
|
||||
std::string description;
|
||||
};
|
||||
|
||||
public:
|
||||
Scene() = default;
|
||||
// Reads a Lua scene description and the OBJ meshes it names; throws SceneLoadError.
|
||||
static Scene loadLua(const std::string& scenePath);
|
||||
|
||||
bool load(const std::string& scenePath);
|
||||
// Appends every mesh of an OBJ file; throws SceneLoadError.
|
||||
void loadOBJ(const std::string& objectsPath);
|
||||
|
||||
bool loadLuaFormat(const std::string& scenePath);
|
||||
bool loadOBJFormat(const std::string& objectsPath);
|
||||
Object& addObject(Object object);
|
||||
[[nodiscard]] Object* findObject(uint8 id);
|
||||
|
||||
const RayCastData& castRay(const Ray& ray, alnf farVal);
|
||||
// Closest hit within maxDistance; uses the caches built by updateCache().
|
||||
[[nodiscard]] std::optional<RayHit> castRay(const Ray& ray, halnf maxDistance) const;
|
||||
|
||||
void updateCache();
|
||||
|
||||
private:
|
||||
Vec3F getVec3(lua_State* state, const char* name);
|
||||
Vec2F getVec2(lua_State* state, const char* name);
|
||||
|
||||
int readLight(lua_State* L, tp::PointLight* light);
|
||||
|
||||
public:
|
||||
Buffer<Object> mObjects;
|
||||
Buffer<PointLight> mLights;
|
||||
Camera mCamera;
|
||||
|
||||
RenderSettings mRenderSettings;
|
||||
|
||||
RayCastData mRayCastData;
|
||||
private:
|
||||
uint8 mNextObjectId = 1;
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,30 +3,11 @@
|
|||
|
||||
using namespace tp;
|
||||
|
||||
Vec3F TrigCache::gHitPos;
|
||||
|
||||
TrigCache::TrigCache() :
|
||||
mP1(0),
|
||||
mP2(0),
|
||||
mP3(0) {}
|
||||
|
||||
TrigCache::TrigCache(ualni v1, ualni v2, ualni v3) :
|
||||
mP1(v1),
|
||||
mP2(v2),
|
||||
mP3(v3) {}
|
||||
|
||||
TrigCache::TrigCache(const TrigCache& in) {
|
||||
mP1 = in.mP1;
|
||||
mP2 = in.mP2;
|
||||
mP3 = in.mP3;
|
||||
|
||||
mEdgeP1P2 = in.mEdgeP1P2;
|
||||
mEdgeP1P3 = in.mEdgeP1P3;
|
||||
|
||||
mOrigin = in.mOrigin;
|
||||
mNormal = in.mNormal;
|
||||
}
|
||||
|
||||
void TrigCache::updateCache(const Buffer<Vec3F>& points) {
|
||||
mOrigin = points[mP1];
|
||||
mEdgeP1P2 = points[mP2] - points[mP1];
|
||||
|
|
@ -34,75 +15,51 @@ void TrigCache::updateCache(const Buffer<Vec3F>& points) {
|
|||
mNormal = (points[mP2] - points[mP1]).cross(points[mP3] - points[mP1]).unitV();
|
||||
}
|
||||
|
||||
bool TrigCache::castRay(const Ray& ray) const {
|
||||
static Vec3F h, s, q;
|
||||
static halnf a, f, u, v;
|
||||
static halnf t;
|
||||
|
||||
if (ray.dir.dot(mNormal) > 0 && 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
h = ray.dir.cross(mEdgeP1P3);
|
||||
a = mEdgeP1P2.dot(h);
|
||||
|
||||
// Moller-Trumbore intersection.
|
||||
std::optional<halnf> TrigCache::castRay(const Ray& ray) const {
|
||||
const Vec3F h = ray.dir.cross(mEdgeP1P3);
|
||||
const halnf a = (halnf) mEdgeP1P2.dot(h);
|
||||
if (a > -EPSILON && a < EPSILON) {
|
||||
return false;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
f = 1.f / a;
|
||||
s = ray.pos - mOrigin;
|
||||
u = f * s.dot(h);
|
||||
|
||||
if (u < 0.0 || u > 1.0) {
|
||||
return false;
|
||||
const halnf f = 1.f / a;
|
||||
const Vec3F s = ray.pos - mOrigin;
|
||||
const halnf u = f * (halnf) s.dot(h);
|
||||
if (u < 0.f || u > 1.f) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
q = s.cross(mEdgeP1P2);
|
||||
v = f * ray.dir.dot(q);
|
||||
|
||||
const Vec3F q = s.cross(mEdgeP1P2);
|
||||
const halnf v = f * (halnf) ray.dir.dot(q);
|
||||
if (v < 0.f || u + v > 1.f) {
|
||||
return false;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
t = f * mEdgeP1P3.dot(q);
|
||||
if (t > EPSILON) {
|
||||
gHitPos = ray.pos + ray.dir * t;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
const halnf t = f * (halnf) mEdgeP1P3.dot(q);
|
||||
if (t <= EPSILON) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
const Vec3F& TrigCache::getHitPos() { return gHitPos; }
|
||||
|
||||
const Vec3F& TrigCache::getNormal() const { return mNormal; }
|
||||
|
||||
void TopologyCache::updateCache() {
|
||||
TransformedNormals.clear();
|
||||
TransformedPoints.clear();
|
||||
TrigCaches.clear();
|
||||
|
||||
if (!Source) {
|
||||
return;
|
||||
}
|
||||
|
||||
TransformedPoints.reserve(Source->Points.size());
|
||||
void TopologyCache::update(const Topology& source) {
|
||||
TransformedPoints.reserve(source.Points.size());
|
||||
for (auto idx : IterRange(TransformedPoints.size())) {
|
||||
TransformedPoints[idx] = Source->Basis.transform(Source->Points[idx]);
|
||||
TransformedPoints[idx] += Source->Origin;
|
||||
TransformedPoints[idx] = source.Basis.transform(source.Points[idx]) + source.Origin;
|
||||
}
|
||||
|
||||
TransformedNormals.reserve(Source->Normals.size());
|
||||
TransformedNormals.reserve(source.Normals.size());
|
||||
for (auto idx : IterRange(TransformedNormals.size())) {
|
||||
TransformedNormals[idx] = Source->Basis.transform(Source->Normals[idx]);
|
||||
TransformedNormals[idx] = source.Basis.transform(source.Normals[idx]);
|
||||
}
|
||||
|
||||
TrigCaches.reserve(Source->Indexes.size());
|
||||
TrigCaches.reserve(source.Indexes.size());
|
||||
for (auto idx : IterRange(TrigCaches.size())) {
|
||||
TrigCaches[idx].mP1 = Source->Indexes[idx].x;
|
||||
TrigCaches[idx].mP2 = Source->Indexes[idx].y;
|
||||
TrigCaches[idx].mP3 = Source->Indexes[idx].z;
|
||||
const auto& index = source.Indexes[idx];
|
||||
TrigCaches[idx] = TrigCache(index.x, index.y, index.z);
|
||||
TrigCaches[idx].updateCache(TransformedPoints);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,29 +3,31 @@
|
|||
#include "Buffer.hpp"
|
||||
#include "Camera.hpp"
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace tp {
|
||||
|
||||
// A triangle of a TopologyCache with the edges precomputed for ray intersection.
|
||||
class TrigCache {
|
||||
static Vec3F gHitPos;
|
||||
|
||||
public:
|
||||
ualni mP1, mP2, mP3;
|
||||
Vec3F mEdgeP1P2, mEdgeP1P3;
|
||||
ualni mP1 = 0;
|
||||
ualni mP2 = 0;
|
||||
ualni mP3 = 0;
|
||||
Vec3F mEdgeP1P2;
|
||||
Vec3F mEdgeP1P3;
|
||||
Vec3F mNormal;
|
||||
Vec3F mOrigin;
|
||||
|
||||
public:
|
||||
TrigCache();
|
||||
TrigCache(const TrigCache& in);
|
||||
TrigCache() = default;
|
||||
TrigCache(ualni v1, ualni v2, ualni v3);
|
||||
|
||||
public:
|
||||
static const Vec3F& getHitPos();
|
||||
[[nodiscard]] const Vec3F& getNormal() const;
|
||||
|
||||
public:
|
||||
void updateCache(const Buffer<Vec3F>& points);
|
||||
[[nodiscard]] bool castRay(const Ray& ray) const;
|
||||
|
||||
// Distance along the ray to the hit point, if the ray hits the triangle.
|
||||
[[nodiscard]] std::optional<halnf> castRay(const Ray& ray) const;
|
||||
};
|
||||
|
||||
struct Topology {
|
||||
|
|
@ -37,13 +39,12 @@ namespace tp {
|
|||
Buffer<Vec3<uhalni>> Indexes;
|
||||
};
|
||||
|
||||
// World-space copy of a Topology, rebuilt by update().
|
||||
struct TopologyCache {
|
||||
const Topology* Source = nullptr;
|
||||
|
||||
Buffer<Vec3F> TransformedPoints;
|
||||
Buffer<Vec3F> TransformedNormals;
|
||||
Buffer<TrigCache> TrigCaches;
|
||||
|
||||
void updateCache();
|
||||
void update(const Topology& source);
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,39 @@
|
|||
project(RasterRender)
|
||||
|
||||
### ---------------------- Externals --------------------- ###
|
||||
set(BINDINGS_INCLUDE ${GLEW_INCLUDE_DIR})
|
||||
set(BINDINGS_LIBS ${GLEW_LIB})
|
||||
find_package(Vulkan REQUIRED)
|
||||
find_program(GLSLC glslc HINTS ${Vulkan_GLSLC_EXECUTABLE} REQUIRED)
|
||||
|
||||
### ---------------------- Shaders --------------------- ###
|
||||
set(SHADER_GEN_DIR ${CMAKE_CURRENT_BINARY_DIR}/shaders_generated)
|
||||
set(SHADER_INCS "")
|
||||
foreach(SHADER object.vert depth.frag solid.frag)
|
||||
set(SHADER_SRC ${CMAKE_CURRENT_SOURCE_DIR}/shaders/${SHADER})
|
||||
set(SHADER_INC ${SHADER_GEN_DIR}/${SHADER}.inc)
|
||||
add_custom_command(
|
||||
OUTPUT ${SHADER_INC}
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${SHADER_GEN_DIR}
|
||||
COMMAND ${GLSLC} -O --target-env=vulkan1.0 -mfmt=c ${SHADER_SRC} -o ${SHADER_INC}
|
||||
DEPENDS ${SHADER_SRC}
|
||||
COMMENT "Compiling ${SHADER} to SPIR-V"
|
||||
VERBATIM
|
||||
)
|
||||
list(APPEND SHADER_INCS ${SHADER_INC})
|
||||
endforeach()
|
||||
add_custom_target(RasterRenderShaders DEPENDS ${SHADER_INCS})
|
||||
|
||||
### ---------------------- Static Library --------------------- ###
|
||||
file(GLOB SOURCES "./private/*.cpp" "./private/*/*.cpp")
|
||||
file(GLOB HEADERS "./public/*.hpp" "./public/*/*.hpp")
|
||||
file(GLOB SOURCES "./private/*.cpp")
|
||||
add_library(${PROJECT_NAME} STATIC ${SOURCES})
|
||||
add_dependencies(${PROJECT_NAME} RasterRenderShaders)
|
||||
|
||||
add_library(${PROJECT_NAME} STATIC ${SOURCES} ${HEADERS})
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC ./public/)
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE ./private/ ${SHADER_GEN_DIR})
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC 3DScene GraphicApplicationGpu)
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC ./public/ ${BINDINGS_INCLUDE} ./ext/)
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC Math Connection 3DScene)
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC ${BINDINGS_LIBS})
|
||||
### -------------------------- Tests -------------------------- ###
|
||||
if (NOT ANDROID)
|
||||
file(GLOB TEST_SOURCES "./tests/*.cpp")
|
||||
add_executable(Test${PROJECT_NAME} ${TEST_SOURCES})
|
||||
target_link_libraries(Test${PROJECT_NAME} ${PROJECT_NAME} UnitTest++)
|
||||
add_test(NAME Test${PROJECT_NAME} COMMAND Test${PROJECT_NAME})
|
||||
endif()
|
||||
|
|
|
|||
|
|
@ -1,159 +0,0 @@
|
|||
|
||||
#include "FrameBuffer.hpp"
|
||||
#include "GraphicsApi.hpp"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
void glerr(GLenum type) {
|
||||
printf("GL ERROR - %i\n", type);
|
||||
}
|
||||
|
||||
#define AssertGL(x) \
|
||||
{ \
|
||||
x; \
|
||||
GLenum __gle = glGetError(); \
|
||||
if (__gle != GL_NO_ERROR) glerr(__gle); \
|
||||
}
|
||||
|
||||
using namespace tp;
|
||||
|
||||
RenderBuffer::RenderBuffer(const Vec2F& size) :
|
||||
mSize(size), mSamples(0)
|
||||
{
|
||||
|
||||
mDrawBuffers[0] = { GL_COLOR_ATTACHMENT0 };
|
||||
|
||||
// --------- texture ---------
|
||||
AssertGL(glGenTextures(1, &mTextureId));
|
||||
AssertGL(glBindTexture(GL_TEXTURE_2D, mTextureId));
|
||||
// Give an empty image to OpenGL ( the last "0" )
|
||||
AssertGL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei) size.x, (GLsizei) size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)
|
||||
);
|
||||
// Poor filtering. Needed
|
||||
AssertGL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
|
||||
AssertGL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
|
||||
|
||||
// --------- depth ---------
|
||||
AssertGL(glGenRenderbuffers(1, &mDepthBufferID));
|
||||
AssertGL(glBindRenderbuffer(GL_RENDERBUFFER, mDepthBufferID));
|
||||
AssertGL(glRenderbufferStorage(GL_RENDERBUFFER, GLW_CONTEXT_DEPTH_COMPONENT, (GLsizei) size.x, (GLsizei) size.y));
|
||||
|
||||
// ------------ framebuffer ------------
|
||||
AssertGL(glGenFramebuffers(1, &mFrameBufferID));
|
||||
AssertGL(glBindFramebuffer(GL_FRAMEBUFFER, mFrameBufferID));
|
||||
AssertGL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, mDepthBufferID));
|
||||
// Set "renderedTexture" as our colour attachement #0
|
||||
AssertGL(glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, mTextureId, 0));
|
||||
// Set the list of draw buffers.
|
||||
AssertGL(glDrawBuffers(1, mDrawBuffers)); // "1" is the size of DrawBuffers
|
||||
ASSERT(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
RenderBuffer::RenderBuffer(const Vec2F& size, tp::uint1 samples) :
|
||||
mSize(size), mSamples(samples)
|
||||
{
|
||||
|
||||
mDrawBuffers[0] = { GL_COLOR_ATTACHMENT0 };
|
||||
|
||||
// ------- texture ---------
|
||||
AssertGL(glGenTextures(1, &mTextureId));
|
||||
AssertGL(glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, mTextureId));
|
||||
#ifdef ENV_OS_ANDROID
|
||||
AssertGL(
|
||||
glTexStorage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, samples, GL_RGBA, (GLsizei) size.x, (GLsizei) size.y, GL_TRUE)
|
||||
);
|
||||
#else
|
||||
AssertGL(
|
||||
glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, samples, GL_RGBA, (GLsizei) size.x, (GLsizei) size.y, GL_TRUE)
|
||||
);
|
||||
#endif
|
||||
// !?
|
||||
// AssertGL(glTexParameteri(GL_TEXTURE_2D_MULTISAMPLE, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
|
||||
// AssertGL(glTexParameteri(GL_TEXTURE_2D_MULTISAMPLE, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
|
||||
|
||||
// ------- depth -------
|
||||
AssertGL(glGenRenderbuffers(1, &mDepthBufferID));
|
||||
AssertGL(glBindRenderbuffer(GL_RENDERBUFFER, mDepthBufferID));
|
||||
AssertGL(glRenderbufferStorageMultisample(
|
||||
GL_RENDERBUFFER, samples, GLW_CONTEXT_DEPTH_COMPONENT, (GLsizei) size.x, (GLsizei) size.y
|
||||
));
|
||||
|
||||
// ------- fbuff -------
|
||||
AssertGL(glGenFramebuffers(1, &mFrameBufferID));
|
||||
AssertGL(glBindFramebuffer(GL_FRAMEBUFFER, mFrameBufferID));
|
||||
AssertGL(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, mTextureId, 0));
|
||||
AssertGL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, mDepthBufferID));
|
||||
AssertGL(glDrawBuffers(1, mDrawBuffers));
|
||||
ASSERT(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
uint4 RenderBuffer::buffId() const { return mFrameBufferID; }
|
||||
|
||||
void RenderBuffer::beginDraw() {
|
||||
AssertGL(glBindFramebuffer(GL_FRAMEBUFFER, mFrameBufferID));
|
||||
setViewport({ 0.f, 0.f, mSize.x, mSize.y });
|
||||
}
|
||||
|
||||
void RenderBuffer::setViewport(const RectF& viewport) {
|
||||
AssertGL(glViewport((GLsizei) viewport.x, (GLsizei) viewport.y, (GLsizei) viewport.z, (GLsizei) viewport.w));
|
||||
}
|
||||
|
||||
void RenderBuffer::clear() {
|
||||
AssertGL(glClearColor(mClearCol.r, mClearCol.g, mClearCol.b, mClearCol.a));
|
||||
AssertGL(glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT));
|
||||
}
|
||||
|
||||
void RenderBuffer::endDraw() {
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
glClearColor(0, 0, 0, 0);
|
||||
glUseProgram(0);
|
||||
}
|
||||
|
||||
RenderBuffer::~RenderBuffer() {
|
||||
glDeleteFramebuffers(1, &mFrameBufferID);
|
||||
glDeleteTextures(1, &mTextureId);
|
||||
glDeleteRenderbuffers(1, &mDepthBufferID);
|
||||
}
|
||||
|
||||
uint4 RenderBuffer::texId() const { return mTextureId; }
|
||||
|
||||
const Vec2F& RenderBuffer::getSize() const { return mSize; }
|
||||
|
||||
void RenderBuffer::resize(const Vec2F& size) {
|
||||
if (size == mSize) return;
|
||||
|
||||
// Update size
|
||||
mSize = size;
|
||||
|
||||
// Bind framebuffer
|
||||
AssertGL(glBindFramebuffer(GL_FRAMEBUFFER, mFrameBufferID));
|
||||
|
||||
// Resize color texture
|
||||
if (mSamples) {
|
||||
AssertGL(glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, mTextureId));
|
||||
AssertGL(glTexImage2DMultisample(
|
||||
GL_TEXTURE_2D_MULTISAMPLE, mSamples, GL_RGBA, (GLsizei)size.x, (GLsizei)size.y, GL_TRUE
|
||||
));
|
||||
AssertGL(glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, 0));
|
||||
} else {
|
||||
AssertGL(glBindTexture(GL_TEXTURE_2D, mTextureId));
|
||||
AssertGL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)size.x, (GLsizei)size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0));
|
||||
AssertGL(glBindTexture(GL_TEXTURE_2D, 0));
|
||||
}
|
||||
|
||||
// Resize depth renderbuffer
|
||||
if (mSamples) {
|
||||
AssertGL(glBindRenderbuffer(GL_RENDERBUFFER, mDepthBufferID));
|
||||
AssertGL(glRenderbufferStorageMultisample(
|
||||
GL_RENDERBUFFER, mSamples, GLW_CONTEXT_DEPTH_COMPONENT, (GLsizei)size.x, (GLsizei)size.y
|
||||
));
|
||||
} else {
|
||||
AssertGL(glBindRenderbuffer(GL_RENDERBUFFER, mDepthBufferID));
|
||||
AssertGL(glRenderbufferStorage(GL_RENDERBUFFER, GLW_CONTEXT_DEPTH_COMPONENT, (GLsizei)size.x, (GLsizei)size.y));
|
||||
}
|
||||
|
||||
// Unbind framebuffer
|
||||
AssertGL(glBindFramebuffer(GL_FRAMEBUFFER, 0));
|
||||
}
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "Scene.hpp"
|
||||
#include "GraphicsApi.hpp"
|
||||
|
||||
namespace tp {
|
||||
|
||||
class ObjectBuffers : public GPUBuffers {
|
||||
public:
|
||||
ObjectBuffers(Object* object) {
|
||||
mObject = (object);
|
||||
|
||||
auto& buff = mObject->mTopology.Points;
|
||||
auto& indices = mObject->mTopology.Indexes;
|
||||
|
||||
glGenVertexArrays(1, &VAO);
|
||||
glGenBuffers(1, &VBO);
|
||||
glGenBuffers(1, &EBO);
|
||||
|
||||
glBindVertexArray(VAO);
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, VBO);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(Vec3F) * buff.size(), buff.getBuff(), GL_STATIC_DRAW);
|
||||
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(Vec3<uhalni>) * indices.size(), indices.getBuff(), GL_STATIC_DRAW);
|
||||
|
||||
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), nullptr);
|
||||
glEnableVertexAttribArray(0);
|
||||
|
||||
// glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
Object* mObject = nullptr;
|
||||
|
||||
GLuint VAO = 0;
|
||||
GLuint VBO = 0;
|
||||
GLuint EBO = 0;
|
||||
|
||||
void drawCall() override {
|
||||
auto& indices = mObject->mTopology.Indexes;
|
||||
|
||||
glBindVertexArray(VAO);
|
||||
|
||||
// glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
|
||||
glDrawElements(GL_TRIANGLES, indices.size() * 3, GL_UNSIGNED_INT, nullptr);
|
||||
|
||||
// glDrawArrays(GL_TRIANGLES, 0, sizeof(buffer) / (2 * sizeof(float)));
|
||||
|
||||
// glBindVertexArray(0);
|
||||
}
|
||||
|
||||
~ObjectBuffers() {
|
||||
glDeleteBuffers(1, &VBO);
|
||||
glDeleteVertexArrays(1, &VAO);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
57
RasterRender/private/GpuBuffer.cpp
Normal file
57
RasterRender/private/GpuBuffer.cpp
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
#include "GpuBuffer.hpp"
|
||||
|
||||
#include "GpuError.hpp"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace tp {
|
||||
|
||||
GpuBuffer::GpuBuffer(GpuDevice& gpu, VkBufferUsageFlags usage, size_t capacity) :
|
||||
mGpu(gpu),
|
||||
mCapacity(capacity) {
|
||||
const VkDevice device = mGpu.device();
|
||||
|
||||
const VkBufferCreateInfo bufferInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
||||
.size = capacity,
|
||||
.usage = usage,
|
||||
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
||||
};
|
||||
requireSuccess(vkCreateBuffer(device, &bufferInfo, nullptr, &mBuffer), "vkCreateBuffer");
|
||||
|
||||
VkMemoryRequirements requirements{};
|
||||
vkGetBufferMemoryRequirements(device, mBuffer, &requirements);
|
||||
const VkMemoryAllocateInfo allocateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
||||
.allocationSize = requirements.size,
|
||||
.memoryTypeIndex = mGpu.findMemoryType(
|
||||
requirements.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
|
||||
),
|
||||
};
|
||||
const VkResult allocated = vkAllocateMemory(device, &allocateInfo, nullptr, &mMemory);
|
||||
if (allocated != VK_SUCCESS) {
|
||||
vkDestroyBuffer(device, mBuffer, nullptr);
|
||||
throw GpuError("vkAllocateMemory", allocated);
|
||||
}
|
||||
vkBindBufferMemory(device, mBuffer, mMemory, 0);
|
||||
}
|
||||
|
||||
GpuBuffer::~GpuBuffer() {
|
||||
vkDestroyBuffer(mGpu.device(), mBuffer, nullptr);
|
||||
vkFreeMemory(mGpu.device(), mMemory, nullptr);
|
||||
}
|
||||
|
||||
void GpuBuffer::write(const void* data, size_t size) {
|
||||
void* mapped = nullptr;
|
||||
requireSuccess(vkMapMemory(mGpu.device(), mMemory, 0, size, 0, &mapped), "vkMapMemory");
|
||||
std::memcpy(mapped, data, size);
|
||||
vkUnmapMemory(mGpu.device(), mMemory);
|
||||
}
|
||||
|
||||
void GpuBuffer::read(void* data, size_t size) const {
|
||||
void* mapped = nullptr;
|
||||
requireSuccess(vkMapMemory(mGpu.device(), mMemory, 0, size, 0, &mapped), "vkMapMemory");
|
||||
std::memcpy(data, mapped, size);
|
||||
vkUnmapMemory(mGpu.device(), mMemory);
|
||||
}
|
||||
}
|
||||
171
RasterRender/private/GraphicsPipeline.cpp
Normal file
171
RasterRender/private/GraphicsPipeline.cpp
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
#include "GraphicsPipeline.hpp"
|
||||
|
||||
#include "GpuError.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
namespace tp {
|
||||
|
||||
namespace {
|
||||
|
||||
class ShaderModules {
|
||||
public:
|
||||
ShaderModules(VkDevice device, std::span<const ShaderStage> stages) :
|
||||
mDevice(device) {
|
||||
for (const ShaderStage& stage : stages) {
|
||||
const VkShaderModuleCreateInfo createInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
|
||||
.codeSize = stage.spirv.size_bytes(),
|
||||
.pCode = stage.spirv.data(),
|
||||
};
|
||||
VkShaderModule module = VK_NULL_HANDLE;
|
||||
requireSuccess(vkCreateShaderModule(device, &createInfo, nullptr, &module), "vkCreateShaderModule");
|
||||
mModules.push_back(module);
|
||||
mStages.push_back({
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
|
||||
.stage = stage.stage,
|
||||
.module = module,
|
||||
.pName = "main",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
~ShaderModules() {
|
||||
for (VkShaderModule module : mModules) {
|
||||
vkDestroyShaderModule(mDevice, module, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
ShaderModules(const ShaderModules&) = delete;
|
||||
ShaderModules& operator=(const ShaderModules&) = delete;
|
||||
|
||||
[[nodiscard]] const std::vector<VkPipelineShaderStageCreateInfo>& stages() const { return mStages; }
|
||||
|
||||
private:
|
||||
VkDevice mDevice;
|
||||
std::vector<VkShaderModule> mModules;
|
||||
std::vector<VkPipelineShaderStageCreateInfo> mStages;
|
||||
};
|
||||
}
|
||||
|
||||
GraphicsPipeline::GraphicsPipeline(GpuDevice& gpu, const PipelineDescription& description) :
|
||||
mGpu(gpu),
|
||||
mPushConstantStages(description.pushConstantStages),
|
||||
mPushConstantSize(description.pushConstantSize) {
|
||||
const VkDevice device = mGpu.device();
|
||||
|
||||
const VkPushConstantRange pushRange{
|
||||
.stageFlags = description.pushConstantStages,
|
||||
.offset = 0,
|
||||
.size = description.pushConstantSize,
|
||||
};
|
||||
const VkPipelineLayoutCreateInfo layoutInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
|
||||
.pushConstantRangeCount = description.pushConstantSize ? 1u : 0u,
|
||||
.pPushConstantRanges = &pushRange,
|
||||
};
|
||||
requireSuccess(vkCreatePipelineLayout(device, &layoutInfo, nullptr, &mLayout), "vkCreatePipelineLayout");
|
||||
|
||||
const ShaderModules modules(device, description.stages);
|
||||
|
||||
const VkVertexInputBindingDescription binding{
|
||||
.binding = 0,
|
||||
.stride = description.vertexStride,
|
||||
.inputRate = VK_VERTEX_INPUT_RATE_VERTEX,
|
||||
};
|
||||
const VkVertexInputAttributeDescription attribute{
|
||||
.location = 0,
|
||||
.binding = 0,
|
||||
.format = description.vertexFormat,
|
||||
.offset = 0,
|
||||
};
|
||||
const VkPipelineVertexInputStateCreateInfo vertexInput{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
|
||||
.vertexBindingDescriptionCount = 1,
|
||||
.pVertexBindingDescriptions = &binding,
|
||||
.vertexAttributeDescriptionCount = 1,
|
||||
.pVertexAttributeDescriptions = &attribute,
|
||||
};
|
||||
const VkPipelineInputAssemblyStateCreateInfo inputAssembly{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
|
||||
.topology = description.topology,
|
||||
};
|
||||
const VkPipelineViewportStateCreateInfo viewportState{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
|
||||
.viewportCount = 1,
|
||||
.scissorCount = 1,
|
||||
};
|
||||
const VkPipelineRasterizationStateCreateInfo rasterization{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
|
||||
.polygonMode = VK_POLYGON_MODE_FILL,
|
||||
.cullMode = VK_CULL_MODE_NONE,
|
||||
.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE,
|
||||
.lineWidth = 1.0f,
|
||||
};
|
||||
const VkPipelineMultisampleStateCreateInfo multisample{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
|
||||
.rasterizationSamples = description.samples,
|
||||
};
|
||||
const VkPipelineDepthStencilStateCreateInfo depthStencil{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
|
||||
.depthTestEnable = VK_TRUE,
|
||||
.depthWriteEnable = VK_TRUE,
|
||||
.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL,
|
||||
};
|
||||
const VkPipelineColorBlendAttachmentState blendAttachment{
|
||||
.blendEnable = VK_FALSE,
|
||||
.colorWriteMask =
|
||||
VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT,
|
||||
};
|
||||
const VkPipelineColorBlendStateCreateInfo colorBlend{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
|
||||
.attachmentCount = 1,
|
||||
.pAttachments = &blendAttachment,
|
||||
};
|
||||
const std::array<VkDynamicState, 2> dynamicStates = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
|
||||
const VkPipelineDynamicStateCreateInfo dynamicState{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
|
||||
.dynamicStateCount = (uint32_t) dynamicStates.size(),
|
||||
.pDynamicStates = dynamicStates.data(),
|
||||
};
|
||||
|
||||
const VkGraphicsPipelineCreateInfo pipelineInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
|
||||
.stageCount = (uint32_t) modules.stages().size(),
|
||||
.pStages = modules.stages().data(),
|
||||
.pVertexInputState = &vertexInput,
|
||||
.pInputAssemblyState = &inputAssembly,
|
||||
.pViewportState = &viewportState,
|
||||
.pRasterizationState = &rasterization,
|
||||
.pMultisampleState = &multisample,
|
||||
.pDepthStencilState = &depthStencil,
|
||||
.pColorBlendState = &colorBlend,
|
||||
.pDynamicState = &dynamicState,
|
||||
.layout = mLayout,
|
||||
.renderPass = description.renderPass,
|
||||
.subpass = 0,
|
||||
};
|
||||
const VkResult created = vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &mPipeline);
|
||||
if (created != VK_SUCCESS) {
|
||||
vkDestroyPipelineLayout(device, mLayout, nullptr);
|
||||
throw GpuError("vkCreateGraphicsPipelines", created);
|
||||
}
|
||||
}
|
||||
|
||||
GraphicsPipeline::~GraphicsPipeline() {
|
||||
vkDestroyPipeline(mGpu.device(), mPipeline, nullptr);
|
||||
vkDestroyPipelineLayout(mGpu.device(), mLayout, nullptr);
|
||||
}
|
||||
|
||||
void GraphicsPipeline::bind(VkCommandBuffer commandBuffer) const {
|
||||
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, mPipeline);
|
||||
}
|
||||
|
||||
void GraphicsPipeline::pushBytes(VkCommandBuffer commandBuffer, const void* data, uint32_t size) const {
|
||||
if (size != mPushConstantSize) {
|
||||
throw GpuError("push constants of the declared size", VK_ERROR_UNKNOWN);
|
||||
}
|
||||
vkCmdPushConstants(commandBuffer, mLayout, mPushConstantStages, 0, size, data);
|
||||
}
|
||||
}
|
||||
124
RasterRender/private/RasterRender.cpp
Normal file
124
RasterRender/private/RasterRender.cpp
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
#include "RasterRender.hpp"
|
||||
|
||||
#include "ClipSpace.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace tp {
|
||||
|
||||
namespace {
|
||||
|
||||
const uint32_t kObjectVert[] =
|
||||
#include "object.vert.inc"
|
||||
;
|
||||
|
||||
const uint32_t kDepthFrag[] =
|
||||
#include "depth.frag.inc"
|
||||
;
|
||||
|
||||
const uint32_t kSolidFrag[] =
|
||||
#include "solid.frag.inc"
|
||||
;
|
||||
|
||||
struct ObjectConstants {
|
||||
Mat4F worldToClip;
|
||||
Mat4F objectToWorld;
|
||||
};
|
||||
static_assert(sizeof(ObjectConstants) == 2 * 16 * sizeof(float));
|
||||
|
||||
constexpr uint4 kSamples = 4;
|
||||
|
||||
Mat4F objectToWorld(const Topology& topology) {
|
||||
Mat4F out = toMat4(topology.Basis);
|
||||
out[0][3] = topology.Origin.x;
|
||||
out[1][3] = topology.Origin.y;
|
||||
out[2][3] = topology.Origin.z;
|
||||
return out.transpose();
|
||||
}
|
||||
|
||||
GraphicsPipeline createPipeline(GpuDevice& gpu, const RenderTarget& target, std::span<const uint32_t> fragment) {
|
||||
const std::array<ShaderStage, 2> stages = {
|
||||
ShaderStage{ VK_SHADER_STAGE_VERTEX_BIT, kObjectVert },
|
||||
ShaderStage{ VK_SHADER_STAGE_FRAGMENT_BIT, fragment },
|
||||
};
|
||||
return GraphicsPipeline(
|
||||
gpu,
|
||||
{
|
||||
.stages = stages,
|
||||
.vertexStride = sizeof(Vec3F),
|
||||
.vertexFormat = VK_FORMAT_R32G32B32_SFLOAT,
|
||||
.pushConstantStages = VK_SHADER_STAGE_VERTEX_BIT,
|
||||
.pushConstantSize = sizeof(ObjectConstants),
|
||||
.renderPass = target.renderPass(),
|
||||
.samples = target.samples(),
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
RasterRender::RasterRender(GpuDevice& gpu, Vec2<uint4> size) :
|
||||
mGpu(gpu),
|
||||
mTarget(gpu, size, kSamples),
|
||||
mDepthPipeline(createPipeline(gpu, mTarget, kDepthFrag)),
|
||||
mSolidPipeline(createPipeline(gpu, mTarget, kSolidFrag)) {}
|
||||
|
||||
RasterRender::~RasterRender() = default;
|
||||
|
||||
void RasterRender::forgetObject(uint8 objectId) { mMeshes.erase(objectId); }
|
||||
|
||||
void RasterRender::syncMeshes(const Scene& scene) {
|
||||
std::unordered_set<uint8> alive;
|
||||
for (ualni index = 0; index < scene.mObjects.size(); index++) {
|
||||
const Object& object = scene.mObjects[index];
|
||||
alive.insert(object.id());
|
||||
if (mMeshes.contains(object.id())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& points = object.mTopology.Points;
|
||||
const auto& indexes = object.mTopology.Indexes;
|
||||
ObjectMesh mesh;
|
||||
mesh.indexCount = (uint32_t) indexes.size() * 3;
|
||||
if (mesh.indexCount) {
|
||||
mesh.vertices = std::make_unique<GpuBuffer>(mGpu, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, sizeof(Vec3F) * points.size());
|
||||
mesh.vertices->write(points.getBuff(), sizeof(Vec3F) * points.size());
|
||||
mesh.indices = std::make_unique<GpuBuffer>(mGpu, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, sizeof(Vec3<uhalni>) * indexes.size());
|
||||
mesh.indices->write(indexes.getBuff(), sizeof(Vec3<uhalni>) * indexes.size());
|
||||
}
|
||||
mMeshes.emplace(object.id(), std::move(mesh));
|
||||
}
|
||||
std::erase_if(mMeshes, [&alive](const auto& entry) { return !alive.contains(entry.first); });
|
||||
}
|
||||
|
||||
void RasterRender::drawObject(
|
||||
VkCommandBuffer commandBuffer, const GraphicsPipeline& pipeline, const Mat4F& worldToClip, const Object& object
|
||||
) {
|
||||
const ObjectMesh& mesh = mMeshes.at(object.id());
|
||||
if (!mesh.indexCount) {
|
||||
return;
|
||||
}
|
||||
pipeline.push(commandBuffer, ObjectConstants{ worldToClip, objectToWorld(object.mTopology) });
|
||||
const VkBuffer vertices = mesh.vertices->handle();
|
||||
const VkDeviceSize offset = 0;
|
||||
vkCmdBindVertexBuffers(commandBuffer, 0, 1, &vertices, &offset);
|
||||
vkCmdBindIndexBuffer(commandBuffer, mesh.indices->handle(), 0, VK_INDEX_TYPE_UINT32);
|
||||
vkCmdDrawIndexed(commandBuffer, mesh.indexCount, 1, 0, 0, 0);
|
||||
}
|
||||
|
||||
void RasterRender::render(const Scene& scene, const Object* highlighted) {
|
||||
syncMeshes(scene);
|
||||
const Mat4F worldToClip = vulkanWorldToClip(scene.mCamera);
|
||||
|
||||
mTarget.render(RGBA(0.f), [&](VkCommandBuffer commandBuffer) {
|
||||
mDepthPipeline.bind(commandBuffer);
|
||||
for (ualni index = 0; index < scene.mObjects.size(); index++) {
|
||||
drawObject(commandBuffer, mDepthPipeline, worldToClip, scene.mObjects[index]);
|
||||
}
|
||||
if (highlighted) {
|
||||
mSolidPipeline.bind(commandBuffer);
|
||||
drawObject(commandBuffer, mSolidPipeline, worldToClip, *highlighted);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,111 +0,0 @@
|
|||
#include "RasterRender.hpp"
|
||||
#include "GPUBuffers.hpp"
|
||||
|
||||
using namespace tp;
|
||||
|
||||
RasterRender::RasterRender() {
|
||||
mDefaultShader.load("rsc/shaders/default.vert", nullptr, "rsc/shaders/default.frag", true);
|
||||
mSolidShader.load("rsc/shaders/default.vert", nullptr, "rsc/shaders/solid.frag", true);
|
||||
}
|
||||
|
||||
RasterRender::~RasterRender() {}
|
||||
|
||||
void RasterRender::resize(const Vec2<tp::ualni>& size) {
|
||||
auto sizeF = Vec2F(size.x, size.y);
|
||||
mRenderBuffer.resize(sizeF);
|
||||
mTempBuffer.resize(sizeF);
|
||||
}
|
||||
|
||||
uint4 RasterRender::getRenderBufferID() { return mRenderBuffer.texId(); }
|
||||
|
||||
Vec2F RasterRender::getBufferSize() { return mRenderBuffer.getSize(); }
|
||||
|
||||
void RasterRender::bindCameraShaderAttributes(const Mat4F& cameraMat) {
|
||||
static auto camera = (GLint) mDefaultShader.getu("Camera");
|
||||
glUniformMatrix4fv(camera, 1, true, &cameraMat[0][0]);
|
||||
}
|
||||
|
||||
void RasterRender::bindObjectShaderAttributes(const Object& object) {
|
||||
static auto origin = (GLint) mDefaultShader.getu("Origin");
|
||||
static auto basis = (GLint) mDefaultShader.getu("Basis");
|
||||
|
||||
Mat4F basisMat = toMat4(object.mTopology.Basis);
|
||||
Vec4F originPoint = toVec4(object.mTopology.Origin);
|
||||
|
||||
basisMat = basisMat.transpose();
|
||||
|
||||
glUniform4fv(origin, 1, &originPoint[0]);
|
||||
glUniformMatrix4fv(basis, 1, false, &basisMat[0][0]);
|
||||
}
|
||||
|
||||
void RasterRender::renderDefault(const Scene& geometry) {
|
||||
mRenderBuffer.mClearCol = { 0.0f, 0.0f, 0.0f, 0.f };
|
||||
|
||||
mRenderBuffer.beginDraw();
|
||||
mRenderBuffer.clear();
|
||||
|
||||
mDefaultShader.bind();
|
||||
|
||||
Mat4F cameraMat = geometry.mCamera.calculateTransformationMatrix();
|
||||
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
|
||||
// glPolygonMode(GL_FRONT, GL_LINE);
|
||||
// glPolygonMode(GL_BACK, GL_LINE);
|
||||
|
||||
// auto rotator = Mat3F::rotatorZ(alnf(0.001));
|
||||
|
||||
bindCameraShaderAttributes(cameraMat);
|
||||
|
||||
for (auto object : geometry.mObjects) {
|
||||
// object->mTopology.Origin += 0.01;
|
||||
// object->mTopology.Basis = rotator * object->mTopology.Basis;
|
||||
|
||||
bindObjectShaderAttributes(object.data());
|
||||
object->mGUPBuffers->drawCall();
|
||||
}
|
||||
|
||||
mDefaultShader.unbind();
|
||||
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
|
||||
// glPolygonMode(GL_FRONT, GL_FILL);
|
||||
// glPolygonMode(GL_BACK, GL_FILL);
|
||||
|
||||
mRenderBuffer.endDraw();
|
||||
}
|
||||
|
||||
void RasterRender::renderOutline(const Camera& camera, const Object& object) {
|
||||
mRenderBuffer.beginDraw();
|
||||
|
||||
// glEnable(GL_STENCIL_TEST);
|
||||
|
||||
mSolidShader.bind();
|
||||
|
||||
bindCameraShaderAttributes(camera.calculateTransformationMatrix());
|
||||
bindObjectShaderAttributes(object);
|
||||
|
||||
object.mGUPBuffers->drawCall();
|
||||
|
||||
mSolidShader.unbind();
|
||||
|
||||
// glDisable(GL_STENCIL_TEST);
|
||||
|
||||
mRenderBuffer.endDraw();
|
||||
}
|
||||
|
||||
void RasterRender::beginRender(const Scene& geometry, const Vec2<tp::ualni>& size) {
|
||||
resize(size);
|
||||
|
||||
for (auto object : geometry.mObjects) {
|
||||
if (!object->mGUPBuffers) {
|
||||
object->mGUPBuffers = new ObjectBuffers(&object.data());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RasterRender::endRender() {
|
||||
// pass
|
||||
}
|
||||
|
||||
RenderBuffer* RasterRender::getRenderBuffer() { return &mRenderBuffer; }
|
||||
399
RasterRender/private/RenderTarget.cpp
Normal file
399
RasterRender/private/RenderTarget.cpp
Normal file
|
|
@ -0,0 +1,399 @@
|
|||
#include "RenderTarget.hpp"
|
||||
|
||||
#include "GpuBuffer.hpp"
|
||||
#include "GpuError.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
|
||||
namespace tp {
|
||||
|
||||
namespace {
|
||||
constexpr VkFormat kColorFormat = VK_FORMAT_R8G8B8A8_UNORM;
|
||||
|
||||
VkFormat selectDepthFormat(VkPhysicalDevice device) {
|
||||
for (VkFormat format : { VK_FORMAT_D32_SFLOAT, VK_FORMAT_D24_UNORM_S8_UINT, VK_FORMAT_D16_UNORM }) {
|
||||
VkFormatProperties properties{};
|
||||
vkGetPhysicalDeviceFormatProperties(device, format, &properties);
|
||||
if (properties.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) {
|
||||
return format;
|
||||
}
|
||||
}
|
||||
throw GpuError("selecting a depth format", VK_ERROR_FORMAT_NOT_SUPPORTED);
|
||||
}
|
||||
|
||||
// The largest supported sample count not above the requested one.
|
||||
VkSampleCountFlagBits selectSamples(VkPhysicalDevice device, uint4 requested) {
|
||||
VkPhysicalDeviceProperties properties{};
|
||||
vkGetPhysicalDeviceProperties(device, &properties);
|
||||
const VkSampleCountFlags supported =
|
||||
properties.limits.framebufferColorSampleCounts & properties.limits.framebufferDepthSampleCounts;
|
||||
for (VkSampleCountFlagBits samples :
|
||||
{ VK_SAMPLE_COUNT_8_BIT, VK_SAMPLE_COUNT_4_BIT, VK_SAMPLE_COUNT_2_BIT }) {
|
||||
if ((uint4) samples <= requested && (supported & samples)) {
|
||||
return samples;
|
||||
}
|
||||
}
|
||||
return VK_SAMPLE_COUNT_1_BIT;
|
||||
}
|
||||
|
||||
VkExtent2D toExtent(Vec2<uint4> size) { return { std::max(size.x, 1u), std::max(size.y, 1u) }; }
|
||||
|
||||
void imageBarrier(
|
||||
VkCommandBuffer commandBuffer,
|
||||
VkImage image,
|
||||
VkImageLayout from,
|
||||
VkImageLayout to,
|
||||
VkAccessFlags srcAccess,
|
||||
VkAccessFlags dstAccess,
|
||||
VkPipelineStageFlags srcStage,
|
||||
VkPipelineStageFlags dstStage
|
||||
) {
|
||||
const VkImageMemoryBarrier barrier{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||
.srcAccessMask = srcAccess,
|
||||
.dstAccessMask = dstAccess,
|
||||
.oldLayout = from,
|
||||
.newLayout = to,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = image,
|
||||
.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 },
|
||||
};
|
||||
vkCmdPipelineBarrier(commandBuffer, srcStage, dstStage, 0, 0, nullptr, 0, nullptr, 1, &barrier);
|
||||
}
|
||||
}
|
||||
|
||||
RenderTarget::RenderTarget(GpuDevice& gpu, Vec2<uint4> size, uint4 samples) :
|
||||
mGpu(gpu),
|
||||
mExtent(toExtent(size)),
|
||||
mSamples(selectSamples(gpu.physicalDevice(), samples)),
|
||||
mDepthFormat(selectDepthFormat(gpu.physicalDevice())) {
|
||||
const VkSamplerCreateInfo samplerInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
|
||||
.magFilter = VK_FILTER_LINEAR,
|
||||
.minFilter = VK_FILTER_LINEAR,
|
||||
.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST,
|
||||
.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
|
||||
.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
|
||||
.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
|
||||
};
|
||||
requireSuccess(vkCreateSampler(mGpu.device(), &samplerInfo, nullptr, &mSampler), "vkCreateSampler");
|
||||
createRenderPass();
|
||||
createImages();
|
||||
mRegistryId = mGpu.renderTargets().registerTarget({});
|
||||
publish();
|
||||
}
|
||||
|
||||
RenderTarget::~RenderTarget() {
|
||||
mGpu.renderTargets().unregisterTarget(mRegistryId);
|
||||
mGpu.waitIdle();
|
||||
destroyImages();
|
||||
vkDestroyRenderPass(mGpu.device(), mRenderPass, nullptr);
|
||||
vkDestroySampler(mGpu.device(), mSampler, nullptr);
|
||||
}
|
||||
|
||||
void RenderTarget::createRenderPass() {
|
||||
const bool multisampled = mSamples != VK_SAMPLE_COUNT_1_BIT;
|
||||
// Note: a resolved or single-sample color image ends the pass ready for Canvas sampling.
|
||||
const VkImageLayout colorFinal =
|
||||
multisampled ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
|
||||
std::vector<VkAttachmentDescription> attachments = {
|
||||
{
|
||||
.format = kColorFormat,
|
||||
.samples = mSamples,
|
||||
.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
|
||||
.storeOp = multisampled ? VK_ATTACHMENT_STORE_OP_DONT_CARE : VK_ATTACHMENT_STORE_OP_STORE,
|
||||
.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
|
||||
.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
|
||||
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
.finalLayout = colorFinal,
|
||||
},
|
||||
{
|
||||
.format = mDepthFormat,
|
||||
.samples = mSamples,
|
||||
.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
|
||||
.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
|
||||
.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
|
||||
.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
|
||||
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
|
||||
},
|
||||
};
|
||||
if (multisampled) {
|
||||
attachments.push_back({
|
||||
.format = kColorFormat,
|
||||
.samples = VK_SAMPLE_COUNT_1_BIT,
|
||||
.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
|
||||
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
|
||||
.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
|
||||
.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
|
||||
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
.finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
||||
});
|
||||
}
|
||||
|
||||
const VkAttachmentReference colorRef{ 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
|
||||
const VkAttachmentReference depthRef{ 1, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL };
|
||||
const VkAttachmentReference resolveRef{ 2, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
|
||||
const VkSubpassDescription subpass{
|
||||
.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
.colorAttachmentCount = 1,
|
||||
.pColorAttachments = &colorRef,
|
||||
.pResolveAttachments = multisampled ? &resolveRef : nullptr,
|
||||
.pDepthStencilAttachment = &depthRef,
|
||||
};
|
||||
|
||||
// Note: orders this pass after earlier Canvas sampling and before later sampling of the result.
|
||||
const std::array<VkSubpassDependency, 2> dependencies = {
|
||||
VkSubpassDependency{
|
||||
.srcSubpass = VK_SUBPASS_EXTERNAL,
|
||||
.dstSubpass = 0,
|
||||
.srcStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
|
||||
.dstStageMask =
|
||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT,
|
||||
.srcAccessMask = VK_ACCESS_SHADER_READ_BIT,
|
||||
.dstAccessMask =
|
||||
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
|
||||
},
|
||||
VkSubpassDependency{
|
||||
.srcSubpass = 0,
|
||||
.dstSubpass = VK_SUBPASS_EXTERNAL,
|
||||
.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
||||
.dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
|
||||
.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
|
||||
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
|
||||
},
|
||||
};
|
||||
|
||||
const VkRenderPassCreateInfo createInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
|
||||
.attachmentCount = (uint32_t) attachments.size(),
|
||||
.pAttachments = attachments.data(),
|
||||
.subpassCount = 1,
|
||||
.pSubpasses = &subpass,
|
||||
.dependencyCount = (uint32_t) dependencies.size(),
|
||||
.pDependencies = dependencies.data(),
|
||||
};
|
||||
requireSuccess(vkCreateRenderPass(mGpu.device(), &createInfo, nullptr, &mRenderPass), "vkCreateRenderPass");
|
||||
}
|
||||
|
||||
RenderTarget::Image RenderTarget::createImage(
|
||||
VkFormat format, VkImageUsageFlags usage, VkSampleCountFlagBits samples, VkImageAspectFlags aspect
|
||||
) const {
|
||||
const VkDevice device = mGpu.device();
|
||||
Image out;
|
||||
|
||||
const VkImageCreateInfo imageInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
||||
.imageType = VK_IMAGE_TYPE_2D,
|
||||
.format = format,
|
||||
.extent = { mExtent.width, mExtent.height, 1 },
|
||||
.mipLevels = 1,
|
||||
.arrayLayers = 1,
|
||||
.samples = samples,
|
||||
.tiling = VK_IMAGE_TILING_OPTIMAL,
|
||||
.usage = usage,
|
||||
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
};
|
||||
requireSuccess(vkCreateImage(device, &imageInfo, nullptr, &out.image), "vkCreateImage");
|
||||
|
||||
VkMemoryRequirements requirements{};
|
||||
vkGetImageMemoryRequirements(device, out.image, &requirements);
|
||||
const VkMemoryAllocateInfo allocateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
||||
.allocationSize = requirements.size,
|
||||
.memoryTypeIndex = mGpu.findMemoryType(requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT),
|
||||
};
|
||||
requireSuccess(vkAllocateMemory(device, &allocateInfo, nullptr, &out.memory), "vkAllocateMemory");
|
||||
vkBindImageMemory(device, out.image, out.memory, 0);
|
||||
|
||||
const VkImageViewCreateInfo viewInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
||||
.image = out.image,
|
||||
.viewType = VK_IMAGE_VIEW_TYPE_2D,
|
||||
.format = format,
|
||||
.subresourceRange = { aspect, 0, 1, 0, 1 },
|
||||
};
|
||||
requireSuccess(vkCreateImageView(device, &viewInfo, nullptr, &out.view), "vkCreateImageView");
|
||||
return out;
|
||||
}
|
||||
|
||||
void RenderTarget::destroyImage(Image& image) const {
|
||||
vkDestroyImageView(mGpu.device(), image.view, nullptr);
|
||||
vkDestroyImage(mGpu.device(), image.image, nullptr);
|
||||
vkFreeMemory(mGpu.device(), image.memory, nullptr);
|
||||
image = {};
|
||||
}
|
||||
|
||||
void RenderTarget::createImages() {
|
||||
const bool multisampled = mSamples != VK_SAMPLE_COUNT_1_BIT;
|
||||
const VkImageUsageFlags resolvedUsage =
|
||||
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
|
||||
VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
|
||||
mColor = createImage(
|
||||
kColorFormat,
|
||||
multisampled ? VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT : resolvedUsage,
|
||||
mSamples,
|
||||
VK_IMAGE_ASPECT_COLOR_BIT
|
||||
);
|
||||
mDepth = createImage(mDepthFormat, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, mSamples, VK_IMAGE_ASPECT_DEPTH_BIT);
|
||||
|
||||
std::vector<VkImageView> views = { mColor.view, mDepth.view };
|
||||
if (multisampled) {
|
||||
mResolve = createImage(kColorFormat, resolvedUsage, VK_SAMPLE_COUNT_1_BIT, VK_IMAGE_ASPECT_COLOR_BIT);
|
||||
views.push_back(mResolve.view);
|
||||
}
|
||||
|
||||
const VkFramebufferCreateInfo framebufferInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
|
||||
.renderPass = mRenderPass,
|
||||
.attachmentCount = (uint32_t) views.size(),
|
||||
.pAttachments = views.data(),
|
||||
.width = mExtent.width,
|
||||
.height = mExtent.height,
|
||||
.layers = 1,
|
||||
};
|
||||
requireSuccess(vkCreateFramebuffer(mGpu.device(), &framebufferInfo, nullptr, &mFramebuffer), "vkCreateFramebuffer");
|
||||
|
||||
// Note: the resolved image is sampled before the first render, so it starts cleared in the sampling layout.
|
||||
const VkCommandBuffer commandBuffer = mGpu.beginOneShotCommands();
|
||||
imageBarrier(
|
||||
commandBuffer,
|
||||
resolved().image,
|
||||
VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
0,
|
||||
VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT
|
||||
);
|
||||
const VkClearColorValue transparent{};
|
||||
const VkImageSubresourceRange range{ VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
|
||||
vkCmdClearColorImage(
|
||||
commandBuffer, resolved().image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &transparent, 1, &range
|
||||
);
|
||||
imageBarrier(
|
||||
commandBuffer,
|
||||
resolved().image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
||||
VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
VK_ACCESS_SHADER_READ_BIT,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT
|
||||
);
|
||||
mGpu.endOneShotCommands(commandBuffer);
|
||||
}
|
||||
|
||||
void RenderTarget::destroyImages() {
|
||||
vkDestroyFramebuffer(mGpu.device(), mFramebuffer, nullptr);
|
||||
mFramebuffer = VK_NULL_HANDLE;
|
||||
destroyImage(mColor);
|
||||
destroyImage(mDepth);
|
||||
if (mResolve.image) {
|
||||
destroyImage(mResolve);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderTarget::publish() {
|
||||
const Image& image = resolved();
|
||||
mGpu.renderTargets().updateTarget(
|
||||
mRegistryId,
|
||||
{
|
||||
.view = image.view,
|
||||
.sampler = mSampler,
|
||||
.width = (int) mExtent.width,
|
||||
.height = (int) mExtent.height,
|
||||
.image = image.image,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void RenderTarget::resize(Vec2<uint4> size) {
|
||||
const VkExtent2D next = toExtent(size);
|
||||
if (next.width == mExtent.width && next.height == mExtent.height) {
|
||||
return;
|
||||
}
|
||||
mGpu.waitIdle();
|
||||
destroyImages();
|
||||
mExtent = next;
|
||||
createImages();
|
||||
publish();
|
||||
}
|
||||
|
||||
void RenderTarget::render(const RGBA& clearColor, const std::function<void(VkCommandBuffer)>& draw) {
|
||||
const VkCommandBuffer commandBuffer = mGpu.beginOneShotCommands();
|
||||
|
||||
std::array<VkClearValue, 3> clears{};
|
||||
clears[0].color = { { clearColor.r, clearColor.g, clearColor.b, clearColor.a } };
|
||||
clears[1].depthStencil = { 1.0f, 0 };
|
||||
const VkRenderPassBeginInfo beginInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
|
||||
.renderPass = mRenderPass,
|
||||
.framebuffer = mFramebuffer,
|
||||
.renderArea = { { 0, 0 }, mExtent },
|
||||
.clearValueCount = (uint32_t) clears.size(),
|
||||
.pClearValues = clears.data(),
|
||||
};
|
||||
vkCmdBeginRenderPass(commandBuffer, &beginInfo, VK_SUBPASS_CONTENTS_INLINE);
|
||||
|
||||
const VkViewport viewport{
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.width = (float) mExtent.width,
|
||||
.height = (float) mExtent.height,
|
||||
.minDepth = 0,
|
||||
.maxDepth = 1,
|
||||
};
|
||||
vkCmdSetViewport(commandBuffer, 0, 1, &viewport);
|
||||
const VkRect2D scissor{ { 0, 0 }, mExtent };
|
||||
vkCmdSetScissor(commandBuffer, 0, 1, &scissor);
|
||||
|
||||
draw(commandBuffer);
|
||||
|
||||
vkCmdEndRenderPass(commandBuffer);
|
||||
mGpu.endOneShotCommands(commandBuffer);
|
||||
}
|
||||
|
||||
std::vector<uint32_t> RenderTarget::readPixels() {
|
||||
const size_t count = (size_t) mExtent.width * mExtent.height;
|
||||
GpuBuffer staging(mGpu, VK_BUFFER_USAGE_TRANSFER_DST_BIT, count * sizeof(uint32_t));
|
||||
|
||||
const VkCommandBuffer commandBuffer = mGpu.beginOneShotCommands();
|
||||
imageBarrier(
|
||||
commandBuffer,
|
||||
resolved().image,
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
|
||||
VK_ACCESS_TRANSFER_READ_BIT,
|
||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT
|
||||
);
|
||||
const VkBufferImageCopy region{
|
||||
.imageSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 },
|
||||
.imageExtent = { mExtent.width, mExtent.height, 1 },
|
||||
};
|
||||
vkCmdCopyImageToBuffer(
|
||||
commandBuffer, resolved().image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, staging.handle(), 1, ®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<uint32_t> pixels(count);
|
||||
staging.read(pixels.data(), pixels.size() * sizeof(uint32_t));
|
||||
return pixels;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,162 +0,0 @@
|
|||
|
||||
|
||||
#include "Buffer.hpp"
|
||||
#include "LocalConnection.hpp"
|
||||
#include "Shader.hpp"
|
||||
#include "GraphicsApi.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
using namespace tp;
|
||||
|
||||
RenderShader::RenderShader() {
|
||||
programm = 0;
|
||||
VertexShaderID = 0;
|
||||
FragmentShaderID = 0;
|
||||
GeometryShaderID = 0;
|
||||
}
|
||||
|
||||
void RenderShader::vert_bind_source(const char* vert_src) {
|
||||
VertexShaderID = glCreateShader(GL_VERTEX_SHADER);
|
||||
compile_shader(vert_src, VertexShaderID);
|
||||
}
|
||||
|
||||
void RenderShader::frag_bind_source(const char* frag_src) {
|
||||
FragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);
|
||||
compile_shader(frag_src, FragmentShaderID);
|
||||
}
|
||||
|
||||
void RenderShader::geom_bind_source(const char* geom_src) {
|
||||
GeometryShaderID = glCreateShader(GL_GEOMETRY_SHADER);
|
||||
compile_shader(geom_src, GeometryShaderID);
|
||||
}
|
||||
|
||||
void RenderShader::compile() {
|
||||
GLint Result = GL_FALSE;
|
||||
int InfoLogLength;
|
||||
|
||||
programm = glCreateProgram();
|
||||
glAttachShader(programm, VertexShaderID);
|
||||
if (GeometryShaderID) glAttachShader(programm, GeometryShaderID);
|
||||
glAttachShader(programm, FragmentShaderID);
|
||||
glLinkProgram(programm);
|
||||
|
||||
// Check the program
|
||||
glGetProgramiv(programm, GL_LINK_STATUS, &Result);
|
||||
glGetProgramiv(programm, GL_INFO_LOG_LENGTH, &InfoLogLength);
|
||||
|
||||
if (InfoLogLength > 0) {
|
||||
Buffer<char> ProgramErrorMessage(InfoLogLength + 1);
|
||||
glGetProgramInfoLog(programm, InfoLogLength, NULL, &ProgramErrorMessage[0]);
|
||||
printf("%s\n", &ProgramErrorMessage[0]);
|
||||
}
|
||||
|
||||
glDetachShader(programm, VertexShaderID);
|
||||
glDetachShader(programm, FragmentShaderID);
|
||||
if (GeometryShaderID) glDetachShader(programm, GeometryShaderID);
|
||||
|
||||
glDeleteShader(VertexShaderID);
|
||||
glDeleteShader(FragmentShaderID);
|
||||
if (GeometryShaderID) glDeleteShader(GeometryShaderID);
|
||||
}
|
||||
|
||||
bool RenderShader::compile_shader(const char* ShaderCode, uint4 ShaderID) {
|
||||
GLint Result = GL_FALSE;
|
||||
int InfoLogLength;
|
||||
|
||||
char const* SourcePointer = ShaderCode;
|
||||
glShaderSource(ShaderID, 1, &SourcePointer, NULL);
|
||||
glCompileShader(ShaderID);
|
||||
|
||||
// Check Shader
|
||||
glGetShaderiv(ShaderID, GL_COMPILE_STATUS, &Result);
|
||||
glGetShaderiv(ShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);
|
||||
|
||||
if (InfoLogLength > 0) {
|
||||
Buffer<char> VertexShaderErrorMessage(InfoLogLength + 1);
|
||||
glGetShaderInfoLog(ShaderID, InfoLogLength, NULL, &VertexShaderErrorMessage[0]);
|
||||
printf("%s\n", &VertexShaderErrorMessage[0]);
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
|
||||
void RenderShader::load(const char* pvert, const char* pgeom, const char* pfrag, bool paths) {
|
||||
|
||||
// Create the shaders
|
||||
VertexShaderID = glCreateShader(GL_VERTEX_SHADER);
|
||||
FragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);
|
||||
if (pgeom) GeometryShaderID = glCreateShader(GL_GEOMETRY_SHADER);
|
||||
else GeometryShaderID = 0;
|
||||
|
||||
GLint Result = GL_FALSE;
|
||||
int InfoLogLength = 0;
|
||||
|
||||
if (paths) {
|
||||
|
||||
std::string content;
|
||||
|
||||
auto loadFile = [&](const char* path) {
|
||||
LocalConnection file;
|
||||
if (!file.connect(LocalConnection::Location(path), LocalConnection::Type(true))) {
|
||||
content = "";
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto size = file.size();
|
||||
auto tmp = new char[size + 1];
|
||||
tmp[size] = 0;
|
||||
file.readBytes(tmp, size);
|
||||
content = tmp;
|
||||
delete[] tmp;
|
||||
return true;
|
||||
};
|
||||
|
||||
printf("Compiling shader : %s\n", pvert);
|
||||
if (loadFile(pvert)) {
|
||||
compile_shader(content.c_str(), VertexShaderID);
|
||||
}
|
||||
|
||||
if (GeometryShaderID) {
|
||||
printf("Compiling shader : %s\n", pgeom);
|
||||
if (loadFile(pgeom)) {
|
||||
compile_shader(content.c_str(), GeometryShaderID);
|
||||
}
|
||||
}
|
||||
|
||||
printf("Compiling shader : %s\n", pfrag);
|
||||
if (loadFile(pfrag)) {
|
||||
compile_shader(content.c_str(), FragmentShaderID);
|
||||
}
|
||||
}
|
||||
else {
|
||||
compile_shader(pvert, VertexShaderID);
|
||||
if (GeometryShaderID) {
|
||||
compile_shader(pgeom, GeometryShaderID);
|
||||
}
|
||||
compile_shader(pfrag, FragmentShaderID);
|
||||
}
|
||||
|
||||
compile();
|
||||
}
|
||||
|
||||
RenderShader::RenderShader(const char* vert, const char* geom, const char* frag, bool paths) {
|
||||
load(vert, geom, frag, paths);
|
||||
}
|
||||
|
||||
void RenderShader::bind() {
|
||||
glUseProgram(programm);
|
||||
}
|
||||
|
||||
GLuint RenderShader::getu(const char* uid) {
|
||||
return glGetUniformLocation(programm, uid);
|
||||
}
|
||||
|
||||
void RenderShader::unbind() {
|
||||
glUseProgram(0);
|
||||
}
|
||||
|
||||
RenderShader::~RenderShader() {
|
||||
glDeleteProgram(programm);
|
||||
}
|
||||
|
|
@ -1,205 +0,0 @@
|
|||
|
||||
#include "Texture.hpp"
|
||||
|
||||
#include "Map.hpp"
|
||||
|
||||
#include "Shader.hpp"
|
||||
|
||||
#include "GraphicsApi.hpp"
|
||||
|
||||
const char* texture_vertex =
|
||||
#ifdef ENV_OS_ANDROID
|
||||
"#version 300 es\n"
|
||||
"precision mediump float;\n"
|
||||
#else
|
||||
"#version 330 core\n"
|
||||
#endif
|
||||
"layout(location = 0) in vec3 vPos;\n"
|
||||
"out vec2 UV;\n"
|
||||
"void main() {\n"
|
||||
" gl_Position = vec4(vPos, 1);\n"
|
||||
" UV = (vPos.xy + vec2(1, 1)) / 2.0;\n"
|
||||
"}\n";
|
||||
|
||||
const char* texture_fragment =
|
||||
#ifdef ENV_OS_ANDROID
|
||||
"#version 300 es\n"
|
||||
"precision mediump float;\n"
|
||||
#else
|
||||
"#version 330 core\n"
|
||||
#endif
|
||||
"in vec2 UV;\n"
|
||||
"out vec4 color;\n"
|
||||
"uniform sampler2D renderedTexture;\n"
|
||||
"uniform float time;\n"
|
||||
"void main() {\n"
|
||||
" vec4 texColor = texture(renderedTexture, UV);\n"
|
||||
" if (texColor.a < 0.2)\n"
|
||||
" discard;\n"
|
||||
" color = texColor;\n"
|
||||
"}\n";
|
||||
|
||||
|
||||
using namespace tp;
|
||||
|
||||
GLuint RenderTexture::getid() { return id; }
|
||||
|
||||
RenderTexture::RenderTexture() {
|
||||
glGenTextures(1, &id);
|
||||
glBindTexture(GL_TEXTURE_2D, id);
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
RenderTexture::~RenderTexture() { glDeleteTextures(1, &id); }
|
||||
|
||||
|
||||
void RenderTexture::update(const Buffer2D<RGBA>& buff) {
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)buff.size().x, (GLsizei)buff.size().y, 0, GL_RGBA, GL_FLOAT, buff.getBuff());
|
||||
}
|
||||
|
||||
void RenderTexture::draw(const GLuint& out) {
|
||||
draw_texture(out, id);
|
||||
}
|
||||
|
||||
|
||||
struct texture_drawer_data {
|
||||
|
||||
Map<std::string, GLuint> textures;
|
||||
|
||||
GLuint quad_VertexArrayID;
|
||||
GLuint quad_vertexbuffer;
|
||||
GLuint texID;
|
||||
RenderShader shader;
|
||||
|
||||
const GLfloat g_quad_vertex_buffer_data[18] = {
|
||||
-1.0f, -1.0f, 0.0f, 1.0f, -1.0f, 0.0f, -1.0f, 1.0f, 0.0f,
|
||||
-1.0f, 1.0f, 0.0f, 1.0f, -1.0f, 0.0f, 1.0f, 1.0f, 0.0f,
|
||||
};
|
||||
|
||||
texture_drawer_data() : shader(texture_vertex, nullptr, texture_fragment, false) {
|
||||
// The fullscreen quad's FBO
|
||||
glGenVertexArrays(1, &quad_VertexArrayID);
|
||||
glBindVertexArray(quad_VertexArrayID);
|
||||
|
||||
glGenBuffers(1, &quad_vertexbuffer);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, quad_vertexbuffer);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(g_quad_vertex_buffer_data),
|
||||
g_quad_vertex_buffer_data, GL_STATIC_DRAW);
|
||||
|
||||
texID = shader.getu("renderedTexture");
|
||||
}
|
||||
|
||||
~texture_drawer_data() {
|
||||
glDeleteBuffers(1, &quad_vertexbuffer);
|
||||
glDeleteVertexArrays(1, &quad_VertexArrayID);
|
||||
|
||||
for (auto tex : textures) {
|
||||
glDeleteTextures(1, &tex->val);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
texture_drawer_data* texdd = NULL;
|
||||
|
||||
void RenderTexture::init() {
|
||||
if (!texdd) texdd = new texture_drawer_data();
|
||||
}
|
||||
|
||||
void RenderTexture::deinit() {
|
||||
if (texdd) delete texdd;
|
||||
texdd = NULL;
|
||||
}
|
||||
|
||||
void RenderTexture::draw_texture(uint4 out, uint4 in) {
|
||||
ASSERT(in);
|
||||
|
||||
// Render to the screen
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, out);
|
||||
|
||||
// Use our shader
|
||||
texdd->shader.bind();
|
||||
|
||||
// Bind our texture in Texture Unit 0
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, in);
|
||||
// Set our "renderedTexture" sampler to use Texture Unit 0
|
||||
glUniform1i(texdd->texID, 0);
|
||||
|
||||
// glUniformMatrix4fv(texdd->rect_mat, 1, GL_FALSE, &tmat[0][0]);
|
||||
|
||||
// 1rst attribute buffer : vertices
|
||||
glEnableVertexAttribArray(0);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, texdd->quad_vertexbuffer);
|
||||
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
|
||||
|
||||
// Draw the triangles. 2*3 indices starting at 0 -> 2 triangles
|
||||
glDrawArrays(GL_TRIANGLES, 0, 6);
|
||||
|
||||
glDisableVertexAttribArray(0);
|
||||
|
||||
texdd->shader.unbind();
|
||||
}
|
||||
|
||||
GLuint load_texture(const std::string& name) {
|
||||
GLuint tex_2d = 0;
|
||||
|
||||
ASSERT(0 && "incomplete compilation - no SOIL support added");
|
||||
|
||||
if (0) {
|
||||
//auto document = lunasvg::Document::loadFromFile("tiger.svg");
|
||||
//auto bitmap = document->renderToBitmap();
|
||||
}
|
||||
else {
|
||||
/*
|
||||
tex_2d = SOIL_load_OGL_texture(
|
||||
name.cstr(), SOIL_LOAD_RGBA, SOIL_CREATE_NEW_ID,
|
||||
SOIL_FLAG_MIPMAPS | SOIL_FLAG_INVERT_Y | SOIL_FLAG_NTSC_SAFE_RGB |
|
||||
SOIL_FLAG_COMPRESS_TO_DXT);
|
||||
|
||||
if (0 == tex_2d) {
|
||||
printf("SOIL loading error: '%s'\n", SOIL_last_result());
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
return tex_2d;
|
||||
}
|
||||
|
||||
GLuint RenderTexture::get_tex(const char* TexId) {
|
||||
GLuint out = 0;
|
||||
auto idx = texdd->textures.presents(TexId);
|
||||
if (idx) {
|
||||
out = texdd->textures.get(TexId);
|
||||
}
|
||||
else {
|
||||
out = load_texture(TexId);
|
||||
texdd->textures.put(TexId, out);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
void RenderTexture::drawCurcle(Vec2F pos, double radius, RGBA col) {
|
||||
#ifndef ENV_OS_ANDROID
|
||||
static alni precision = 40;
|
||||
|
||||
glColor4f(col.r, col.g, col.b, col.a);
|
||||
|
||||
double twicePi = 2.0 * 3.142;
|
||||
|
||||
glBegin(GL_TRIANGLE_FAN); // BEGIN CIRCLE
|
||||
glVertex2f(pos.x, pos.y); // center of circle
|
||||
|
||||
for (alni i = 0; i <= precision; i++) {
|
||||
glVertex2f((GLfloat)(pos.x + (radius * cos(i * twicePi / precision))),
|
||||
(GLfloat)(pos.y + (radius * sin(i * twicePi / precision))));
|
||||
}
|
||||
glEnd(); // END
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
22
RasterRender/public/ClipSpace.hpp
Normal file
22
RasterRender/public/ClipSpace.hpp
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#pragma once
|
||||
|
||||
#include "Camera.hpp"
|
||||
|
||||
namespace tp {
|
||||
|
||||
// Camera matrices produce OpenGL clip depth in [-w, w]; Vulkan clips depth to [0, w].
|
||||
// Note: Y is kept, so image row 0 is clip y = -1, the same memory layout the GL framebuffers had.
|
||||
inline Mat4F vulkanDepthCorrection() {
|
||||
Mat4F correction;
|
||||
correction[0] = Vec4F(1, 0, 0, 0);
|
||||
correction[1] = Vec4F(0, 1, 0, 0);
|
||||
correction[2] = Vec4F(0, 0, 0.5f, 0.5f);
|
||||
correction[3] = Vec4F(0, 0, 0, 1);
|
||||
return correction;
|
||||
}
|
||||
|
||||
// World to Vulkan clip space, transposed into the column-major layout GLSL reads.
|
||||
inline Mat4F vulkanWorldToClip(const Camera& camera) {
|
||||
return (vulkanDepthCorrection() * camera.calculateTransformationMatrix()).transpose();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
};
|
||||
};
|
||||
33
RasterRender/public/GpuBuffer.hpp
Normal file
33
RasterRender/public/GpuBuffer.hpp
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
#pragma once
|
||||
|
||||
#include "GpuDevice.hpp"
|
||||
|
||||
#include <vulkan/vulkan.h>
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace tp {
|
||||
|
||||
// A host-visible Vulkan buffer the CPU writes or reads directly.
|
||||
class GpuBuffer {
|
||||
public:
|
||||
GpuBuffer(GpuDevice& gpu, VkBufferUsageFlags usage, size_t capacity);
|
||||
~GpuBuffer();
|
||||
|
||||
GpuBuffer(const GpuBuffer&) = delete;
|
||||
GpuBuffer& operator=(const GpuBuffer&) = delete;
|
||||
|
||||
[[nodiscard]] VkBuffer handle() const { return mBuffer; }
|
||||
[[nodiscard]] size_t capacity() const { return mCapacity; }
|
||||
|
||||
// Note: the caller keeps the GPU off this buffer while writing (one-shot submits wait for completion).
|
||||
void write(const void* data, size_t size);
|
||||
void read(void* data, size_t size) const;
|
||||
|
||||
private:
|
||||
GpuDevice& mGpu;
|
||||
size_t mCapacity;
|
||||
VkBuffer mBuffer = VK_NULL_HANDLE;
|
||||
VkDeviceMemory mMemory = VK_NULL_HANDLE;
|
||||
};
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "Environment.hpp"
|
||||
|
||||
#ifdef ENV_OS_ANDROID
|
||||
#include <EGL/egl.h>
|
||||
#include <GLES3/gl32.h>
|
||||
#else
|
||||
#include "GL/glew.h"
|
||||
#endif
|
||||
|
||||
#ifdef ENV_OS_ANDROID
|
||||
#define GLW_CONTEXT_DEPTH_BITS 16
|
||||
#define GLW_CONTEXT_DEPTH_COMPONENT GL_DEPTH_COMPONENT16
|
||||
#else
|
||||
#define GLW_CONTEXT_DEPTH_BITS 32
|
||||
#define GLW_CONTEXT_DEPTH_COMPONENT GL_DEPTH_COMPONENT32
|
||||
#endif
|
||||
56
RasterRender/public/GraphicsPipeline.hpp
Normal file
56
RasterRender/public/GraphicsPipeline.hpp
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
#pragma once
|
||||
|
||||
#include "GpuDevice.hpp"
|
||||
|
||||
#include <vulkan/vulkan.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
#include <type_traits>
|
||||
|
||||
namespace tp {
|
||||
|
||||
struct ShaderStage {
|
||||
VkShaderStageFlagBits stage;
|
||||
std::span<const uint32_t> spirv;
|
||||
};
|
||||
|
||||
// Fixed-function state of a pipeline that draws one interleaved vertex buffer with depth testing.
|
||||
struct PipelineDescription {
|
||||
std::span<const ShaderStage> stages;
|
||||
uint32_t vertexStride = 0;
|
||||
VkFormat vertexFormat = VK_FORMAT_R32G32B32_SFLOAT;
|
||||
VkPrimitiveTopology topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||
VkShaderStageFlags pushConstantStages = VK_SHADER_STAGE_VERTEX_BIT;
|
||||
uint32_t pushConstantSize = 0;
|
||||
VkRenderPass renderPass = VK_NULL_HANDLE;
|
||||
VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
};
|
||||
|
||||
class GraphicsPipeline {
|
||||
public:
|
||||
GraphicsPipeline(GpuDevice& gpu, const PipelineDescription& description);
|
||||
~GraphicsPipeline();
|
||||
|
||||
GraphicsPipeline(const GraphicsPipeline&) = delete;
|
||||
GraphicsPipeline& operator=(const GraphicsPipeline&) = delete;
|
||||
|
||||
void bind(VkCommandBuffer commandBuffer) const;
|
||||
|
||||
// Note: tConstants must match the shader's push-constant block byte for byte and have standard layout.
|
||||
template <typename tConstants>
|
||||
void push(VkCommandBuffer commandBuffer, const tConstants& constants) const {
|
||||
static_assert(std::is_standard_layout_v<tConstants>);
|
||||
pushBytes(commandBuffer, &constants, sizeof(tConstants));
|
||||
}
|
||||
|
||||
private:
|
||||
void pushBytes(VkCommandBuffer commandBuffer, const void* data, uint32_t size) const;
|
||||
|
||||
GpuDevice& mGpu;
|
||||
VkShaderStageFlags mPushConstantStages;
|
||||
uint32_t mPushConstantSize;
|
||||
VkPipelineLayout mLayout = VK_NULL_HANDLE;
|
||||
VkPipeline mPipeline = VK_NULL_HANDLE;
|
||||
};
|
||||
}
|
||||
|
|
@ -1,38 +1,46 @@
|
|||
#pragma once
|
||||
|
||||
#include "Scene.hpp"
|
||||
#include "Rect.hpp"
|
||||
#include "RenderTarget.hpp"
|
||||
#include "GraphicsPipeline.hpp"
|
||||
#include "GpuBuffer.hpp"
|
||||
|
||||
#include "FrameBuffer.hpp"
|
||||
#include "Shader.hpp"
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace tp {
|
||||
|
||||
// Draws a Scene into an offscreen RenderTarget: depth-shaded objects plus an optional solid highlight.
|
||||
class RasterRender {
|
||||
public:
|
||||
RasterRender();
|
||||
explicit RasterRender(GpuDevice& gpu, Vec2<uint4> size = { 1, 1 });
|
||||
~RasterRender();
|
||||
|
||||
void beginRender(const Scene& geometry, const Vec2<ualni>& size);
|
||||
void endRender();
|
||||
RasterRender(const RasterRender&) = delete;
|
||||
RasterRender& operator=(const RasterRender&) = delete;
|
||||
|
||||
void renderDefault(const Scene& geometry);
|
||||
void renderOutline(const Camera& camera, const Object& object);
|
||||
[[nodiscard]] RenderTarget& target() { return mTarget; }
|
||||
[[nodiscard]] size_t uploadedMeshes() const { return mMeshes.size(); }
|
||||
|
||||
void resize(const Vec2<ualni>& size);
|
||||
|
||||
uint4 getRenderBufferID();
|
||||
RenderBuffer* getRenderBuffer();
|
||||
Vec2F getBufferSize();
|
||||
// Uploads meshes it has not seen, drops meshes of removed objects, then renders.
|
||||
// Note: a changed mTopology of an existing object needs forgetObject() to upload again.
|
||||
void render(const Scene& scene, const Object* highlighted = nullptr);
|
||||
void forgetObject(uint8 objectId);
|
||||
|
||||
private:
|
||||
void bindObjectShaderAttributes(const Object& object);
|
||||
void bindCameraShaderAttributes(const Mat4F& cameraMat);
|
||||
struct ObjectMesh {
|
||||
std::unique_ptr<GpuBuffer> vertices;
|
||||
std::unique_ptr<GpuBuffer> indices;
|
||||
uint32_t indexCount = 0;
|
||||
};
|
||||
|
||||
private:
|
||||
RenderBuffer mRenderBuffer;
|
||||
RenderBuffer mTempBuffer;
|
||||
void syncMeshes(const Scene& scene);
|
||||
void drawObject(VkCommandBuffer commandBuffer, const GraphicsPipeline& pipeline, const Mat4F& worldToClip, const Object& object);
|
||||
|
||||
RenderShader mDefaultShader;
|
||||
RenderShader mSolidShader;
|
||||
GpuDevice& mGpu;
|
||||
RenderTarget mTarget;
|
||||
GraphicsPipeline mDepthPipeline;
|
||||
GraphicsPipeline mSolidPipeline;
|
||||
std::unordered_map<uint8, ObjectMesh> mMeshes;
|
||||
};
|
||||
}
|
||||
71
RasterRender/public/RenderTarget.hpp
Normal file
71
RasterRender/public/RenderTarget.hpp
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
#pragma once
|
||||
|
||||
#include "GpuDevice.hpp"
|
||||
#include "Color.hpp"
|
||||
#include "Vec.hpp"
|
||||
|
||||
#include <vulkan/vulkan.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
namespace tp {
|
||||
|
||||
// Offscreen color and depth images that one render pass draws into; the resolved color image is
|
||||
// registered in the device's RenderTargetRegistry so a Canvas can draw it.
|
||||
class RenderTarget {
|
||||
public:
|
||||
RenderTarget(GpuDevice& gpu, Vec2<uint4> size, uint4 samples);
|
||||
~RenderTarget();
|
||||
|
||||
RenderTarget(const RenderTarget&) = delete;
|
||||
RenderTarget& operator=(const RenderTarget&) = delete;
|
||||
|
||||
[[nodiscard]] uint32_t registryId() const { return mRegistryId; }
|
||||
[[nodiscard]] Vec2<uint4> size() const { return { mExtent.width, mExtent.height }; }
|
||||
[[nodiscard]] VkRenderPass renderPass() const { return mRenderPass; }
|
||||
[[nodiscard]] VkSampleCountFlagBits samples() const { return mSamples; }
|
||||
|
||||
// Waits for the GPU and recreates the images; Canvas images made from this target must be updated.
|
||||
void resize(Vec2<uint4> size);
|
||||
|
||||
// Records one cleared render pass, submits it and waits for completion.
|
||||
void render(const RGBA& clearColor, const std::function<void(VkCommandBuffer)>& draw);
|
||||
|
||||
// The resolved color image as RGBA8 rows, top row first.
|
||||
[[nodiscard]] std::vector<uint32_t> readPixels();
|
||||
|
||||
private:
|
||||
struct Image {
|
||||
VkImage image = VK_NULL_HANDLE;
|
||||
VkDeviceMemory memory = VK_NULL_HANDLE;
|
||||
VkImageView view = VK_NULL_HANDLE;
|
||||
};
|
||||
|
||||
[[nodiscard]] Image createImage(
|
||||
VkFormat format, VkImageUsageFlags usage, VkSampleCountFlagBits samples, VkImageAspectFlags aspect
|
||||
) const;
|
||||
void destroyImage(Image& image) const;
|
||||
|
||||
void createRenderPass();
|
||||
void createImages();
|
||||
void destroyImages();
|
||||
void publish();
|
||||
|
||||
[[nodiscard]] const Image& resolved() const { return mResolve.image ? mResolve : mColor; }
|
||||
|
||||
GpuDevice& mGpu;
|
||||
VkExtent2D mExtent{};
|
||||
VkSampleCountFlagBits mSamples = VK_SAMPLE_COUNT_1_BIT;
|
||||
VkFormat mDepthFormat = VK_FORMAT_UNDEFINED;
|
||||
|
||||
VkRenderPass mRenderPass = VK_NULL_HANDLE;
|
||||
VkSampler mSampler = VK_NULL_HANDLE;
|
||||
Image mColor;
|
||||
Image mDepth;
|
||||
Image mResolve;
|
||||
VkFramebuffer mFramebuffer = VK_NULL_HANDLE;
|
||||
uint32_t mRegistryId = 0;
|
||||
};
|
||||
}
|
||||
|
|
@ -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;
|
||||
};
|
||||
};
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "Buffer2D.hpp"
|
||||
#include "Color.hpp"
|
||||
#include "Vec.hpp"
|
||||
|
||||
namespace tp {
|
||||
class RenderTexture {
|
||||
public:
|
||||
RenderTexture();
|
||||
~RenderTexture();
|
||||
|
||||
uint4 getid();
|
||||
|
||||
void update(const Buffer2D<RGBA>& buff);
|
||||
void draw(const uint4& out = 0);
|
||||
|
||||
public:
|
||||
static void init();
|
||||
static void deinit();
|
||||
static void draw_texture(uint4 out, uint4 in);
|
||||
static uint4 get_tex(const char* TexId);
|
||||
static void drawCurcle(Vec2F pos, double radius, RGBA col);
|
||||
|
||||
private:
|
||||
uint4 id;
|
||||
};
|
||||
};
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
#version 330 core
|
||||
|
||||
out vec4 FragColor;
|
||||
|
||||
void main() {
|
||||
FragColor = vec4(1, 0, 0, 1.f);
|
||||
}
|
||||
7
RasterRender/shaders/depth.frag
Normal file
7
RasterRender/shaders/depth.frag
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
#version 450
|
||||
|
||||
layout(location = 0) out vec4 color;
|
||||
|
||||
void main() {
|
||||
color = vec4(vec3(gl_FragCoord.z), 1.0);
|
||||
}
|
||||
12
RasterRender/shaders/object.vert
Normal file
12
RasterRender/shaders/object.vert
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
#version 450
|
||||
|
||||
layout(location = 0) in vec3 point;
|
||||
|
||||
layout(push_constant) uniform Constants {
|
||||
mat4 worldToClip;
|
||||
mat4 objectToWorld;
|
||||
} constants;
|
||||
|
||||
void main() {
|
||||
gl_Position = constants.worldToClip * constants.objectToWorld * vec4(point, 1.0);
|
||||
}
|
||||
7
RasterRender/shaders/solid.frag
Normal file
7
RasterRender/shaders/solid.frag
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
#version 450
|
||||
|
||||
layout(location = 0) out vec4 color;
|
||||
|
||||
void main() {
|
||||
color = vec4(1.0, 0.0, 0.0, 1.0);
|
||||
}
|
||||
100
RasterRender/tests/RasterRenderTests.cpp
Normal file
100
RasterRender/tests/RasterRenderTests.cpp
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
#include "RasterRender.hpp"
|
||||
|
||||
#include "UnitTest++/UnitTest++.h"
|
||||
|
||||
using namespace tp;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uint4 kSize = 64;
|
||||
|
||||
// A triangle around the origin, facing a camera on +X.
|
||||
Object triangle(Vec3F origin) {
|
||||
Object object;
|
||||
object.mTopology.Points = { { 0, -1, -1 }, { 0, 1, -1 }, { 0, 0, 1 } };
|
||||
object.mTopology.Normals = { { 1, 0, 0 }, { 1, 0, 0 }, { 1, 0, 0 } };
|
||||
object.mTopology.Indexes = { { 0, 1, 2 } };
|
||||
object.mTopology.Origin = origin;
|
||||
object.updateCache();
|
||||
return object;
|
||||
}
|
||||
|
||||
Scene sceneWith(Vec3F origin) {
|
||||
Scene scene;
|
||||
scene.mCamera.lookAtPoint({ 0, 0, 0 }, { 4, 0, 0 }, { 0, 0, 1 });
|
||||
scene.addObject(triangle(origin));
|
||||
return scene;
|
||||
}
|
||||
|
||||
struct Fixture {
|
||||
// Note: GpuDevice always enables the swapchain extension, which requires the surface instance extension.
|
||||
GpuDevice gpu{ { VK_KHR_SURFACE_EXTENSION_NAME } };
|
||||
RasterRender render{ gpu, { kSize, kSize } };
|
||||
};
|
||||
|
||||
uint32_t pixel(const std::vector<uint32_t>& pixels, uint4 x, uint4 y) { return pixels[y * kSize + x]; }
|
||||
|
||||
uint8_t channel(uint32_t pixel, int index) { return (uint8_t) (pixel >> (index * 8)); }
|
||||
}
|
||||
|
||||
SUITE(RasterRender) {
|
||||
TEST_FIXTURE(Fixture, DrawsDepthShadedObjectsAndClearsTheRest) {
|
||||
const Scene scene = sceneWith({ 0, 0, 0 });
|
||||
|
||||
render.render(scene);
|
||||
const auto pixels = render.target().readPixels();
|
||||
|
||||
const uint32_t center = pixel(pixels, kSize / 2, kSize / 2);
|
||||
CHECK(channel(center, 0) > 0);
|
||||
CHECK_EQUAL(channel(center, 0), channel(center, 1));
|
||||
CHECK_EQUAL(255, channel(center, 3));
|
||||
CHECK_EQUAL(0u, pixel(pixels, 1, 1));
|
||||
}
|
||||
|
||||
TEST_FIXTURE(Fixture, RowsFollowCameraProjectionAndObjectOrigin) {
|
||||
const Scene scene = sceneWith({ 0, 0, 1.4f });
|
||||
render.render(scene);
|
||||
const auto pixels = render.target().readPixels();
|
||||
|
||||
// Row 0 is projected y = -1, the layout Camera::project and the old GL framebuffers use.
|
||||
const Vec2F projected = scene.mCamera.project(Vec3F(0, 0, 1.4f));
|
||||
const auto row = (uint4) ((projected.y + 1) / 2 * kSize);
|
||||
const auto mirroredRow = kSize - 1 - row;
|
||||
CHECK_EQUAL(255, channel(pixel(pixels, kSize / 2, row), 3));
|
||||
CHECK_EQUAL(0u, pixel(pixels, kSize / 2, mirroredRow));
|
||||
}
|
||||
|
||||
TEST_FIXTURE(Fixture, HighlightDrawsSolidRed) {
|
||||
const Scene scene = sceneWith({ 0, 0, 0 });
|
||||
|
||||
render.render(scene, &scene.mObjects[0]);
|
||||
const uint32_t center = pixel(render.target().readPixels(), kSize / 2, kSize / 2);
|
||||
|
||||
CHECK_EQUAL(255, channel(center, 0));
|
||||
CHECK_EQUAL(0, channel(center, 1));
|
||||
CHECK_EQUAL(0, channel(center, 2));
|
||||
}
|
||||
|
||||
TEST_FIXTURE(Fixture, ResizeKeepsTheRegistryEntryCurrent) {
|
||||
render.target().resize({ 32, 16 });
|
||||
|
||||
RenderTargetEntry entry;
|
||||
CHECK(gpu.renderTargets().resolve(render.target().registryId(), entry));
|
||||
CHECK_EQUAL(32, entry.width);
|
||||
CHECK_EQUAL(16, entry.height);
|
||||
CHECK(entry.view != VK_NULL_HANDLE);
|
||||
}
|
||||
|
||||
TEST_FIXTURE(Fixture, UploadsEachObjectOnceAndReleasesRemovedOnes) {
|
||||
Scene scene = sceneWith({ 0, 0, 0 });
|
||||
render.render(scene);
|
||||
render.render(scene);
|
||||
CHECK_EQUAL(1u, render.uploadedMeshes());
|
||||
|
||||
scene.mObjects.clear();
|
||||
render.render(scene);
|
||||
CHECK_EQUAL(0u, render.uploadedMeshes());
|
||||
}
|
||||
}
|
||||
|
||||
int main() { return UnitTest::RunAllTests(); }
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -9,69 +9,9 @@
|
|||
|
||||
#include <cstdio>
|
||||
|
||||
/*
|
||||
if (1) {
|
||||
const auto& points = castData.obj->mCache.TransformedPoints;
|
||||
const auto& normals = castData.obj->mCache.TransformedNormals;
|
||||
const auto trig = castData.trig;
|
||||
|
||||
const auto& n1 = normals[trig->mP1];
|
||||
const auto& n2 = normals[trig->mP2];
|
||||
const auto& n3 = normals[trig->mP3];
|
||||
|
||||
auto v0 = points[trig->mP1];
|
||||
auto v1 = points[trig->mP2];
|
||||
auto v2 = points[trig->mP3];
|
||||
|
||||
// Calculate barycentric coordinates
|
||||
Vec3F barycentric;
|
||||
|
||||
// Calculate the area of the triangle
|
||||
auto areaABC = (halnf) (v1 - v0).cross(v2 - v0).length();
|
||||
auto areaPBC = (halnf) (v1 - castData.hitPos).cross(v2 - castData.hitPos).length();
|
||||
auto areaPCA = (halnf) (v2 - castData.hitPos).cross(v0 - castData.hitPos).length();
|
||||
|
||||
// Calculate the barycentric coordinates
|
||||
barycentric.x = areaPBC / areaABC;
|
||||
barycentric.y = areaPCA / areaABC;
|
||||
barycentric.z = 1.0f - barycentric.x - barycentric.y;
|
||||
|
||||
// Interpolate the normal using barycentric coordinates
|
||||
normal = n1 * barycentric.x + n2 * barycentric.y + n3 * barycentric.z;
|
||||
}
|
||||
*/
|
||||
|
||||
using namespace tp;
|
||||
|
||||
|
||||
// TODO : de-duplicate in Scene?
|
||||
void RayTracer::castRay(const Ray& ray, RayCastData& out, alnf farVal) {
|
||||
out.hit = false;
|
||||
out.obj = nullptr;
|
||||
|
||||
farVal *= farVal;
|
||||
|
||||
for (auto obj : mScene->mObjects) {
|
||||
for (auto trig : obj->mCache.TrigCaches) {
|
||||
if (trig->castRay(ray)) {
|
||||
// printf("Hit\n");
|
||||
|
||||
auto dist = (trig->getHitPos() - ray.pos).length2();
|
||||
|
||||
if (farVal > dist && dist > EPSILON) {
|
||||
out.trig = &trig.data();
|
||||
out.hitPos = trig->getHitPos();
|
||||
out.obj = &obj.data();
|
||||
out.hit = true;
|
||||
|
||||
farVal = dist;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RayTracer::cycle(const RayCastData& castData, LightData& out, uhalni depth) {
|
||||
void RayTracer::cycle(const RayHit& castData, LightData& out, uhalni depth) {
|
||||
if (depth) {
|
||||
depth--;
|
||||
|
||||
|
|
@ -82,7 +22,6 @@ void RayTracer::cycle(const RayCastData& castData, LightData& out, uhalni depth)
|
|||
const auto delta2 = normal.cross(delta1);
|
||||
|
||||
for (auto idx : IterRange(mSettings.spray)) {
|
||||
RayCastData materialCastData;
|
||||
LightData lightData;
|
||||
|
||||
auto d1 = ((halnf) randomFloat() - 0.5f) * 2;
|
||||
|
|
@ -90,9 +29,9 @@ void RayTracer::cycle(const RayCastData& castData, LightData& out, uhalni depth)
|
|||
|
||||
auto sprayNormal = (normal + delta1 * d1 + delta2 * d2).normalize();
|
||||
|
||||
castRay({ sprayNormal, castData.hitPos }, materialCastData, mScene->mCamera.getFar());
|
||||
if (materialCastData.hit) {
|
||||
cycle(materialCastData, lightData, depth);
|
||||
const auto bounce = mScene->castRay({ sprayNormal, castData.position }, mScene->mCamera.getFar());
|
||||
if (bounce) {
|
||||
cycle(*bounce, lightData, depth);
|
||||
out.intensity += lightData.intensity * 0.2;
|
||||
}
|
||||
}
|
||||
|
|
@ -100,18 +39,15 @@ void RayTracer::cycle(const RayCastData& castData, LightData& out, uhalni depth)
|
|||
|
||||
// cast for light
|
||||
for (auto light : mScene->mLights) {
|
||||
RayCastData lightCastData;
|
||||
auto dir = light->pos - castData.hitPos;
|
||||
auto dir = light->pos - castData.position;
|
||||
auto length = (halnf) dir.length();
|
||||
|
||||
Ray lightRay = { dir.unitV(), castData.hitPos };
|
||||
Ray lightRay = { dir.unitV(), castData.position };
|
||||
|
||||
if (lightRay.dir.dot(castData.trig->mNormal) < 0) {
|
||||
continue;
|
||||
}
|
||||
castRay(lightRay, lightCastData, length);
|
||||
|
||||
if (lightCastData.hit) {
|
||||
if (mScene->castRay(lightRay, length)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -138,8 +74,6 @@ void RayTracer::render(const Scene& scene, OutputBuffers& out, const RenderSetti
|
|||
const auto up = (planeRightBottom - planeRightTop);
|
||||
const auto right = planeRightTop - planeLeftTop;
|
||||
|
||||
RayCastData castData;
|
||||
|
||||
Ray ray = { { 0, 0, 0 }, pos };
|
||||
|
||||
Vec3F iterPoint = { 0, 0, 0 };
|
||||
|
|
@ -184,21 +118,21 @@ void RayTracer::render(const Scene& scene, OutputBuffers& out, const RenderSetti
|
|||
iterPoint = planeLeftTop + ((deltaX * (halnf) (i + randX)) + (deltaY * (halnf) (j + randY)));
|
||||
ray.dir = (iterPoint - pos).unitV();
|
||||
|
||||
castRay(ray, castData, mScene->mCamera.getFar());
|
||||
const auto castData = mScene->castRay(ray, mScene->mCamera.getFar());
|
||||
|
||||
halni albedoColor = abs(hash((ualni) castData.obj));
|
||||
halni albedoColor = abs(hash((alni) (castData ? castData->object->id() : 0)));
|
||||
halnf albedoColorR = float((albedoColor & 0x00000011) % 155) + 100;
|
||||
halnf albedoColorG = float((albedoColor & 0x00001100) % 155) + 100;
|
||||
halnf albedoColorB = float((albedoColor & 0x00110000) % 155) + 100;
|
||||
|
||||
out.albedo.set({ i, j }, { albedoColorR, albedoColorG, albedoColorB, 1.f });
|
||||
|
||||
if (castData.hit) {
|
||||
if (castData) {
|
||||
LightData lightData;
|
||||
cycle(castData, lightData, mSettings.depth);
|
||||
cycle(*castData, lightData, mSettings.depth);
|
||||
|
||||
const auto normal = castData.trig->getNormal();
|
||||
const auto depth = (halnf) (castData.hitPos - ray.pos).length();
|
||||
const auto normal = castData->trig->getNormal();
|
||||
const auto depth = castData->distance;
|
||||
|
||||
lightData.intensity = clamp(lightData.intensity, 0.f, 1.f);
|
||||
RGBA col = { lightData.intensity, lightData.intensity, lightData.intensity, 1.f };
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ public:
|
|||
Sketch3DApplication() {
|
||||
setRoot(&mGui);
|
||||
|
||||
mGui.createRenderWidget(&canvas(), { 2560, 1440 });
|
||||
mGui.createRenderWidget(application().gpu(), &canvas(), { 2560, 1440 });
|
||||
mGui.setProject(&mSketch);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,26 +1,63 @@
|
|||
|
||||
#include "Sketch3D.hpp"
|
||||
|
||||
#include <array>
|
||||
|
||||
using namespace tp;
|
||||
|
||||
StrokeGPUHandles::StrokeGPUHandles() {
|
||||
glGenVertexArrays(1, &VertexArrayID);
|
||||
glBindVertexArray(VertexArrayID);
|
||||
namespace {
|
||||
|
||||
glGenBuffers(1, &vertexbuffer);
|
||||
const uint32_t kStrokeVert[] =
|
||||
#include "stroke.vert.inc"
|
||||
;
|
||||
|
||||
const uint32_t kStrokeGeom[] =
|
||||
#include "stroke.geom.inc"
|
||||
;
|
||||
|
||||
const uint32_t kStrokeFrag[] =
|
||||
#include "stroke.frag.inc"
|
||||
;
|
||||
|
||||
struct StrokeConstants {
|
||||
Mat4F mvp;
|
||||
Vec4F color;
|
||||
Vec4F background;
|
||||
float ratio = 0;
|
||||
float target = 0;
|
||||
float padding[2] = {};
|
||||
};
|
||||
static_assert(sizeof(StrokeConstants) == (16 + 4 + 4 + 4) * sizeof(float));
|
||||
static_assert(sizeof(StrokePoint) == 4 * sizeof(float));
|
||||
|
||||
// Note: matches the old GL layout (row 0 at clip y = -1); only depth is remapped to Vulkan's [0, w] in the shader.
|
||||
constexpr uint4 kSamples = 4;
|
||||
|
||||
GraphicsPipeline createStrokePipeline(GpuDevice& gpu, const RenderTarget& target) {
|
||||
const std::array<ShaderStage, 3> stages = {
|
||||
ShaderStage{ VK_SHADER_STAGE_VERTEX_BIT, kStrokeVert },
|
||||
ShaderStage{ VK_SHADER_STAGE_GEOMETRY_BIT, kStrokeGeom },
|
||||
ShaderStage{ VK_SHADER_STAGE_FRAGMENT_BIT, kStrokeFrag },
|
||||
};
|
||||
return GraphicsPipeline(
|
||||
gpu,
|
||||
{
|
||||
.stages = stages,
|
||||
.vertexStride = sizeof(StrokePoint),
|
||||
.vertexFormat = VK_FORMAT_R32G32B32A32_SFLOAT,
|
||||
.topology = VK_PRIMITIVE_TOPOLOGY_LINE_STRIP,
|
||||
.pushConstantStages = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_GEOMETRY_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||
.pushConstantSize = sizeof(StrokeConstants),
|
||||
.renderPass = target.renderPass(),
|
||||
.samples = target.samples(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void StrokeGPUHandles::sendDataToGPU(Buffer<StrokePoint>* mPoints) {
|
||||
glBindVertexArray(VertexArrayID);
|
||||
vbo_len = mPoints->size();
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(StrokePoint) * vbo_len, mPoints->getBuff(), GL_STATIC_COPY);
|
||||
uint64_t gNextStrokeVersion = 1;
|
||||
}
|
||||
|
||||
StrokeGPUHandles::~StrokeGPUHandles() {
|
||||
glDeleteBuffers(1, &vertexbuffer);
|
||||
glDeleteVertexArrays(1, &VertexArrayID);
|
||||
}
|
||||
void Stroke::updateGpuBuffers() { mVersion = gNextStrokeVersion++; }
|
||||
|
||||
Stroke::Stroke() = default;
|
||||
|
||||
|
|
@ -32,8 +69,6 @@ void Stroke::setColor(const RGBA& col) { mColor = col; }
|
|||
|
||||
const RGBA& Stroke::getColor() const { return mColor; }
|
||||
|
||||
void Stroke::updateGpuBuffers() { mGPUHandles.sendDataToGPU(&mPoints); }
|
||||
|
||||
void Stroke::denoisePos(halni passes) {
|
||||
for (auto pass : IterRange(passes)) {
|
||||
for (auto pi : IterRange(mPoints.size() - 2)) {
|
||||
|
|
@ -347,115 +382,84 @@ Project::~Project() {
|
|||
}
|
||||
}
|
||||
|
||||
Renderer::Renderer(Vec2F size) :
|
||||
mBuffer(size, 4),
|
||||
mBufferDowncast(size),
|
||||
mShader("rsc/shaders/stroke.vert", "rsc/shaders/stroke.geom", "rsc/shaders/stroke.frag") {
|
||||
mMaxSize = size;
|
||||
Renderer::Renderer(GpuDevice& gpu, Vec2<uint4> maxSize) :
|
||||
mGpu(gpu),
|
||||
mMaxSize(maxSize),
|
||||
mTarget(gpu, maxSize, kSamples),
|
||||
mPipeline(createStrokePipeline(gpu, mTarget)) {}
|
||||
|
||||
mMatrixUniform = mShader.getu("MVP");
|
||||
mColorUniform = mShader.getu("Color");
|
||||
mRatioUniform = mShader.getu("Ratio");
|
||||
mTargetUniform = mShader.getu("Target");
|
||||
mBGColUniform = mShader.getu("BGCol");
|
||||
|
||||
mBuffer.mClearCol = 0.f;
|
||||
mBufferDowncast.mClearCol = 0.f;
|
||||
}
|
||||
Renderer::~Renderer() = default;
|
||||
|
||||
void Renderer::renderToTexture(const Project* project, Vec2F size) {
|
||||
size.clamp({ 1, 1 }, mMaxSize);
|
||||
size.clamp({ 1, 1 }, { (halnf) mMaxSize.x, (halnf) mMaxSize.y });
|
||||
mTarget.resize({ (uint4) size.x, (uint4) size.y });
|
||||
mClearColor = project->mBackgroundColor;
|
||||
mUsedThisFrame.clear();
|
||||
|
||||
setViewport({ 0, 0, size.x, size.y });
|
||||
setClearCol(project->mBackgroundColor);
|
||||
renderBegin();
|
||||
mTarget.render(mClearColor, [&](VkCommandBuffer commandBuffer) {
|
||||
mCommands = commandBuffer;
|
||||
mPipeline.bind(commandBuffer);
|
||||
|
||||
for (auto lay : project->mLayers) {
|
||||
if (lay.data()->enabled) {
|
||||
for (auto str : lay.data()->strokes) {
|
||||
drawStroke(str.data(), &project->mCamera);
|
||||
for (auto layer : project->mLayers) {
|
||||
if (!layer.data()->enabled) {
|
||||
continue;
|
||||
}
|
||||
for (auto stroke : layer.data()->strokes) {
|
||||
drawStroke(stroke.data(), &project->mCamera);
|
||||
}
|
||||
}
|
||||
|
||||
if (auto index = project->mBrushes.presents(project->mActiveBrush)) {
|
||||
project->mBrushes.getSlotVal(index)->draw(this, &project->mCamera);
|
||||
}
|
||||
mCommands = VK_NULL_HANDLE;
|
||||
});
|
||||
|
||||
releaseUnusedMeshes();
|
||||
}
|
||||
|
||||
auto idx = project->mBrushes.presents(project->mActiveBrush);
|
||||
if (idx) {
|
||||
auto brush = project->mBrushes.getSlotVal(idx);
|
||||
brush->draw(this, &project->mCamera);
|
||||
const Renderer::StrokeMesh* Renderer::meshOf(const Stroke* stroke) {
|
||||
mUsedThisFrame.insert(stroke);
|
||||
StrokeMesh& mesh = mMeshes[stroke];
|
||||
if (mesh.version == stroke->version() && mesh.vertices) {
|
||||
return &mesh;
|
||||
}
|
||||
|
||||
renderEnd();
|
||||
const auto& points = stroke->getPoints();
|
||||
const size_t bytes = sizeof(StrokePoint) * points.size();
|
||||
if (!mesh.vertices || mesh.vertices->capacity() < bytes) {
|
||||
mesh.vertices = std::make_unique<GpuBuffer>(mGpu, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, bytes);
|
||||
}
|
||||
mesh.vertices->write(points.getBuff(), bytes);
|
||||
mesh.pointCount = (uint32_t) points.size();
|
||||
mesh.version = stroke->version();
|
||||
return &mesh;
|
||||
}
|
||||
|
||||
void Renderer::renderBegin() {
|
||||
mBuffer.beginDraw();
|
||||
mBuffer.clear();
|
||||
void Renderer::releaseUnusedMeshes() {
|
||||
std::erase_if(mMeshes, [this](const auto& entry) { return !mUsedThisFrame.contains(entry.first); });
|
||||
}
|
||||
|
||||
void Renderer::setViewport(RectF viewport) { mBuffer.setViewport(viewport); }
|
||||
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);
|
||||
|
||||
void Renderer::drawStroke(const Stroke* str, const Camera* camera) {
|
||||
// return;
|
||||
|
||||
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() {}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -6,9 +6,13 @@
|
|||
#include "Topology.hpp"
|
||||
#include "Color.hpp"
|
||||
|
||||
#include "GraphicsApi.hpp"
|
||||
#include "FrameBuffer.hpp"
|
||||
#include "Shader.hpp"
|
||||
#include "RenderTarget.hpp"
|
||||
#include "GraphicsPipeline.hpp"
|
||||
#include "GpuBuffer.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace tp {
|
||||
|
||||
|
|
@ -22,44 +26,37 @@ namespace tp {
|
|||
halnf thickness = 0;
|
||||
};
|
||||
|
||||
struct StrokeGPUHandles {
|
||||
GLuint VertexArrayID = 0;
|
||||
GLuint vertexbuffer = 0;
|
||||
GLuint vbo_len = 0;
|
||||
StrokeGPUHandles();
|
||||
void sendDataToGPU(Buffer<StrokePoint>* mPoints);
|
||||
~StrokeGPUHandles();
|
||||
};
|
||||
|
||||
// Draws a Project's strokes into an offscreen RenderTarget.
|
||||
class Renderer {
|
||||
public:
|
||||
Renderer(Vec2F size); // max render size
|
||||
Renderer(GpuDevice& gpu, Vec2<uint4> maxSize);
|
||||
~Renderer();
|
||||
|
||||
void renderToTexture(const Project* project, Vec2F size); // needed size
|
||||
[[nodiscard]] RenderTarget& target() { return mTarget; }
|
||||
|
||||
void renderBegin();
|
||||
void setViewport(RectF viewport);
|
||||
void drawStroke(const Stroke* str, const Camera* camera);
|
||||
void renderEnd();
|
||||
|
||||
void setClearCol(RGBA col);
|
||||
uhalni getTextudeId();
|
||||
RenderBuffer* getBuff();
|
||||
// Renders at `size`, clamped to the maximum size given at construction.
|
||||
void renderToTexture(const Project* project, Vec2F size);
|
||||
void drawStroke(const Stroke* stroke, const Camera* camera);
|
||||
|
||||
private:
|
||||
RenderBuffer mBufferDowncast;
|
||||
RenderBuffer mBuffer;
|
||||
RenderShader mShader;
|
||||
struct StrokeMesh {
|
||||
std::unique_ptr<GpuBuffer> vertices;
|
||||
uint32_t pointCount = 0;
|
||||
uint64_t version = 0;
|
||||
};
|
||||
|
||||
// shader uniforms
|
||||
GLuint mMatrixUniform = 0;
|
||||
GLuint mColorUniform = 0;
|
||||
GLuint mRatioUniform = 0;
|
||||
GLuint mTargetUniform = 0;
|
||||
GLuint mBGColUniform = 0;
|
||||
[[nodiscard]] const StrokeMesh* meshOf(const Stroke* stroke);
|
||||
void releaseUnusedMeshes();
|
||||
|
||||
Vec2F mMaxSize;
|
||||
GpuDevice& mGpu;
|
||||
Vec2<uint4> mMaxSize;
|
||||
RenderTarget mTarget;
|
||||
GraphicsPipeline mPipeline;
|
||||
std::unordered_map<const Stroke*, StrokeMesh> mMeshes;
|
||||
std::unordered_set<const Stroke*> mUsedThisFrame;
|
||||
|
||||
VkCommandBuffer mCommands = VK_NULL_HANDLE;
|
||||
RGBA mClearColor;
|
||||
};
|
||||
|
||||
class Stroke {
|
||||
|
|
@ -69,7 +66,10 @@ namespace tp {
|
|||
void denoiseThickness(halni passes);
|
||||
void compress(halnf factor);
|
||||
void subdiv(halnf precition, const Camera* cam, halni passes = 1);
|
||||
// Marks the points as changed so renderers upload them again.
|
||||
void updateGpuBuffers();
|
||||
// Unique across all strokes, so a renderer never mistakes a new stroke at a reused address for an old one.
|
||||
[[nodiscard]] uint64_t version() const { return mVersion; }
|
||||
Buffer<StrokePoint>& getPoints();
|
||||
const Buffer<StrokePoint>& getPoints() const;
|
||||
void setColor(const RGBA& col);
|
||||
|
|
@ -79,8 +79,7 @@ namespace tp {
|
|||
Buffer<StrokePoint> mPoints;
|
||||
RGBA mColor;
|
||||
|
||||
friend Renderer;
|
||||
StrokeGPUHandles mGPUHandles;
|
||||
uint64_t mVersion = 0;
|
||||
};
|
||||
|
||||
struct Layer {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
28
Sketch3D/shaders/stroke.frag
Normal file
28
Sketch3D/shaders/stroke.frag
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#version 450
|
||||
|
||||
layout(location = 0) out vec4 color;
|
||||
|
||||
layout(push_constant) uniform PushConstants {
|
||||
mat4 MVP;
|
||||
vec4 Color;
|
||||
vec4 BGCol;
|
||||
float Ratio;
|
||||
float Target;
|
||||
} pc;
|
||||
|
||||
#define A 0.1
|
||||
|
||||
void main() {
|
||||
float x = gl_FragCoord.z;
|
||||
|
||||
float val = A;
|
||||
float f = pc.Target * 2.0;
|
||||
|
||||
if (x < f) {
|
||||
val = ((A - 1.0) / f) * x + 1.0;
|
||||
}
|
||||
|
||||
val = clamp(val, A, 1.0);
|
||||
|
||||
color = vec4((pc.Color.xyz - pc.BGCol.xyz) * val + pc.BGCol.xyz, 1.0);
|
||||
}
|
||||
89
Sketch3D/shaders/stroke.geom
Normal file
89
Sketch3D/shaders/stroke.geom
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
#version 450
|
||||
|
||||
layout(lines) in;
|
||||
layout(triangle_strip, max_vertices = 16) out;
|
||||
|
||||
layout(location = 0) in float thickness[];
|
||||
|
||||
layout(push_constant) uniform PushConstants {
|
||||
mat4 MVP;
|
||||
vec4 Color;
|
||||
vec4 BGCol;
|
||||
float Ratio;
|
||||
float Target;
|
||||
} pc;
|
||||
|
||||
void main() {
|
||||
float r = sqrt(pc.Ratio);
|
||||
vec4 l1 = gl_in[0].gl_Position;
|
||||
vec4 l2 = gl_in[1].gl_Position;
|
||||
|
||||
vec2 l1p = l1.xy / l1.w;
|
||||
vec2 l2p = l2.xy / l2.w;
|
||||
|
||||
float t1 = thickness[0] / l1.w;
|
||||
float t2 = thickness[1] / l2.w;
|
||||
|
||||
vec2 l = normalize(vec2(l2p.x / r, l2p.y * r) - vec2(l1p.x / r, l1p.y * r));
|
||||
vec2 tmp = vec2(-l.y, l.x);
|
||||
|
||||
vec2 tmp_poj = vec2(tmp.x * r, tmp.y / r);
|
||||
vec2 l_poj = vec2(l.x * r, l.y / r);
|
||||
|
||||
vec2 p1 = l1p.xy + (tmp_poj * t1);
|
||||
vec2 p2 = l1p.xy - (tmp_poj * t1);
|
||||
vec2 p3 = l2p.xy + (tmp_poj * t2);
|
||||
vec2 p4 = l2p.xy - (tmp_poj * t2);
|
||||
|
||||
gl_Position = vec4(p1.x * l1.w, p1.y * l1.w, l1.z, l1.w);
|
||||
EmitVertex();
|
||||
gl_Position = vec4(p2.x * l1.w, p2.y * l1.w, l1.z, l1.w);
|
||||
EmitVertex();
|
||||
gl_Position = vec4(p3.x * l2.w, p3.y * l2.w, l2.z, l2.w);
|
||||
EmitVertex();
|
||||
gl_Position = vec4(p4.x * l2.w, p4.y * l2.w, l2.z, l2.w);
|
||||
EmitVertex();
|
||||
EndPrimitive();
|
||||
|
||||
p1 = l1p.xy + (tmp_poj * t1);
|
||||
p2 = l1p.xy - (tmp_poj * t1);
|
||||
p3 = l1p.xy + (tmp_poj * t1) - (l_poj * t1) / 2.0;
|
||||
p4 = l1p.xy - (tmp_poj * t1) - (l_poj * t1) / 2.0;
|
||||
vec2 p5 = l1p.xy + (tmp_poj * t1) / 2.0 - (l_poj * t1);
|
||||
vec2 p6 = l1p.xy - (tmp_poj * t1) / 2.0 - (l_poj * t1);
|
||||
|
||||
gl_Position = vec4(p1.x * l1.w, p1.y * l1.w, l1.z, l1.w);
|
||||
EmitVertex();
|
||||
gl_Position = vec4(p2.x * l1.w, p2.y * l1.w, l1.z, l1.w);
|
||||
EmitVertex();
|
||||
gl_Position = vec4(p3.x * l1.w, p3.y * l1.w, l1.z, l1.w);
|
||||
EmitVertex();
|
||||
gl_Position = vec4(p4.x * l1.w, p4.y * l1.w, l1.z, l1.w);
|
||||
EmitVertex();
|
||||
gl_Position = vec4(p5.x * l1.w, p5.y * l1.w, l1.z, l1.w);
|
||||
EmitVertex();
|
||||
gl_Position = vec4(p6.x * l1.w, p6.y * l1.w, l1.z, l1.w);
|
||||
EmitVertex();
|
||||
EndPrimitive();
|
||||
|
||||
p1 = l2p.xy + (tmp_poj * t2);
|
||||
p2 = l2p.xy - (tmp_poj * t2);
|
||||
p3 = l2p.xy + (tmp_poj * t2) + (l_poj * t2) / 2.0;
|
||||
p4 = l2p.xy - (tmp_poj * t2) + (l_poj * t2) / 2.0;
|
||||
p5 = l2p.xy + (tmp_poj * t2) / 2.0 + (l_poj * t2);
|
||||
p6 = l2p.xy - (tmp_poj * t2) / 2.0 + (l_poj * t2);
|
||||
|
||||
gl_Position = vec4(p1.x * l2.w, p1.y * l2.w, l2.z, l2.w);
|
||||
EmitVertex();
|
||||
gl_Position = vec4(p2.x * l2.w, p2.y * l2.w, l2.z, l2.w);
|
||||
EmitVertex();
|
||||
gl_Position = vec4(p3.x * l2.w, p3.y * l2.w, l2.z, l2.w);
|
||||
EmitVertex();
|
||||
gl_Position = vec4(p4.x * l2.w, p4.y * l2.w, l2.z, l2.w);
|
||||
EmitVertex();
|
||||
gl_Position = vec4(p5.x * l2.w, p5.y * l2.w, l2.z, l2.w);
|
||||
EmitVertex();
|
||||
gl_Position = vec4(p6.x * l2.w, p6.y * l2.w, l2.z, l2.w);
|
||||
EmitVertex();
|
||||
EndPrimitive();
|
||||
}
|
||||
23
Sketch3D/shaders/stroke.vert
Normal file
23
Sketch3D/shaders/stroke.vert
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
#version 450
|
||||
|
||||
layout(location = 0) in vec4 point;
|
||||
|
||||
layout(location = 0) out float thickness;
|
||||
|
||||
layout(push_constant) uniform PushConstants {
|
||||
mat4 MVP;
|
||||
vec4 Color;
|
||||
vec4 BGCol;
|
||||
float Ratio;
|
||||
float Target;
|
||||
} pc;
|
||||
|
||||
void main() {
|
||||
thickness = point.w;
|
||||
|
||||
vec4 clip = pc.MVP * vec4(point.xyz, 1.0);
|
||||
// Note: depth is kept linear (z * w, divided back by w) and remapped from OpenGL [-w, w] to Vulkan [0, w].
|
||||
clip.z *= clip.w;
|
||||
clip.z = (clip.z + clip.w) * 0.5;
|
||||
gl_Position = clip;
|
||||
}
|
||||
58
Sketch3D/tests/RendererTests.cpp
Normal file
58
Sketch3D/tests/RendererTests.cpp
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
#include "Sketch3D.hpp"
|
||||
|
||||
#include "UnitTest++/UnitTest++.h"
|
||||
|
||||
using namespace tp;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uint4 kSize = 64;
|
||||
|
||||
struct Fixture {
|
||||
// Note: GpuDevice always enables the swapchain extension, which requires the surface instance extension.
|
||||
GpuDevice gpu{ { VK_KHR_SURFACE_EXTENSION_NAME } };
|
||||
Renderer renderer{ gpu, { kSize, kSize } };
|
||||
Project project;
|
||||
|
||||
Fixture() { project.mBackgroundColor = RGBA(0.f, 0.f, 0.f, 1.f); }
|
||||
|
||||
Stroke* addStroke(halnf thickness) {
|
||||
auto stroke = new Stroke();
|
||||
stroke->setColor(RGBA(1.f, 1.f, 1.f, 1.f));
|
||||
stroke->getPoints().append({ project.mCamera.project(Vec2F(-0.5f, 0.f)), thickness });
|
||||
stroke->getPoints().append({ project.mCamera.project(Vec2F(0.5f, 0.f)), thickness });
|
||||
stroke->updateGpuBuffers();
|
||||
project.mLayers[0]->strokes.pushBack(stroke);
|
||||
return stroke;
|
||||
}
|
||||
|
||||
uint32_t centerPixel() {
|
||||
renderer.renderToTexture(&project, { kSize, kSize });
|
||||
return renderer.target().readPixels()[kSize / 2 * kSize + kSize / 2];
|
||||
}
|
||||
};
|
||||
|
||||
uint8_t red(uint32_t pixel) { return (uint8_t) pixel; }
|
||||
}
|
||||
|
||||
SUITE(Sketch3D) {
|
||||
TEST_FIXTURE(Fixture, DrawsAStrokeInItsColorOverTheBackground) {
|
||||
CHECK_EQUAL(0, red(centerPixel()));
|
||||
|
||||
addStroke(0.05f);
|
||||
CHECK(red(centerPixel()) > 100);
|
||||
}
|
||||
|
||||
TEST_FIXTURE(Fixture, RedrawsAfterPointsChange) {
|
||||
Stroke* stroke = addStroke(0.05f);
|
||||
CHECK(red(centerPixel()) > 100);
|
||||
|
||||
for (ualni index = 0; index < stroke->getPoints().size(); index++) {
|
||||
stroke->getPoints()[index].pos.z += 10;
|
||||
}
|
||||
stroke->updateGpuBuffers();
|
||||
CHECK_EQUAL(0, red(centerPixel()));
|
||||
}
|
||||
}
|
||||
|
||||
int main() { return UnitTest::RunAllTests(); }
|
||||
Loading…
Add table
Add a link
Reference in a new issue