Adding 3D support #77

Merged
burkkyy merged 3 commits from dev/adding-3d-support into main 2026-10-02 20:12:46 -07:00
75 changed files with 2708 additions and 737 deletions
+3
View File
@@ -11,3 +11,6 @@
[submodule "third_party/tracy"] [submodule "third_party/tracy"]
path = third_party/tracy path = third_party/tracy
url = https://github.com/wolfpld/tracy.git url = https://github.com/wolfpld/tracy.git
[submodule "third_party/ImGuizmo"]
path = third_party/ImGuizmo
url = https://github.com/CedricGuillemet/ImGuizmo.git
+1
View File
@@ -7,6 +7,7 @@ add_library(${NAME}_deps INTERFACE)
target_link_libraries(${NAME}_deps INTERFACE target_link_libraries(${NAME}_deps INTERFACE
${ENGINE_NAME} ${ENGINE_NAME}
imguizmo
) )
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17) target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17)
+99
View File
@@ -0,0 +1,99 @@
#include "editor_camera.hpp"
#include <algorithm>
#include <cmath>
namespace {
constexpr float ORBIT_RADIANS_PER_PIXEL = 0.005f;
const float MAX_PITCH = glm::radians(89.f);
// Each wheel step moves this fraction of the way towards the pivot.
constexpr float ZOOM_STEP = 0.1f;
constexpr float MIN_DISTANCE = 0.05f;
constexpr float MIN_ORTHO_HEIGHT = 0.01f;
// Fly speed per unit of distance from the pivot, so it suits any scale.
constexpr float FLY_SPEED = 1.5f;
} // namespace
void EditorCamera::orbit(glm::vec2 pixels) {
if (mode != Mode::Scene3D) {
return;
}
yaw -= pixels.x * ORBIT_RADIANS_PER_PIXEL;
pitch = std::clamp(pitch - pixels.y * ORBIT_RADIANS_PER_PIXEL, -MAX_PITCH,
MAX_PITCH);
}
void EditorCamera::pan(glm::vec2 pixels, float viewportHeight) {
if (viewportHeight <= 0.f) {
return;
}
// World units one pixel covers at the pivot's depth.
float unitsPerPixel =
mode == Mode::Scene3D
? 2.f * distance * std::tan(fov / 2.f) / viewportHeight
: orthoHeight / viewportHeight;
// Screen y runs down, world y up. Dragging moves the scene with the mouse,
// so the camera moves the other way.
glm::vec3 offset{-pixels.x * unitsPerPixel, pixels.y * unitsPerPixel, 0.f};
pivot += mode == Mode::Scene3D ? getRotation() * offset : offset;
}
void EditorCamera::zoom(float steps) {
float scale = std::pow(1.f - ZOOM_STEP, steps);
if (mode == Mode::Scene3D) {
distance = std::max(distance * scale, MIN_DISTANCE);
} else {
orthoHeight = std::max(orthoHeight * scale, MIN_ORTHO_HEIGHT);
}
}
void EditorCamera::fly(glm::vec3 direction, float deltaTime) {
if (mode != Mode::Scene3D || direction == glm::vec3{0.f}) {
return;
}
float speed = FLY_SPEED * std::max(distance, 1.f);
pivot += getRotation() * glm::normalize(direction) * speed * deltaTime;
}
void EditorCamera::frame(glm::vec3 target, float radius) {
pivot = target;
radius = std::max(radius, 0.1f);
// Far enough that the sphere fits in the vertical field of view.
distance = radius / std::sin(fov / 2.f);
orthoHeight = radius * 2.5f;
}
sophia::TransformComponent EditorCamera::getTransform() const {
sophia::TransformComponent transform;
if (mode == Mode::Scene3D) {
transform.rotation = getRotation();
transform.position =
pivot + transform.rotation * glm::vec3{0.f, 0.f, distance};
} else {
transform.position = pivot;
}
return transform;
}
sophia::CameraComponent EditorCamera::getCamera() const {
sophia::CameraComponent camera;
if (mode == Mode::Scene3D) {
camera.projection = sophia::CameraComponent::Projection::Perspective;
camera.perspective.fov = fov;
// Room to zoom far out without the scene clipping.
camera.perspective.farPlane = 1000.f;
} else {
camera.projection = sophia::CameraComponent::Projection::Orthographic;
camera.ortho.height = orthoHeight;
}
return camera;
}
glm::quat EditorCamera::getRotation() const {
return glm::angleAxis(yaw, glm::vec3{0.f, 1.f, 0.f}) *
glm::angleAxis(pitch, glm::vec3{1.f, 0.f, 0.f});
}
+49
View File
@@ -0,0 +1,49 @@
#pragma once
#include <sophia/components.hpp>
#include <sophia/math.hpp>
/**
* The Scene panel's camera. Not part of any scene the editor edits, and never
* saved with one.
*
* In 3D it orbits a pivot point at some distance; in 2D it looks straight
* down -Z at the pivot through an orthographic projection, like a 2D game
* camera.
*/
class EditorCamera {
public:
enum class Mode { Scene3D, Scene2D };
Mode mode = Mode::Scene3D;
// Turns around the pivot by a mouse movement in pixels. 3D only.
void orbit(glm::vec2 pixels);
// Moves the pivot so the scene follows a mouse movement in pixels across a
// viewport `viewportHeight` pixels tall.
void pan(glm::vec2 pixels, float viewportHeight);
// Moves in (positive) or out by mouse wheel steps.
void zoom(float steps);
// Moves the pivot along the camera's axes: x right, y up, z backwards.
// `direction` needn't be normalized. 3D only.
void fly(glm::vec3 direction, float deltaTime);
// Centers on `target`, backing off enough to see a sphere of `radius`.
void frame(glm::vec3 target, float radius);
sophia::TransformComponent getTransform() const;
sophia::CameraComponent getCamera() const;
private:
glm::quat getRotation() const;
glm::vec3 pivot{0.f, 0.f, 0.f};
// 3D: looking at the pivot from in front, a little to the right and above.
float yaw = glm::radians(30.f);
float pitch = glm::radians(-25.f);
float distance = 8.f;
float fov = glm::radians(60.f);
// 2D: world units visible from the bottom of the view to the top.
float orthoHeight = 10.f;
};
+89
View File
@@ -0,0 +1,89 @@
#pragma once
#include <sophia/entity.hpp>
#include <sophia/handles.hpp>
#include <sophia/scene.hpp>
#include <sophia/scene_registry.hpp>
#include <sophia/scene_view_registry.hpp>
#include <string>
#include <vector>
/**
* What the editor is working on, shared by its panels. Panels read it every
* frame instead of holding on to a scene, so switching scenes is one call.
*/
class EditorContext {
public:
// A scene the editor has open, and can switch to.
struct OpenScene {
sophia::SceneHandle handle;
std::string name;
};
explicit EditorContext(sophia::SceneRegistry& scenes) : scenes{scenes} {}
// Creates an open scene with a "Main Camera", without switching to it.
sophia::SceneHandle createScene(std::string name) {
sophia::SceneHandle handle = scenes.create();
scenes.get(handle).createCamera("Main Camera");
openScenes.push_back({handle, std::move(name)});
return handle;
}
const std::vector<OpenScene>& getOpenScenes() const { return openScenes; }
// Null if `handle` isn't an open scene.
const std::string* getSceneName(sophia::SceneHandle handle) const {
for (const OpenScene& open : openScenes) {
if (open.handle == handle) {
return &open.name;
}
}
return nullptr;
}
void renameScene(sophia::SceneHandle handle, std::string name) {
for (OpenScene& open : openScenes) {
if (open.handle == handle) {
open.name = std::move(name);
}
}
}
bool hasScene() const { return scenes.isAlive(activeScene); }
sophia::SceneHandle getSceneHandle() const { return activeScene; }
sophia::Scene& getScene() { return scenes.get(activeScene); }
// Also clears the selection, which belonged to the previous scene.
void setScene(sophia::SceneHandle handle) {
activeScene = handle;
selected = {};
}
// Null when nothing is selected. Always an entity of the active scene.
sophia::Entity selected;
private:
sophia::SceneRegistry& scenes;
sophia::SceneHandle activeScene = sophia::INVALID_HANDLE;
// Kept open even when not active: with no saving yet, closing one would
// lose it.
std::vector<OpenScene> openScenes;
};
// Points `view` at the context's active scene, and renders it only while
// there is one and its panel is `shown`. Call every frame from the panel that
// owns the view.
inline void syncSceneView(EditorContext& context,
sophia::SceneViewRegistry& sceneViews,
sophia::SceneViewHandle view, bool shown) {
bool enabled = shown && context.hasScene();
const sophia::SceneView& current = sceneViews.peek(view);
// get() re-sorts the render queue, so only use it when something changed.
if (current.enabled != enabled ||
(context.hasScene() && current.scene != context.getSceneHandle())) {
sophia::SceneView& updated = sceneViews.get(view);
updated.enabled = enabled;
updated.scene = context.getSceneHandle();
}
}
+83 -16
View File
@@ -1,49 +1,86 @@
#include <iostream>
#include <sophia/application.hpp> #include <sophia/application.hpp>
#include <sophia/components.hpp> #include <sophia/components.hpp>
#include <sophia/entity.hpp> #include <sophia/entity.hpp>
#include <sophia/primitives.hpp>
#include <vector>
#include "editor_context.hpp"
#include "panels/asset_browser_panel.hpp" #include "panels/asset_browser_panel.hpp"
#include "panels/game_panel.hpp"
#include "panels/inspector_panel.hpp" #include "panels/inspector_panel.hpp"
#include "panels/scene_hierarchy_panel.hpp" #include "panels/scene_hierarchy_panel.hpp"
#include "panels/viewport_panel.hpp" #include "panels/scene_panel.hpp"
#include "utilities/logger.hpp"
class Editor : public sophia::Application { class Editor : public sophia::Application {
public: public:
Editor() : Application{{.width = 750, .height = 1000, .name = "Editor"}} {} Editor() : Application{{.width = 750, .height = 1000, .name = "Editor"}} {}
void onCreate() override { void onCreate() override {
std::cout << "Editor created" << std::endl; log("Editor created");
debugUI().show(); debugUI().show();
textures.emplace_back("Checker", createCheckerTexture()); textures.emplace_back("Checker", createCheckerTexture());
cubeMesh = createShadedCube();
sophia::SceneHandle sceneHandle = scenes().create(); sophia::SceneHandle sceneHandle = context.createScene("Demo");
sophia::Scene& scene = scenes().get(sceneHandle); createDemoScene(scenes().get(sceneHandle));
createDemoScene(scene); context.setScene(sceneHandle);
panels().emplace<SceneHierarchyPanel>(scene, selected); // The editor's own scene, never edited or rendered. It holds the camera
panels().emplace<InspectorPanel>(selected, textures); // the Scene panel looks through, which a SceneView may take from any
panels().emplace<AssetBrowserPanel>(); // scene.
sophia::SceneHandle editorSceneHandle = scenes().create();
sophia::Entity editorCamera =
scenes().get(editorSceneHandle).createCamera("Editor Camera");
// Views start without a scene; their panels point them at the active
// scene each frame.
sophia::RenderTargetHandle sceneTarget = sophia::RenderTargetHandle sceneTarget =
resources().create<sophia::RenderTarget>(); resources().create<sophia::RenderTarget>();
sceneViews().create({.scene = sceneHandle, .target = sceneTarget}); sophia::SceneViewHandle sceneView = sceneViews().create(
panels().emplace<ViewportPanel>(ui(), sceneTarget, scene, selected); {.target = sceneTarget, .camera = editorCamera, .enabled = false});
// No camera: the Game panel shows the scene through its active one.
sophia::RenderTargetHandle gameTarget =
resources().create<sophia::RenderTarget>();
sophia::SceneViewHandle gameView =
sceneViews().create({.target = gameTarget, .enabled = false});
panels().emplace<SceneHierarchyPanel>(context, cubeMesh);
panels().emplace<InspectorPanel>(context, textures);
panels().emplace<AssetBrowserPanel>();
viewPanels.push_back(
{panels().emplace<ScenePanel>(ui(), resources(), context, sceneViews(),
sceneView, sceneTarget, editorCamera),
sceneView});
viewPanels.push_back(
{panels().emplace<GamePanel>(ui(), context, sceneViews(), gameView,
gameTarget),
gameView});
panels().setDefaultLayout( panels().setDefaultLayout(
{.left = {"Scene Hierarchy"}, {.left = {"Scene Hierarchy"},
.right = {"Inspector", "Frame Stats", "PanelRegistry"}, .right = {"Inspector", "Frame Stats", "PanelRegistry"},
.bottom = {"Asset Browser", "Console"}, .bottom = {"Asset Browser", "Console"},
.center = {"Viewport"}}); .center = {"Scene", "Game"}});
} }
void onUpdate(const sophia::FrameInfo& frameInfo) override {} void onUpdate(const sophia::FrameInfo& frameInfo) override {
// A closed panel's onUpdate() doesn't run, so it can't stop its view
// rendering itself.
for (const ViewPanel& viewPanel : viewPanels) {
if (!panels().get(viewPanel.panel).isVisible() &&
sceneViews().peek(viewPanel.view).enabled) {
sceneViews().get(viewPanel.view).enabled = false;
}
}
}
private: private:
// Adds to a new scene, which already has a camera.
void createDemoScene(sophia::Scene& scene) { void createDemoScene(sophia::Scene& scene) {
scene.createCamera("Main Camera");
sophia::Entity checker = scene.createEntity("Checker"); sophia::Entity checker = scene.createEntity("Checker");
checker.getComponent<sophia::TransformComponent>().position = {-1.5f, 0.f, checker.getComponent<sophia::TransformComponent>().position = {-1.5f, 0.f,
0.f}; 0.f};
@@ -63,6 +100,27 @@ class Editor : public sophia::Application {
sophia::SpriteComponent{.color = {0.25f, 0.45f, 0.95f, 0.6f}, sophia::SpriteComponent{.color = {0.25f, 0.45f, 0.95f, 0.6f},
.size = {2.f, 2.f}, .size = {2.f, 2.f},
.sortOrder = 1}); .sortOrder = 1});
sophia::Entity cube = scene.createEntity("Cube");
auto& cubeTransform = cube.getComponent<sophia::TransformComponent>();
cubeTransform.position = {0.f, 2.5f, 0.f};
cubeTransform.rotation =
glm::quat{glm::radians(glm::vec3{30.f, 45.f, 0.f})};
cube.addComponent<sophia::MeshComponent>(sophia::MeshComponent{
.handle = cubeMesh, .color = {0.4f, 0.8f, 0.5f, 1.f}});
}
// Meshes are unlit, so a plain cube is a flat silhouette. Bake a fixed
// light into its vertex colors so its faces read in 3D until there's real
// lighting; MeshComponent::color still tints it.
sophia::MeshHandle createShadedCube() {
const glm::vec3 lightDirection = glm::normalize(glm::vec3{0.4f, 1.f, 0.7f});
sophia::MeshData data = sophia::primitives::cube();
for (sophia::MeshVertex& vertex : data.vertices) {
float light = glm::dot(vertex.normal, lightDirection) * 0.5f + 0.5f;
vertex.color = glm::vec4{glm::vec3{0.35f + 0.65f * light}, 1.f};
}
return resources().create<sophia::Mesh>(data);
} }
sophia::TextureHandle createCheckerTexture() { sophia::TextureHandle createCheckerTexture() {
@@ -79,8 +137,17 @@ class Editor : public sophia::Application {
return resources().create<sophia::Texture>(SIZE, SIZE, std::move(pixels)); return resources().create<sophia::Texture>(SIZE, SIZE, std::move(pixels));
} }
sophia::Entity selected; // A panel showing a scene view, which should only render while the panel
// is on screen.
struct ViewPanel {
sophia::PanelHandle panel;
sophia::SceneViewHandle view;
};
EditorContext context{scenes()};
std::vector<ViewPanel> viewPanels;
InspectorPanel::TextureList textures; InspectorPanel::TextureList textures;
sophia::MeshHandle cubeMesh;
}; };
SOPHIA_MAIN(Editor) SOPHIA_MAIN(Editor)
+20
View File
@@ -0,0 +1,20 @@
#include "game_panel.hpp"
#include <imgui.h>
void GamePanel::onUpdate(const sophia::FrameInfo&) {
syncSceneView(context, sceneViews, view, isShown());
if (!context.hasScene()) {
ImGui::TextDisabled("No scene open");
return;
}
const ImVec2 origin = ImGui::GetCursorScreenPos();
ui.image(target);
if (!context.getScene().getActiveCamera()) {
ImGui::SetCursorScreenPos(ImVec2(origin.x + 8.0f, origin.y + 8.0f));
ImGui::TextDisabled("The scene has no active camera");
}
}
+39
View File
@@ -0,0 +1,39 @@
#pragma once
#include <sophia/frame_info.hpp>
#include <sophia/handles.hpp>
#include <sophia/scene_view_registry.hpp>
#include <sophia/ui/panel.hpp>
#include <sophia/ui/ui.hpp>
#include "../editor_context.hpp"
/**
* Shows the active scene through its own active camera: what the game will
* see.
*/
class GamePanel : public sophia::Panel {
public:
// Renders into `target` through `view`, a SceneView with no camera of its
// own that this panel keeps pointed at the active scene.
GamePanel(sophia::UI& ui, EditorContext& context,
sophia::SceneViewRegistry& sceneViews, sophia::SceneViewHandle view,
sophia::RenderTargetHandle target)
: Panel{{.name = "Game",
.windowFlags = ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_NoScrollWithMouse}},
ui{ui},
context{context},
sceneViews{sceneViews},
view{view},
target{target} {}
void onUpdate(const sophia::FrameInfo& frameInfo) override;
private:
sophia::UI& ui;
EditorContext& context;
sophia::SceneViewRegistry& sceneViews;
sophia::SceneViewHandle view;
sophia::RenderTargetHandle target;
};
+32 -5
View File
@@ -6,6 +6,7 @@
#include <sophia/components.hpp> #include <sophia/components.hpp>
void InspectorPanel::onUpdate(const sophia::FrameInfo&) { void InspectorPanel::onUpdate(const sophia::FrameInfo&) {
selected = context.selected;
if (!selected) { if (!selected) {
ImGui::TextDisabled("No entity selected"); ImGui::TextDisabled("No entity selected");
return; return;
@@ -16,6 +17,7 @@ void InspectorPanel::onUpdate(const sophia::FrameInfo&) {
drawTransform(); drawTransform();
drawCamera(); drawCamera();
drawSprite(); drawSprite();
drawMesh();
ImGui::Separator(); ImGui::Separator();
drawAddComponent(); drawAddComponent();
@@ -40,11 +42,13 @@ void InspectorPanel::drawTransform() {
auto& transform = selected.getComponent<sophia::TransformComponent>(); auto& transform = selected.getComponent<sophia::TransformComponent>();
ImGui::DragFloat3("Position", &transform.position.x, 0.1f); ImGui::DragFloat3("Position", &transform.position.x, 0.1f);
// 2D only: edits rotation around Z and drops any X/Y rotation. // Euler angles in degrees, recomputed from the quaternion every frame.
float degrees = glm::degrees(glm::eulerAngles(transform.rotation).z); // Near 90 degrees of pitch the angles can jump between equivalent values
if (ImGui::DragFloat("Rotation", &degrees, 1.0f)) { // while dragging.
transform.rotation = // TODO: keep each entity's edited angles, or replace with gizmo rotation.
glm::angleAxis(glm::radians(degrees), glm::vec3{0.f, 0.f, 1.f}); glm::vec3 degrees = glm::degrees(glm::eulerAngles(transform.rotation));
if (ImGui::DragFloat3("Rotation", &degrees.x, 1.0f)) {
transform.rotation = glm::quat{glm::radians(degrees)};
} }
ImGui::DragFloat3("Scale", &transform.scale.x, 0.1f); ImGui::DragFloat3("Scale", &transform.scale.x, 0.1f);
@@ -118,6 +122,29 @@ void InspectorPanel::drawSprite() {
} }
} }
void InspectorPanel::drawMesh() {
if (!selected.hasComponent<sophia::MeshComponent>()) {
return;
}
bool open = true;
if (!ImGui::CollapsingHeader("Mesh", &open, ImGuiTreeNodeFlags_DefaultOpen)) {
if (!open) {
selected.removeComponent<sophia::MeshComponent>();
}
return;
}
auto& mesh = selected.getComponent<sophia::MeshComponent>();
// TODO: pick the mesh once assets have names.
ImGui::Text("Mesh: %u", static_cast<sophia::u32>(mesh.handle.index));
ImGui::ColorEdit3("Color", &mesh.color.x);
if (!open) {
selected.removeComponent<sophia::MeshComponent>();
}
}
void InspectorPanel::drawAddComponent() { void InspectorPanel::drawAddComponent() {
if (ImGui::Button("Add Component")) { if (ImGui::Button("Add Component")) {
ImGui::OpenPopup("AddComponent"); ImGui::OpenPopup("AddComponent");
+8 -3
View File
@@ -8,14 +8,16 @@
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "../editor_context.hpp"
class InspectorPanel : public sophia::Panel { class InspectorPanel : public sophia::Panel {
public: public:
// Stand-in for an asset browser: the textures a sprite can pick from. // Stand-in for an asset browser: the textures a sprite can pick from.
using TextureList = using TextureList =
std::vector<std::pair<std::string, sophia::TextureHandle>>; std::vector<std::pair<std::string, sophia::TextureHandle>>;
InspectorPanel(sophia::Entity& selected, const TextureList& textures) InspectorPanel(EditorContext& context, const TextureList& textures)
: Panel{{.name = "Inspector"}}, selected{selected}, textures{textures} {} : Panel{{.name = "Inspector"}}, context{context}, textures{textures} {}
void onUpdate(const sophia::FrameInfo& frameInfo) override; void onUpdate(const sophia::FrameInfo& frameInfo) override;
@@ -24,8 +26,11 @@ class InspectorPanel : public sophia::Panel {
void drawTransform(); void drawTransform();
void drawCamera(); void drawCamera();
void drawSprite(); void drawSprite();
void drawMesh();
void drawAddComponent(); void drawAddComponent();
sophia::Entity& selected; EditorContext& context;
// The selected entity, for the duration of onUpdate().
sophia::Entity selected;
const TextureList& textures; const TextureList& textures;
}; };
+103 -1
View File
@@ -2,18 +2,37 @@
#include <imgui.h> #include <imgui.h>
#include <cstdio>
#include <sophia/components.hpp> #include <sophia/components.hpp>
#include <string>
void SceneHierarchyPanel::onUpdate(const sophia::FrameInfo&) { void SceneHierarchyPanel::onUpdate(const sophia::FrameInfo&) {
drawSceneSelector();
ImGui::Separator();
if (!context.hasScene()) {
ImGui::TextDisabled("No scene open");
return;
}
sophia::Scene& scene = context.getScene();
sophia::Entity& selected = context.selected;
if (ImGui::Button("+ Sprite")) { if (ImGui::Button("+ Sprite")) {
selected = scene.createEntity("Sprite"); selected = scene.createEntity("Sprite");
selected.addComponent<sophia::SpriteComponent>(); selected.addComponent<sophia::SpriteComponent>();
} }
ImGui::SameLine();
if (ImGui::Button("+ Cube")) {
selected = scene.createEntity("Cube");
selected.addComponent<sophia::MeshComponent>(
sophia::MeshComponent{.handle = cubeMesh});
}
ImGui::Separator(); ImGui::Separator();
// Flat for now: there is no parent/child component yet. // Flat for now: there is no parent/child component yet.
scene.forEachEntity([this](sophia::Entity entity) { scene.forEachEntity([&selected](sophia::Entity entity) {
ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_Leaf |
ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_NoTreePushOnOpen |
ImGuiTreeNodeFlags_SpanAvailWidth; ImGuiTreeNodeFlags_SpanAvailWidth;
@@ -30,3 +49,86 @@ void SceneHierarchyPanel::onUpdate(const sophia::FrameInfo&) {
ImGui::PopID(); ImGui::PopID();
}); });
} }
namespace {
// See SceneHierarchyPanel::renameFocusFrames.
constexpr int RENAME_FOCUS_FRAMES = 2;
} // namespace
void SceneHierarchyPanel::drawSceneSelector() {
// Leave room for the button: its label, its padding, and the gap to it.
constexpr const char* ADD_SCENE_LABEL = "Add Scene";
const ImGuiStyle& style = ImGui::GetStyle();
ImGui::SetNextItemWidth(-(ImGui::CalcTextSize(ADD_SCENE_LABEL).x +
style.FramePadding.x * 2.f + style.ItemSpacing.x));
const std::string* current = context.getSceneName(context.getSceneHandle());
bool startRename = false;
if (renamingScene && current) {
drawSceneRename();
} else {
renamingScene = false;
if (ImGui::BeginCombo("##scene", current ? current->c_str() : "No scene")) {
for (const EditorContext::OpenScene& open : context.getOpenScenes()) {
ImGui::PushID(static_cast<int>(open.handle.index));
bool isCurrent = open.handle == context.getSceneHandle();
if (ImGui::Selectable(open.name.c_str(), isCurrent) && !isCurrent) {
context.setScene(open.handle);
}
ImGui::PopID();
}
ImGui::EndCombo();
}
if (current && ImGui::BeginPopupContextItem("##sceneMenu")) {
startRename = ImGui::MenuItem("Rename", "F2");
ImGui::EndPopup();
}
startRename = startRename || (current && ImGui::IsWindowFocused() &&
!ImGui::GetIO().WantTextInput &&
ImGui::IsKeyPressed(ImGuiKey_F2));
}
ImGui::SameLine();
if (ImGui::Button(ADD_SCENE_LABEL)) {
std::string name = "Untitled " + std::to_string(++this->untitledSceneCount);
context.setScene(context.createScene(std::move(name)));
startRename = false;
}
if (startRename) {
renamingScene = true;
renameFocusFrames = RENAME_FOCUS_FRAMES;
std::snprintf(renameBuffer.data(), renameBuffer.size(), "%s",
current->c_str());
}
}
void SceneHierarchyPanel::drawSceneRename() {
if (renameFocusFrames == RENAME_FOCUS_FRAMES) {
ImGui::SetKeyboardFocusHere();
}
ImGui::InputText("##sceneName", renameBuffer.data(), renameBuffer.size(),
ImGuiInputTextFlags_AutoSelectAll);
bool active = ImGui::IsItemActive();
if (active) {
renameFocusFrames = 0;
} else if (renameFocusFrames > 0) {
renameFocusFrames--;
return;
}
// Escape reverts the text before deactivating, so committing whatever is
// left when the field closes covers Enter, clicking away and Escape alike.
if (!active) {
std::string name = renameBuffer.data();
// Keep the old name rather than leave the scene nameless.
if (name.find_first_not_of(' ') != std::string::npos) {
context.renameScene(context.getSceneHandle(), std::move(name));
}
renamingScene = false;
}
}
+25 -7
View File
@@ -1,20 +1,38 @@
#pragma once #pragma once
#include <sophia/entity.hpp> #include <array>
#include <sophia/frame_info.hpp> #include <sophia/frame_info.hpp>
#include <sophia/scene.hpp> #include <sophia/handles.hpp>
#include <sophia/ui/panel.hpp> #include <sophia/ui/panel.hpp>
#include "../editor_context.hpp"
class SceneHierarchyPanel : public sophia::Panel { class SceneHierarchyPanel : public sophia::Panel {
public: public:
SceneHierarchyPanel(sophia::Scene& scene, sophia::Entity& selected) // `cubeMesh` is what "+ Cube" gives new entities.
SceneHierarchyPanel(EditorContext& context, sophia::MeshHandle cubeMesh)
: Panel{{.name = "Scene Hierarchy", .locked = true}}, : Panel{{.name = "Scene Hierarchy", .locked = true}},
scene{scene}, context{context},
selected{selected} {} cubeMesh{cubeMesh} {}
void onUpdate(const sophia::FrameInfo& frameInfo) override; void onUpdate(const sophia::FrameInfo& frameInfo) override;
private: private:
sophia::Scene& scene; // Switches between open scenes, renames the active one, and creates new
sophia::Entity& selected; // ones.
void drawSceneSelector();
// Replaces the selector with a text field editing the active scene's name.
void drawSceneRename();
EditorContext& context;
sophia::MeshHandle cubeMesh;
// For naming new scenes "Untitled 1", "Untitled 2", ...
int untitledSceneCount = 0;
// Set while the scene selector is a text field for renaming.
bool renamingScene = false;
// Frames the text field has left to become active after renaming starts;
// focus can take a frame to land. If it never does, renaming ends.
int renameFocusFrames = 0;
std::array<char, 128> renameBuffer{};
}; };
+603
View File
@@ -0,0 +1,603 @@
#include "scene_panel.hpp"
#include <imgui.h>
// After imgui.h, which it relies on.
#include <ImGuizmo.h>
#include <algorithm>
#include <array>
#include <cmath>
#include <glm/gtc/type_ptr.hpp>
#include <limits>
#include <optional>
#include <sophia/aabb.hpp>
#include <sophia/components.hpp>
#include <tuple>
#include <utility>
#include <vector>
namespace {
// Corners of the unit quad a SpriteComponent's model matrix maps to world.
constexpr std::array<glm::vec2, 4> UNIT_QUAD{
glm::vec2{0.f, 0.f}, glm::vec2{1.f, 0.f}, glm::vec2{1.f, 1.f},
glm::vec2{0.f, 1.f}};
const ImU32 SELECTION_COLOR = IM_COL32(255, 170, 40, 255);
// Holding Ctrl snaps the gizmo to these steps.
constexpr float TRANSLATE_SNAP = 0.5f;
constexpr float ROTATE_SNAP_DEGREES = 15.f;
constexpr float SCALE_SNAP = 0.1f;
// Writes a matrix built from a translation, rotation and scale (no shear)
// back into `transform`. A mirroring matrix comes back with a negative x
// scale.
void decompose(const glm::mat4& matrix, sophia::TransformComponent& transform) {
transform.position = glm::vec3{matrix[3]};
glm::vec3 scale{glm::length(glm::vec3{matrix[0]}),
glm::length(glm::vec3{matrix[1]}),
glm::length(glm::vec3{matrix[2]})};
if (glm::determinant(glm::mat3{matrix}) < 0.f) {
scale.x = -scale.x;
}
transform.scale = scale;
// A zero scale loses the axis, and with it the rotation; keep the old one.
if (scale.x == 0.f || scale.y == 0.f || scale.z == 0.f) {
return;
}
glm::mat3 rotation{glm::vec3{matrix[0]} / scale.x,
glm::vec3{matrix[1]} / scale.y,
glm::vec3{matrix[2]} / scale.z};
transform.rotation = glm::normalize(glm::quat_cast(rotation));
}
// How far along a ray (from `origin`, along the normalized `direction`) it
// enters `bounds` placed by `model`, or null if it misses. A ray starting
// inside hits at 0.
std::optional<float> intersectBox(const glm::vec3& origin,
const glm::vec3& direction,
const glm::mat4& model,
const sophia::AABB& bounds) {
if (glm::determinant(model) == 0.f) {
return std::nullopt;
}
// Test in the box's space, where it's axis-aligned. The direction isn't
// renormalized, so distances along it are still world distances.
glm::mat4 inverse = glm::inverse(model);
glm::vec3 localOrigin = glm::vec3{inverse * glm::vec4{origin, 1.f}};
glm::vec3 localDirection = glm::vec3{inverse * glm::vec4{direction, 0.f}};
float enter = 0.f;
float exit = std::numeric_limits<float>::infinity();
for (int axis = 0; axis < 3; axis++) {
if (std::abs(localDirection[axis]) < 1e-8f) {
// Parallel to this pair of faces: hits only if already between them.
if (localOrigin[axis] < bounds.min[axis] ||
localOrigin[axis] > bounds.max[axis]) {
return std::nullopt;
}
continue;
}
float toMin = (bounds.min[axis] - localOrigin[axis]) / localDirection[axis];
float toMax = (bounds.max[axis] - localOrigin[axis]) / localDirection[axis];
if (toMin > toMax) {
std::swap(toMin, toMax);
}
enter = std::max(enter, toMin);
exit = std::min(exit, toMax);
if (enter > exit) {
return std::nullopt;
}
}
return enter;
}
// How far along a ray it hits the unit quad (0, 0)-(1, 1) in the XY plane,
// placed by `model`, or null if it misses.
std::optional<float> intersectQuad(const glm::vec3& origin,
const glm::vec3& direction,
const glm::mat4& model) {
if (glm::determinant(model) == 0.f) {
return std::nullopt;
}
glm::mat4 inverse = glm::inverse(model);
glm::vec3 localOrigin = glm::vec3{inverse * glm::vec4{origin, 1.f}};
glm::vec3 localDirection = glm::vec3{inverse * glm::vec4{direction, 0.f}};
// Edge on to the quad.
if (std::abs(localDirection.z) < 1e-8f) {
return std::nullopt;
}
float distance = -localOrigin.z / localDirection.z;
if (distance < 0.f) {
return std::nullopt;
}
glm::vec3 hit = localOrigin + distance * localDirection;
if (hit.x < 0.f || hit.x > 1.f || hit.y < 0.f || hit.y > 1.f) {
return std::nullopt;
}
return distance;
}
} // namespace
ImVec2 ScenePanel::ViewportTransform::clipToScreen(
const glm::vec4& clip) const {
glm::vec2 ndc = glm::vec2{clip} / clip.w;
// Vulkan NDC has +y pointing down, same as the screen.
return {origin.x + (ndc.x * 0.5f + 0.5f) * size.x,
origin.y + (ndc.y * 0.5f + 0.5f) * size.y};
}
std::optional<ImVec2> ScenePanel::ViewportTransform::worldToScreen(
const glm::vec3& world) const {
glm::vec4 clip = viewProjection * glm::vec4{world, 1.f};
if (clip.w <= 0.f) {
return std::nullopt;
}
return clipToScreen(clip);
}
glm::vec2 ScenePanel::ViewportTransform::screenToWorld(
const ImVec2& screen) const {
glm::vec2 ndc{(screen.x - origin.x) / size.x * 2.f - 1.f,
(screen.y - origin.y) / size.y * 2.f - 1.f};
glm::vec4 world = inverseViewProjection * glm::vec4{ndc, 0.f, 1.f};
return glm::vec2{world} / world.w;
}
ScenePanel::Ray ScenePanel::ViewportTransform::screenToRay(
const ImVec2& screen) const {
glm::vec2 ndc{(screen.x - origin.x) / size.x * 2.f - 1.f,
(screen.y - origin.y) / size.y * 2.f - 1.f};
// Depth runs 0 at the near plane to 1 at the far one.
glm::vec4 nearPoint = inverseViewProjection * glm::vec4{ndc, 0.f, 1.f};
glm::vec4 farPoint = inverseViewProjection * glm::vec4{ndc, 1.f, 1.f};
glm::vec3 from = glm::vec3{nearPoint} / nearPoint.w;
glm::vec3 to = glm::vec3{farPoint} / farPoint.w;
return {from, glm::normalize(to - from)};
}
void ScenePanel::ViewportTransform::drawLine(ImDrawList* drawList,
const glm::vec3& a,
const glm::vec3& b, ImU32 color,
float thickness) const {
// Just in front of the camera. Points behind it project mirrored, so the
// part of the line there is cut off rather than drawn.
constexpr float MIN_W = 1e-4f;
glm::vec4 clipA = viewProjection * glm::vec4{a, 1.f};
glm::vec4 clipB = viewProjection * glm::vec4{b, 1.f};
if (clipA.w < MIN_W && clipB.w < MIN_W) {
return;
}
if (clipA.w < MIN_W) {
clipA = glm::mix(clipA, clipB, (MIN_W - clipA.w) / (clipB.w - clipA.w));
} else if (clipB.w < MIN_W) {
clipB = glm::mix(clipB, clipA, (MIN_W - clipB.w) / (clipA.w - clipB.w));
}
drawList->AddLine(clipToScreen(clipA), clipToScreen(clipB), color, thickness);
}
void ScenePanel::onUpdate(const sophia::FrameInfo& frameInfo) {
syncSceneView(context, sceneViews, view, isShown());
drawToolbar();
ImGui::Separator();
const ImVec2 origin = ImGui::GetCursorScreenPos();
const ImVec2 size = ImGui::GetContentRegionAvail();
if (size.x < 1.0f || size.y < 1.0f) {
return;
}
if (!context.hasScene()) {
ImGui::TextDisabled("No scene open");
return;
}
ui.image(target);
// Rolls over ImGuizmo's "mouse is over a handle" state, so IsOver() below
// reports last frame's.
ImGuizmo::BeginFrame();
// A handle can't be grabbed while any ImGui item is hovered, so while the
// mouse is over one, leave out the viewport button, and keep the window
// still so dragging the handle doesn't drag it. Unless a camera drag is
// already under way: dropping the button would end it.
bool overGizmo =
(ImGuizmo::IsOver() || ImGuizmo::IsUsing()) && !viewportActive;
getConfig().windowFlags =
overGizmo ? WINDOW_FLAGS | ImGuiWindowFlags_NoMove : WINDOW_FLAGS;
// The image doesn't take mouse input, so without this, dragging in it
// would drag the window instead.
ImGui::SetCursorScreenPos(origin);
if (overGizmo) {
ImGui::Dummy(size);
} else {
ImGui::InvisibleButton("##viewport", size,
ImGuiButtonFlags_MouseButtonLeft |
ImGuiButtonFlags_MouseButtonRight |
ImGuiButtonFlags_MouseButtonMiddle);
}
viewportActive = !overGizmo && ImGui::IsItemActive();
handleCameraInput(frameInfo, size);
handleGizmoHotkeys();
// Drive the view's camera from the editor camera. The scene view renders
// after the panels update, so this frame already uses it.
camera.getComponent<sophia::TransformComponent>() =
editorCamera.getTransform();
camera.getComponent<sophia::CameraComponent>() = editorCamera.getCamera();
ImDrawList* drawList = ImGui::GetWindowDrawList();
drawList->PushClipRect(origin, ImVec2(origin.x + size.x, origin.y + size.y),
true);
// Screen-space, so it only lines up with anything in 2D.
// TODO: a world-space grid on the XZ plane for 3D, once there's debug
// drawing in scene views.
if (showGrid && editorCamera.mode == EditorCamera::Mode::Scene2D) {
drawGrid(origin, size);
}
// Use the panel's size rather than the target's: the target is resized a
// frame late, and the mouse is over the panel.
float aspect = size.x / size.y;
glm::mat4 view = sophia::CameraComponent::getView(
camera.getComponent<sophia::TransformComponent>());
glm::mat4 viewProjection =
camera.getComponent<sophia::CameraComponent>().getProjection(aspect) *
view;
ViewportTransform viewport{origin, size, viewProjection,
glm::inverse(viewProjection)};
handleSelectionInput(viewport);
drawSelection(viewport);
ImGuizmo::SetDrawlist();
ImGuizmo::SetRect(origin.x, origin.y, size.x, size.y);
drawGizmo(view, aspect);
drawList->PopClipRect();
ImGui::SetCursorScreenPos(ImVec2(origin.x + 8.0f, origin.y + 8.0f));
ImGui::TextDisabled("%.0f x %.0f", size.x, size.y);
}
void ScenePanel::drawToolbar() {
bool is2D = editorCamera.mode == EditorCamera::Mode::Scene2D;
if (ImGui::Checkbox("2D", &is2D)) {
editorCamera.mode =
is2D ? EditorCamera::Mode::Scene2D : EditorCamera::Mode::Scene3D;
dragging = false;
}
ImGui::SameLine();
ImGui::BeginDisabled(!is2D);
ImGui::Checkbox("Grid", &showGrid);
ImGui::EndDisabled();
ImGui::SameLine();
ImGui::TextDisabled("|");
ImGui::SameLine();
const struct {
const char* label;
GizmoOperation operation;
} operations[] = {{"Move (W)", GizmoOperation::Translate},
{"Rotate (E)", GizmoOperation::Rotate},
{"Scale (R)", GizmoOperation::Scale}};
for (const auto& entry : operations) {
if (ImGui::RadioButton(entry.label, gizmoOperation == entry.operation)) {
gizmoOperation = entry.operation;
}
ImGui::SameLine();
}
if (ImGui::Button(gizmoLocal ? "Local" : "World")) {
gizmoLocal = !gizmoLocal;
}
}
void ScenePanel::handleGizmoHotkeys() {
const ImGuiIO& io = ImGui::GetIO();
// W/E/Q fly the camera while the right button is held.
if (!ImGui::IsWindowFocused() || io.WantTextInput ||
ImGui::IsMouseDown(ImGuiMouseButton_Right)) {
return;
}
if (ImGui::IsKeyPressed(ImGuiKey_W, false)) {
gizmoOperation = GizmoOperation::Translate;
} else if (ImGui::IsKeyPressed(ImGuiKey_E, false)) {
gizmoOperation = GizmoOperation::Rotate;
} else if (ImGui::IsKeyPressed(ImGuiKey_R, false)) {
gizmoOperation = GizmoOperation::Scale;
}
}
void ScenePanel::drawGizmo(const glm::mat4& view, float aspect) {
sophia::Entity selected = context.selected;
if (!selected) {
return;
}
bool is2D = editorCamera.mode == EditorCamera::Mode::Scene2D;
// In 2D, only the axes that keep things in the XY plane.
ImGuizmo::OPERATION operation{};
float snapStep = 0.f;
switch (gizmoOperation) {
case GizmoOperation::Translate:
operation = is2D ? ImGuizmo::TRANSLATE_X | ImGuizmo::TRANSLATE_Y
: ImGuizmo::TRANSLATE;
snapStep = TRANSLATE_SNAP;
break;
case GizmoOperation::Rotate:
operation = is2D ? ImGuizmo::ROTATE_Z : ImGuizmo::ROTATE;
snapStep = ROTATE_SNAP_DEGREES;
break;
case GizmoOperation::Scale:
operation =
is2D ? ImGuizmo::SCALE_X | ImGuizmo::SCALE_Y : ImGuizmo::SCALE;
snapStep = SCALE_SNAP;
break;
}
std::array<float, 3> snap{snapStep, snapStep, snapStep};
bool snapping = ImGui::GetIO().KeyCtrl;
// ImGuizmo maps clip space to the screen the OpenGL way, with +y up, so it
// needs the projection without the Y flip CameraComponent adds for Vulkan.
glm::mat4 projection =
camera.getComponent<sophia::CameraComponent>().getProjection(aspect);
projection[1][1] *= -1.f;
auto& transform = selected.getComponent<sophia::TransformComponent>();
glm::mat4 model = transform.mat4();
ImGuizmo::SetOrthographic(is2D);
// Per entity, so a drag in progress doesn't carry over to a new selection.
ImGuizmo::PushID(static_cast<int>(static_cast<sophia::u32>(selected)));
bool changed = ImGuizmo::Manipulate(
glm::value_ptr(view), glm::value_ptr(projection), operation,
gizmoLocal ? ImGuizmo::LOCAL : ImGuizmo::WORLD, glm::value_ptr(model),
nullptr, snapping ? snap.data() : nullptr);
ImGuizmo::PopID();
if (changed) {
decompose(model, transform);
}
}
void ScenePanel::drawGrid(ImVec2 origin, ImVec2 size) {
constexpr float GRID_STEP = 40.0f;
const ImU32 gridColor = IM_COL32(255, 255, 255, 18);
const ImVec2 end(origin.x + size.x, origin.y + size.y);
ImDrawList* drawList = ImGui::GetWindowDrawList();
for (float x = GRID_STEP; x < size.x; x += GRID_STEP) {
drawList->AddLine(ImVec2(origin.x + x, origin.y),
ImVec2(origin.x + x, end.y), gridColor);
}
for (float y = GRID_STEP; y < size.y; y += GRID_STEP) {
drawList->AddLine(ImVec2(origin.x, origin.y + y),
ImVec2(end.x, origin.y + y), gridColor);
}
}
void ScenePanel::handleCameraInput(const sophia::FrameInfo& frameInfo,
ImVec2 size) {
const ImGuiIO& io = ImGui::GetIO();
bool is3D = editorCamera.mode == EditorCamera::Mode::Scene3D;
glm::vec2 mouseDelta{io.MouseDelta.x, io.MouseDelta.y};
// The button stays active while any of its mouse buttons is held, even
// once the mouse leaves the panel.
if (ImGui::IsItemActive()) {
bool rightDown = ImGui::IsMouseDown(ImGuiMouseButton_Right);
bool middleDown = ImGui::IsMouseDown(ImGuiMouseButton_Middle);
if (is3D && rightDown) {
editorCamera.orbit(mouseDelta);
glm::vec3 direction{0.f};
if (ImGui::IsKeyDown(ImGuiKey_D)) direction.x += 1.f;
if (ImGui::IsKeyDown(ImGuiKey_A)) direction.x -= 1.f;
if (ImGui::IsKeyDown(ImGuiKey_E)) direction.y += 1.f;
if (ImGui::IsKeyDown(ImGuiKey_Q)) direction.y -= 1.f;
if (ImGui::IsKeyDown(ImGuiKey_S)) direction.z += 1.f;
if (ImGui::IsKeyDown(ImGuiKey_W)) direction.z -= 1.f;
editorCamera.fly(direction, static_cast<float>(frameInfo.deltaTime));
} else if (middleDown || (!is3D && rightDown)) {
editorCamera.pan(mouseDelta, size.y);
}
}
if (ImGui::IsItemHovered() && io.MouseWheel != 0.f) {
editorCamera.zoom(io.MouseWheel);
}
if (ImGui::IsWindowFocused() && !io.WantTextInput &&
ImGui::IsKeyPressed(ImGuiKey_F, false)) {
frameSelection();
}
}
void ScenePanel::handleSelectionInput(const ViewportTransform& viewport) {
const ImGuiIO& io = ImGui::GetIO();
if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) {
context.selected = pick(viewport.screenToRay(io.MousePos));
// Dragging the selection around is 2D only; in 3D, use the gizmo.
dragging =
context.selected && editorCamera.mode == EditorCamera::Mode::Scene2D;
}
if (!ImGui::IsItemActive() || !ImGui::IsMouseDown(ImGuiMouseButton_Left)) {
dragging = false;
return;
}
if (dragging && (io.MouseDelta.x != 0.f || io.MouseDelta.y != 0.f)) {
ImVec2 previous(io.MousePos.x - io.MouseDelta.x,
io.MousePos.y - io.MouseDelta.y);
glm::vec2 delta =
viewport.screenToWorld(io.MousePos) - viewport.screenToWorld(previous);
auto& transform =
context.selected.getComponent<sophia::TransformComponent>();
transform.position.x += delta.x;
transform.position.y += delta.y;
}
}
void ScenePanel::drawSelection(const ViewportTransform& viewport) {
sophia::Entity selected = context.selected;
if (!selected) {
return;
}
ImDrawList* drawList = ImGui::GetWindowDrawList();
const auto& transform = selected.getComponent<sophia::TransformComponent>();
if (selected.hasComponent<sophia::SpriteComponent>()) {
glm::mat4 model =
selected.getComponent<sophia::SpriteComponent>().getModel(transform);
for (size_t i = 0; i < UNIT_QUAD.size(); i++) {
const glm::vec2& from = UNIT_QUAD[i];
const glm::vec2& to = UNIT_QUAD[(i + 1) % UNIT_QUAD.size()];
viewport.drawLine(drawList, glm::vec3{model * glm::vec4{from, 0.f, 1.f}},
glm::vec3{model * glm::vec4{to, 0.f, 1.f}},
SELECTION_COLOR, 2.0f);
}
}
if (std::optional<sophia::AABB> bounds = getMeshBounds(selected)) {
glm::mat4 model = transform.mat4();
std::array<glm::vec3, 8> corners = bounds->getCorners();
for (glm::vec3& corner : corners) {
corner = glm::vec3{model * glm::vec4{corner, 1.f}};
}
// Corners whose indices differ in one bit share an edge.
for (int i = 0; i < 8; i++) {
for (int bit : {1, 2, 4}) {
if ((i & bit) == 0) {
viewport.drawLine(drawList, corners[i], corners[i | bit],
SELECTION_COLOR, 2.0f);
}
}
}
}
// Mark the transform's position (the sprite's pivot), which is also all
// there is to show for entities without a sprite or mesh, such as the
// camera.
if (std::optional<ImVec2> center =
viewport.worldToScreen(transform.position)) {
drawList->AddCircle(*center, 4.0f, SELECTION_COLOR, 0, 2.0f);
}
}
void ScenePanel::frameSelection() {
sophia::Entity selected = context.selected;
if (!selected) {
return;
}
const auto& transform = selected.getComponent<sophia::TransformComponent>();
// The selection's corners in world space, from its mesh or sprite.
std::vector<glm::vec3> points;
if (std::optional<sophia::AABB> bounds = getMeshBounds(selected)) {
glm::mat4 model = transform.mat4();
for (const glm::vec3& corner : bounds->getCorners()) {
points.push_back(glm::vec3{model * glm::vec4{corner, 1.f}});
}
}
if (selected.hasComponent<sophia::SpriteComponent>()) {
glm::mat4 model =
selected.getComponent<sophia::SpriteComponent>().getModel(transform);
for (const glm::vec2& corner : UNIT_QUAD) {
points.push_back(glm::vec3{model * glm::vec4{corner, 0.f, 1.f}});
}
}
if (points.empty()) {
// Nothing with a size: frame a unit around its position.
editorCamera.frame(transform.position, 0.5f);
return;
}
glm::vec3 min = points.front();
glm::vec3 max = points.front();
for (const glm::vec3& point : points) {
min = glm::min(min, point);
max = glm::max(max, point);
}
editorCamera.frame((min + max) * 0.5f, glm::length(max - min) * 0.5f);
}
std::optional<sophia::AABB> ScenePanel::getMeshBounds(sophia::Entity entity) {
if (!entity.hasComponent<sophia::MeshComponent>()) {
return std::nullopt;
}
sophia::MeshHandle mesh = entity.getComponent<sophia::MeshComponent>().handle;
if (!resources.isAlive(mesh)) {
return std::nullopt;
}
return resources.getBounds(mesh);
}
sophia::Entity ScenePanel::pick(const Ray& ray) {
sophia::Scene& scene = context.getScene();
// Meshes are opaque and depth tested, so the nearest one hides anything
// behind it.
sophia::Entity nearestMesh;
float meshDistance = std::numeric_limits<float>::infinity();
scene.forEachEntity([&](sophia::Entity entity) {
std::optional<sophia::AABB> bounds = getMeshBounds(entity);
if (!bounds) {
return;
}
glm::mat4 model = entity.getComponent<sophia::TransformComponent>().mat4();
std::optional<float> distance =
intersectBox(ray.origin, ray.direction, model, *bounds);
if (distance && *distance < meshDistance) {
nearestMesh = entity;
meshDistance = *distance;
}
});
// Sprites in front of that mesh draw over it, in the renderer's order:
// higher sortOrder on top, then nearer.
sophia::Entity topSprite;
std::optional<std::tuple<sophia::s32, float>> topOrder;
scene.forEachEntity([&](sophia::Entity entity) {
if (!entity.hasComponent<sophia::SpriteComponent>()) {
return;
}
const auto& sprite = entity.getComponent<sophia::SpriteComponent>();
glm::mat4 model =
sprite.getModel(entity.getComponent<sophia::TransformComponent>());
std::optional<float> distance =
intersectQuad(ray.origin, ray.direction, model);
if (!distance || *distance > meshDistance) {
return;
}
// Negated distance, so nearer compares greater.
std::tuple order{sprite.sortOrder, -*distance};
if (!topOrder || order > *topOrder) {
topSprite = entity;
topOrder = order;
}
});
return topSprite ? topSprite : nearestMesh;
}
+125
View File
@@ -0,0 +1,125 @@
#pragma once
#include <optional>
#include <sophia/aabb.hpp>
#include <sophia/entity.hpp>
#include <sophia/frame_info.hpp>
#include <sophia/handles.hpp>
#include <sophia/math.hpp>
#include <sophia/resource_registry_ref.hpp>
#include <sophia/scene_view_registry.hpp>
#include <sophia/ui/panel.hpp>
#include <sophia/ui/ui.hpp>
#include "../editor_camera.hpp"
#include "../editor_context.hpp"
/**
* Shows the active scene through the editor's own camera.
*
* 3D: right-drag orbits, middle-drag pans, the wheel zooms, right-drag with
* WASD/QE flies. 2D: right- or middle-drag pans, the wheel zooms. F frames
* the selection in both.
*
* The selection gets a transform gizmo: W moves, E rotates, R scales, and
* holding Ctrl snaps. Clicking selects the mesh or sprite under the mouse;
* in 2D, dragging it also moves it.
*/
class ScenePanel : public sophia::Panel {
public:
// Renders into `target` through `view`, a SceneView this panel keeps
// pointed at the active scene. `camera` is the entity the view looks
// through; it belongs to a scene of the editor's own, and this panel
// drives it from its EditorCamera.
ScenePanel(sophia::UI& ui, sophia::ResourceRegistryRef resources,
EditorContext& context, sophia::SceneViewRegistry& sceneViews,
sophia::SceneViewHandle view, sophia::RenderTargetHandle target,
sophia::Entity camera)
: Panel{{.name = "Scene", .windowFlags = WINDOW_FLAGS}},
ui{ui},
resources{resources},
context{context},
sceneViews{sceneViews},
view{view},
target{target},
camera{camera} {}
void onUpdate(const sophia::FrameInfo& frameInfo) override;
private:
static constexpr ImGuiWindowFlags WINDOW_FLAGS =
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse;
enum class GizmoOperation { Translate, Rotate, Scale };
// A half-line in world space. `direction` is normalized, so distances
// along it are in world units.
struct Ray {
glm::vec3 origin;
glm::vec3 direction;
};
// Where the scene image is on screen this frame, and the camera looking
// through it.
struct ViewportTransform {
ImVec2 origin;
ImVec2 size;
glm::mat4 viewProjection;
glm::mat4 inverseViewProjection;
// Null for points behind the camera.
std::optional<ImVec2> worldToScreen(const glm::vec3& world) const;
// Where `screen` is on the camera's near plane. Only the x and y, which
// is all that's needed in 2D.
glm::vec2 screenToWorld(const ImVec2& screen) const;
// From the near plane through `screen`, into the scene.
Ray screenToRay(const ImVec2& screen) const;
// Draws a world-space line, cut off where it passes behind the camera.
void drawLine(ImDrawList* drawList, const glm::vec3& a, const glm::vec3& b,
ImU32 color, float thickness) const;
private:
ImVec2 clipToScreen(const glm::vec4& clip) const;
};
void drawToolbar();
void drawGrid(ImVec2 origin, ImVec2 size);
void handleCameraInput(const sophia::FrameInfo& frameInfo, ImVec2 size);
void handleSelectionInput(const ViewportTransform& viewport);
void handleGizmoHotkeys();
// Draws the transform gizmo on the selection, and applies any change made
// with it. `view` is the editor camera's, and `aspect` the viewport's
// width over its height.
void drawGizmo(const glm::mat4& view, float aspect);
void drawSelection(const ViewportTransform& viewport);
void frameSelection();
// The bounds of the entity's mesh in the mesh's own space, if it has one.
std::optional<sophia::AABB> getMeshBounds(sophia::Entity entity);
// The entity drawn in front along `ray`, or a null Entity. Only meshes and
// sprites can be hit.
sophia::Entity pick(const Ray& ray);
sophia::UI& ui;
sophia::ResourceRegistryRef resources;
EditorContext& context;
sophia::SceneViewRegistry& sceneViews;
sophia::SceneViewHandle view;
sophia::RenderTargetHandle target;
sophia::Entity camera;
EditorCamera editorCamera;
GizmoOperation gizmoOperation = GizmoOperation::Translate;
// Whether the gizmo works along the entity's own axes or the world's.
bool gizmoLocal = true;
// Whether the viewport button was active last frame, i.e. a camera drag or
// click is under way, which the gizmo mustn't interrupt.
bool viewportActive = false;
bool showGrid = true;
// Set while a drag that started on the selected entity is moving it.
bool dragging = false;
};
-202
View File
@@ -1,202 +0,0 @@
#include "viewport_panel.hpp"
#include <imgui.h>
#include <array>
#include <optional>
#include <sophia/components.hpp>
#include <tuple>
namespace {
// Corners of the unit quad a SpriteComponent's model matrix maps to world.
constexpr std::array<glm::vec2, 4> UNIT_QUAD{
glm::vec2{0.f, 0.f}, glm::vec2{1.f, 0.f}, glm::vec2{1.f, 1.f},
glm::vec2{0.f, 1.f}};
const ImU32 SELECTION_COLOR = IM_COL32(255, 170, 40, 255);
} // namespace
ImVec2 ViewportPanel::ViewportTransform::worldToScreen(
const glm::vec3& world) const {
glm::vec4 clip = viewProjection * glm::vec4{world, 1.f};
glm::vec2 ndc = glm::vec2{clip} / clip.w;
// Vulkan NDC has +y pointing down, same as the screen.
return {origin.x + (ndc.x * 0.5f + 0.5f) * size.x,
origin.y + (ndc.y * 0.5f + 0.5f) * size.y};
}
glm::vec2 ViewportPanel::ViewportTransform::screenToWorld(
const ImVec2& screen) const {
glm::vec2 ndc{(screen.x - origin.x) / size.x * 2.f - 1.f,
(screen.y - origin.y) / size.y * 2.f - 1.f};
glm::vec4 world = inverseViewProjection * glm::vec4{ndc, 0.f, 1.f};
return glm::vec2{world} / world.w;
}
void ViewportPanel::onUpdate(const sophia::FrameInfo&) {
drawToolbar();
ImGui::Separator();
const ImVec2 origin = ImGui::GetCursorScreenPos();
const ImVec2 size = ImGui::GetContentRegionAvail();
if (size.x < 1.0f || size.y < 1.0f) {
return;
}
ui.image(target);
// The image doesn't take mouse input, so without this, dragging in it
// would drag the window instead.
ImGui::SetCursorScreenPos(origin);
ImGui::InvisibleButton("##viewport", size);
ImDrawList* drawList = ImGui::GetWindowDrawList();
drawList->PushClipRect(origin, ImVec2(origin.x + size.x, origin.y + size.y),
true);
if (showGrid) {
drawGrid(origin, size);
}
sophia::Entity camera = scene.getActiveCamera();
if (camera && camera.hasComponent<sophia::CameraComponent>()) {
// Use the panel's size rather than the target's: the target is resized
// a frame late, and the mouse is over the panel.
float aspect = size.x / size.y;
glm::mat4 viewProjection =
camera.getComponent<sophia::CameraComponent>().getViewProjection(
camera.getComponent<sophia::TransformComponent>(), aspect);
ViewportTransform viewport{origin, size, viewProjection,
glm::inverse(viewProjection)};
handleInput(viewport);
drawSelection(viewport);
}
drawList->PopClipRect();
ImGui::SetCursorScreenPos(ImVec2(origin.x + 8.0f, origin.y + 8.0f));
ImGui::TextDisabled("%.0f x %.0f", size.x, size.y);
}
void ViewportPanel::drawToolbar() {
const struct {
const char* label;
GizmoMode mode;
} modes[] = {{"Move", GizmoMode::Move},
{"Rotate", GizmoMode::Rotate},
{"Scale", GizmoMode::Scale}};
for (const auto& entry : modes) {
if (ImGui::RadioButton(entry.label, gizmoMode == entry.mode)) {
gizmoMode = entry.mode;
}
ImGui::SameLine();
}
ImGui::Checkbox("Grid", &showGrid);
}
void ViewportPanel::drawGrid(ImVec2 origin, ImVec2 size) {
constexpr float GRID_STEP = 40.0f;
const ImU32 gridColor = IM_COL32(255, 255, 255, 18);
const ImVec2 end(origin.x + size.x, origin.y + size.y);
ImDrawList* drawList = ImGui::GetWindowDrawList();
for (float x = GRID_STEP; x < size.x; x += GRID_STEP) {
drawList->AddLine(ImVec2(origin.x + x, origin.y),
ImVec2(origin.x + x, end.y), gridColor);
}
for (float y = GRID_STEP; y < size.y; y += GRID_STEP) {
drawList->AddLine(ImVec2(origin.x, origin.y + y),
ImVec2(end.x, origin.y + y), gridColor);
}
}
void ViewportPanel::handleInput(const ViewportTransform& viewport) {
const ImGuiIO& io = ImGui::GetIO();
if (ImGui::IsItemActivated()) {
selected = pick(viewport.screenToWorld(io.MousePos));
dragging = selected && gizmoMode == GizmoMode::Move;
}
if (!ImGui::IsItemActive()) {
dragging = false;
return;
}
if (dragging && (io.MouseDelta.x != 0.f || io.MouseDelta.y != 0.f)) {
ImVec2 previous(io.MousePos.x - io.MouseDelta.x,
io.MousePos.y - io.MouseDelta.y);
glm::vec2 delta =
viewport.screenToWorld(io.MousePos) - viewport.screenToWorld(previous);
auto& transform = selected.getComponent<sophia::TransformComponent>();
transform.position.x += delta.x;
transform.position.y += delta.y;
}
}
void ViewportPanel::drawSelection(const ViewportTransform& viewport) {
if (!selected) {
return;
}
ImDrawList* drawList = ImGui::GetWindowDrawList();
const auto& transform = selected.getComponent<sophia::TransformComponent>();
if (selected.hasComponent<sophia::SpriteComponent>()) {
glm::mat4 model =
selected.getComponent<sophia::SpriteComponent>().getModel(transform);
std::array<ImVec2, 4> corners;
for (size_t i = 0; i < UNIT_QUAD.size(); i++) {
corners[i] = viewport.worldToScreen(
glm::vec3{model * glm::vec4{UNIT_QUAD[i], 0.f, 1.f}});
}
drawList->AddQuad(corners[0], corners[1], corners[2], corners[3],
SELECTION_COLOR, 2.0f);
}
// Mark the transform's position (the sprite's pivot), which is also all
// there is to show for entities without a sprite, such as the camera.
ImVec2 center = viewport.worldToScreen(transform.position);
drawList->AddCircle(center, 4.0f, SELECTION_COLOR, 0, 2.0f);
}
sophia::Entity ViewportPanel::pick(glm::vec2 world) {
sophia::Entity best;
std::optional<std::tuple<sophia::s32, float>> bestOrder;
scene.forEachEntity([&](sophia::Entity entity) {
if (!entity.hasComponent<sophia::SpriteComponent>()) {
return;
}
const auto& transform = entity.getComponent<sophia::TransformComponent>();
const auto& sprite = entity.getComponent<sophia::SpriteComponent>();
glm::mat4 model = sprite.getModel(transform);
// Zero size or scale; nothing to hit.
if (glm::determinant(model) == 0.f) {
return;
}
glm::vec4 local =
glm::inverse(model) * glm::vec4{world, transform.position.z, 1.f};
if (local.x < 0.f || local.x > 1.f || local.y < 0.f || local.y > 1.f) {
return;
}
// Same ordering the renderer draws in, so this is the one on top.
std::tuple order{sprite.sortOrder, transform.position.z};
if (!bestOrder || order >= *bestOrder) {
best = entity;
bestOrder = order;
}
});
return best;
}
-64
View File
@@ -1,64 +0,0 @@
#pragma once
#include <sophia/entity.hpp>
#include <sophia/frame_info.hpp>
#include <sophia/handles.hpp>
#include <sophia/math.hpp>
#include <sophia/scene.hpp>
#include <sophia/ui/panel.hpp>
#include <sophia/ui/ui.hpp>
/**
* Shows the scene through its active camera. Click a sprite to select it,
* drag it to move it (in Move mode).
*
* Picking and moving are 2D: they assume an orthographic camera rotated only
* around Z, and work in the z = 0 plane.
*/
class ViewportPanel : public sophia::Panel {
public:
ViewportPanel(sophia::UI& ui, sophia::RenderTargetHandle target,
sophia::Scene& scene, sophia::Entity& selected)
: Panel{{.name = "Viewport",
.windowFlags = ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_NoScrollWithMouse}},
ui{ui},
target{target},
scene{scene},
selected{selected} {}
void onUpdate(const sophia::FrameInfo& frameInfo) override;
private:
// Where the scene image is on screen this frame, and the camera looking
// through it.
struct ViewportTransform {
ImVec2 origin;
ImVec2 size;
glm::mat4 viewProjection;
glm::mat4 inverseViewProjection;
ImVec2 worldToScreen(const glm::vec3& world) const;
glm::vec2 screenToWorld(const ImVec2& screen) const;
};
void drawToolbar();
void drawGrid(ImVec2 origin, ImVec2 size);
void handleInput(const ViewportTransform& viewport);
void drawSelection(const ViewportTransform& viewport);
// The top-most sprite under `world`, or a null Entity.
sophia::Entity pick(glm::vec2 world);
sophia::UI& ui;
sophia::RenderTargetHandle target;
sophia::Scene& scene;
sophia::Entity& selected;
enum class GizmoMode { Move, Rotate, Scale };
GizmoMode gizmoMode = GizmoMode::Move;
bool showGrid = true;
// Set while a drag that started on the selected entity is moving it.
bool dragging = false;
};
+8
View File
@@ -0,0 +1,8 @@
#pragma once
#include <sophia/util/logger.hpp>
template <typename... Args>
void log(Args... args) {
sophia::log::print("\033[1;38;2;170;90;255m[editor]\033[0m", args...);
}
+30
View File
@@ -0,0 +1,30 @@
#pragma once
#include <array>
#include <sophia/math.hpp>
namespace sophia {
/** @brief Axis-Aligned Bounding Box
*
* An axis-aligned box, from its smallest corner to its largest.
*/
struct AABB {
vec3 min{0.f, 0.f, 0.f};
vec3 max{0.f, 0.f, 0.f};
vec3 getCenter() const { return (min + max) * 0.5f; }
vec3 getSize() const { return max - min; }
// The 8 corners. Bit 0 of the index picks x from max, bit 1 y, bit 2 z.
std::array<vec3, 8> getCorners() const {
std::array<vec3, 8> corners;
for (int i = 0; i < 8; i++) {
corners[i] = {(i & 1) ? max.x : min.x, (i & 2) ? max.y : min.y,
(i & 4) ? max.z : min.z};
}
return corners;
}
};
} // namespace sophia
-2
View File
@@ -3,7 +3,6 @@
#include <cstdlib> #include <cstdlib>
#include <iostream> #include <iostream>
#include <memory> #include <memory>
#include <sophia/camera_2d.hpp>
#include <sophia/canvas.hpp> #include <sophia/canvas.hpp>
#include <sophia/console/console_command_registry.hpp> #include <sophia/console/console_command_registry.hpp>
#include <sophia/frame_info.hpp> #include <sophia/frame_info.hpp>
@@ -53,7 +52,6 @@ class Application {
DebugUI& debugUI() { return this->_debugUI; } DebugUI& debugUI() { return this->_debugUI; }
Canvas canvas; Canvas canvas;
Camera2D camera;
std::vector<std::string> args; std::vector<std::string> args;
ConsoleCommandRegistry consoleCommandRegistry; ConsoleCommandRegistry consoleCommandRegistry;
-28
View File
@@ -1,28 +0,0 @@
#pragma once
#include <glm/gtc/matrix_transform.hpp>
#include <sophia/math.hpp>
namespace sophia {
class Camera2D {
public:
glm::vec2 position{0.f, 0.f};
void setProjection(float left, float right, float bottom, float top) {
projection = glm::ortho(left, right, bottom, top);
}
const glm::mat4& getProjection() const { return projection; }
glm::mat4 getView() const {
return glm::translate(glm::mat4(1.f), glm::vec3(-position, 0.f));
}
glm::mat4 getViewProjection() const { return projection * getView(); }
private:
glm::mat4 projection{1.f};
};
} // namespace sophia
+40 -17
View File
@@ -43,8 +43,15 @@ struct TransformComponent {
} }
}; };
/**
* Draws a mesh, placed by the entity's TransformComponent. Opaque and unlit
* for now.
*/
struct MeshComponent { struct MeshComponent {
MeshHandle handle; MeshHandle handle;
// Multiplied with the mesh's vertex colors. Alpha is ignored. Stands in for
// a material until there are materials.
glm::vec4 color{1.f, 1.f, 1.f, 1.f};
}; };
/** /**
@@ -58,13 +65,17 @@ struct SpriteComponent {
// Size in world units, before the transform's scale. // Size in world units, before the transform's scale.
glm::vec2 size{1.f, 1.f}; glm::vec2 size{1.f, 1.f};
// Point on the sprite, from (0, 0) to (1, 1) across its size, that sits at // Point on the sprite, from (0, 0) at its bottom-left to (1, 1) at its
// the transform's position and that it rotates around. // top-right, that sits at the transform's position and that it rotates
// around.
glm::vec2 pivot{0.5f, 0.5f}; glm::vec2 pivot{0.5f, 0.5f};
// Region of the texture to draw, as normalized (u0, v0, u1, v1). // Region of the texture to draw, as normalized (u0, v0, u1, v1) with
// (0, 0) at the texture's top-left.
glm::vec4 uvRect{0.f, 0.f, 1.f, 1.f}; glm::vec4 uvRect{0.f, 0.f, 1.f, 1.f};
// Higher draws on top. Ties are broken by the transform's position.z. // Higher draws on top, whatever the depth. Sprites with the same sortOrder
// draw back to front from the camera; the transform's position.z is depth
// like any other axis, not a layer.
s32 sortOrder = 0; s32 sortOrder = 0;
// Maps the unit quad (0, 0)-(1, 1) to world space: to the sprite's // Maps the unit quad (0, 0)-(1, 1) to world space: to the sprite's
@@ -79,8 +90,8 @@ struct SpriteComponent {
struct CameraComponent { struct CameraComponent {
struct Orthographic { struct Orthographic {
float height = 10.f; float height = 10.f;
float nearPlane = -1.f; float nearPlane = -1000.f;
float farPlane = 1.f; float farPlane = 1000.f;
}; };
struct Perspective { struct Perspective {
@@ -95,25 +106,37 @@ struct CameraComponent {
Orthographic ortho; Orthographic ortho;
Perspective perspective; Perspective perspective;
// View -> clip space. World +Y is up on screen: Vulkan's clip space has +Y
// pointing down, so the projection flips it.
glm::mat4 getProjection(float aspect) const { glm::mat4 getProjection(float aspect) const {
glm::mat4 result;
if (projection == Projection::Perspective) { if (projection == Projection::Perspective) {
return glm::perspective(perspective.fov, aspect, perspective.nearPlane, result = glm::perspective(perspective.fov, aspect, perspective.nearPlane,
perspective.farPlane); perspective.farPlane);
} else {
float halfHeight = ortho.height / 2.f;
float halfWidth = halfHeight * aspect;
result = glm::ortho(-halfWidth, halfWidth, -halfHeight, halfHeight,
ortho.nearPlane, ortho.farPlane);
} }
float halfHeight = ortho.height / 2.f; result[1][1] *= -1.f;
float halfWidth = halfHeight * aspect; return result;
return glm::ortho(-halfWidth, halfWidth, -halfHeight, halfHeight,
ortho.nearPlane, ortho.farPlane);
} }
// World -> clip space for this camera placed by `transform`. The view // World -> view space for this camera placed by `transform`. Ignores the
// ignores the transform's scale; zoom is the projection's job. // transform's scale; zoom is the projection's job. The camera looks down
glm::mat4 getViewProjection(const TransformComponent& transform, // view-space -Z.
float aspect) const { static glm::mat4 getView(const TransformComponent& transform) {
glm::mat4 world = glm::translate(glm::mat4{1.f}, transform.position) * glm::mat4 world = glm::translate(glm::mat4{1.f}, transform.position) *
glm::mat4_cast(transform.rotation); glm::mat4_cast(transform.rotation);
return getProjection(aspect) * glm::inverse(world); return glm::inverse(world);
}
// World -> clip space for this camera placed by `transform`.
glm::mat4 getViewProjection(const TransformComponent& transform,
float aspect) const {
return getProjection(aspect) * getView(transform);
} }
}; };
-2
View File
@@ -1,6 +1,5 @@
#pragma once #pragma once
#include <sophia/camera_2d.hpp>
#include <sophia/math.hpp> #include <sophia/math.hpp>
#include <sophia/types.hpp> #include <sophia/types.hpp>
@@ -11,7 +10,6 @@ struct FrameInfo {
double elapsedTime; double elapsedTime;
double deltaTime; double deltaTime;
glm::vec2 currentFramebufferExtent; glm::vec2 currentFramebufferExtent;
const Camera2D& camera;
}; };
} // namespace sophia } // namespace sophia
+29
View File
@@ -0,0 +1,29 @@
#pragma once
#include <sophia/math.hpp>
#include <sophia/types.hpp>
#include <vector>
namespace sophia {
// Layout must match the per-vertex inputs in mesh.vert.
struct MeshVertex {
vec3 position{0.f, 0.f, 0.f};
vec3 normal{0.f, 0.f, 1.f};
// Texture coordinates, with (0, 0) at the texture's top-left.
vec2 uv{0.f, 0.f};
vec4 color{1.f, 1.f, 1.f, 1.f};
};
/**
* Geometry on the CPU, uploaded with `resources().create<Mesh>(data)`.
* Triangles wind counter-clockwise when seen from the front; back faces are
* culled. See primitives.hpp for ready-made shapes.
*/
struct MeshData {
std::vector<MeshVertex> vertices;
// Empty draws the vertices in order, three per triangle.
std::vector<u32> indices;
};
} // namespace sophia
+18
View File
@@ -0,0 +1,18 @@
#pragma once
#include <sophia/math.hpp>
#include <sophia/mesh_data.hpp>
/**
* Ready-made shapes, centered on the origin. They return MeshData rather than
* a mesh so they can be adjusted before uploading:
*
* MeshHandle cube = resources().create<Mesh>(primitives::cube());
*/
namespace sophia::primitives {
// An axis-aligned cube `size` across. Each face has its own four vertices so
// it gets its own normal and UVs.
MeshData cube(float size = 1.f, vec4 color = vec4{1.f});
} // namespace sophia::primitives
@@ -1,8 +1,9 @@
#pragma once #pragma once
#include <sophia/canvas.hpp> #include <sophia/aabb.hpp>
#include <sophia/handles.hpp> #include <sophia/handles.hpp>
#include <sophia/math.hpp> #include <sophia/math.hpp>
#include <sophia/mesh_data.hpp>
#include <sophia/types.hpp> #include <sophia/types.hpp>
#include <string> #include <string>
#include <type_traits> #include <type_traits>
@@ -28,6 +29,10 @@ class ResourceRegistryRef {
glm::vec2 getExtent(RenderTargetHandle handle) const; glm::vec2 getExtent(RenderTargetHandle handle) const;
glm::vec2 getExtent(TextureHandle handle) const; glm::vec2 getExtent(TextureHandle handle) const;
// In the mesh's own space.
AABB getBounds(MeshHandle handle) const;
bool isAlive(MeshHandle handle) const;
bool isAlive(RenderTargetHandle handle) const; bool isAlive(RenderTargetHandle handle) const;
bool isAlive(TextureHandle handle) const; bool isAlive(TextureHandle handle) const;
@@ -40,9 +45,7 @@ class ResourceRegistryRef {
explicit ResourceRegistryRef(ResourceRegistry& registry) explicit ResourceRegistryRef(ResourceRegistry& registry)
: registry{&registry} {} : registry{&registry} {}
MeshHandle createResource(std::type_identity<Mesh>, MeshHandle createResource(std::type_identity<Mesh>, const MeshData& data);
std::vector<CanvasVertex> vertices,
std::vector<u32> indices = {});
RenderTargetHandle createResource(std::type_identity<RenderTarget>, RenderTargetHandle createResource(std::type_identity<RenderTarget>,
u32 width = 1, u32 height = 1); u32 width = 1, u32 height = 1);
+8
View File
@@ -25,12 +25,20 @@ class Scene {
Entity getActiveCamera(); Entity getActiveCamera();
void setActiveCamera(Entity camera); void setActiveCamera(Entity camera);
bool isVisible() const { return this->visible; }
void setVisible(bool visible) { this->visible = visible; }
bool isActive() const { return this->active; }
void setActive(bool active) { this->active = active; }
private: private:
friend class Entity; friend class Entity;
friend class SceneRenderer; // !TEMP remove this friend class SceneRenderer; // !TEMP remove this
entt::registry registry; entt::registry registry;
entt::entity activeCamera = entt::null; entt::entity activeCamera = entt::null;
bool visible = true;
bool active = true;
}; };
} // namespace sophia } // namespace sophia
+4 -1
View File
@@ -19,8 +19,11 @@ struct SceneView {
SceneHandle scene = INVALID_HANDLE; SceneHandle scene = INVALID_HANDLE;
RenderTargetHandle target = RenderTargetHandle target =
INVALID_HANDLE; // null implies render to swapchain INVALID_HANDLE; // null implies render to swapchain
Entity camera; // null implies use scene's active camera // Null implies the scene's active camera. May belong to another scene,
// e.g. an editor camera that isn't part of the scene being shown.
Entity camera;
// TODO(#76): not used yet; every view renders to its whole target.
vec4 viewport{0.0f, 0.0f, 1.0f, 1.0f}; vec4 viewport{0.0f, 0.0f, 1.0f, 1.0f};
vec4 clearColor{0.f, 0.f, 0.f, 1.f}; vec4 clearColor{0.f, 0.f, 0.f, 1.f};
+2
View File
@@ -48,6 +48,7 @@ class Panel {
bool isLocked() const { return config.locked; } bool isLocked() const { return config.locked; }
bool isVisible() const { return this->visible; } bool isVisible() const { return this->visible; }
bool isShown() const { return this->visible && this->shown; }
void setVisible(bool visible) { this->visible = visible; } void setVisible(bool visible) { this->visible = visible; }
void toggleVisible() { this->visible = !this->visible; } void toggleVisible() { this->visible = !this->visible; }
@@ -55,6 +56,7 @@ class Panel {
virtual void onUpdate(const FrameInfo& frameInfo) = 0; virtual void onUpdate(const FrameInfo& frameInfo) = 0;
bool visible = true; bool visible = true;
bool shown = false;
private: private:
Config config; Config config;
+21
View File
@@ -5,6 +5,7 @@
* using namespace sophia; * using namespace sophia;
* log::fatal("fatal message") * log::fatal("fatal message")
* log::info("Infomation message") * log::info("Infomation message")
* log::print("no prefix, just the message")
* *
* TODO Impl some form of log error handling * TODO Impl some form of log error handling
*/ */
@@ -122,4 +123,24 @@ void trace(Args... args) {
log(LEVEL::TRACE, args...); log(LEVEL::TRACE, args...);
} }
// Like the functions above, but with no "[LEVEL]" prefix: just the arguments,
// separated by spaces, then a newline. With none, an empty line.
inline void print() {
#ifndef NDEBUG
std::cout << std::endl;
#endif // !NDEBUG
}
template <typename First, typename... Rest>
void print(First first, Rest... rest) {
#ifndef NDEBUG
std::cout << first;
((std::cout << ' ' << rest), ...);
std::cout << std::endl;
#else
(void)first;
(void)std::initializer_list<int>{((void)rest, 0)...};
#endif // !NDEBUG
}
} // namespace sophia::log } // namespace sophia::log
+81 -28
View File
@@ -5,7 +5,9 @@
#include <chrono> #include <chrono>
#include <cstddef> #include <cstddef>
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <map> #include <map>
#include <optional>
#include <set> #include <set>
#include <sophia/application.hpp> #include <sophia/application.hpp>
#include <sophia/util/logger.hpp> #include <sophia/util/logger.hpp>
@@ -92,8 +94,9 @@ class Application::Impl {
imguiRenderer.setDefaultLayout(panelRegistry.getDefaultLayout()); imguiRenderer.setDefaultLayout(panelRegistry.getDefaultLayout());
// Views already warned about having no camera, so each warns once. // Views already warned about, so each warns once.
std::set<SceneViewHandle> viewsWithoutCamera; std::set<SceneViewHandle> viewsWithoutCamera;
std::set<SceneViewHandle> viewsOverwritingOthers;
auto startTime = std::chrono::high_resolution_clock::now(); auto startTime = std::chrono::high_resolution_clock::now();
auto currentTime = startTime; auto currentTime = startTime;
@@ -117,17 +120,20 @@ class Application::Impl {
vk::Extent2D extent = renderContext.getExtent(); vk::Extent2D extent = renderContext.getExtent();
FrameInfo frameInfo{frameIndex, elapsedTime, deltaTime, FrameInfo frameInfo{frameIndex, elapsedTime, deltaTime,
glm::vec2(static_cast<float>(extent.width), glm::vec2(static_cast<float>(extent.width),
static_cast<float>(extent.height)), static_cast<float>(extent.height))};
this->application.camera};
{ {
ZoneScopedN("Application::onUpdate"); ZoneScopedN("Application::onUpdate");
this->application.onUpdate(frameInfo); this->application.onUpdate(frameInfo);
} }
this->application.camera.setProjection(
-frameInfo.currentFramebufferExtent.x / 2.f, // Canvas draws in window pixels, origin at the center.
frameInfo.currentFramebufferExtent.x / 2.f, // TODO: replace with Debug/Gizmos drawn into scene views through their
-frameInfo.currentFramebufferExtent.y / 2.f, // cameras.
frameInfo.currentFramebufferExtent.y / 2.f); glm::mat4 canvasProjection =
glm::ortho(-frameInfo.currentFramebufferExtent.x / 2.f,
frameInfo.currentFramebufferExtent.x / 2.f,
-frameInfo.currentFramebufferExtent.y / 2.f,
frameInfo.currentFramebufferExtent.y / 2.f);
vk::CommandBuffer commandBuffer = renderContext.beginFrame(); vk::CommandBuffer commandBuffer = renderContext.beginFrame();
if (!commandBuffer) { if (!commandBuffer) {
@@ -152,37 +158,79 @@ class Application::Impl {
this->drawCalls.end()); this->drawCalls.end());
this->drawCalls.clear(); this->drawCalls.clear();
RenderGraph graph; RenderGraph graph{renderContext.getTracyContext()};
ImageRef backbuffer = ImageRef backbuffer =
graph.importImage(renderContext.getCurrentImage(), Access::Present); graph.importImage(renderContext.getCurrentImage(), Access::Present);
std::map<RenderTargetHandle, ImageRef> viewTargets; struct ViewTarget {
ImageRef color;
ImageRef depth;
};
std::map<RenderTargetHandle, ViewTarget> viewTargets;
// Views without a target render straight into the backbuffer.
std::optional<ViewTarget> swapchainTarget;
for (SceneViewHandle handle : sceneViewRegistry.GetRenderQueue()) { for (SceneViewHandle handle : sceneViewRegistry.GetRenderQueue()) {
const SceneView& view = sceneViewRegistry.peek(handle); const SceneView& view = sceneViewRegistry.peek(handle);
bool toSwapchain = !view.target.isValid();
if (!sceneRegistry.isAlive(view.scene) || if (!sceneRegistry.isAlive(view.scene) ||
!renderTargetPool.isAlive(view.target)) { !sceneRegistry.get(view.scene).isVisible() ||
(!toSwapchain && !renderTargetPool.isAlive(view.target))) {
continue; continue;
} }
auto [it, inserted] = viewTargets.try_emplace(view.target); ViewTarget target;
if (inserted) { vk::Extent2D targetExtent;
it->second = // Whether this is the first view this frame to render to its target.
graph.importImage(renderTargetPool.get(view.target).getImage(), bool firstOnTarget = false;
Access::SampledRead); if (toSwapchain) {
firstOnTarget = !swapchainTarget;
if (!swapchainTarget) {
swapchainTarget = {
backbuffer,
graph.importImage(renderContext.getCurrentDepthImage(),
Access::DepthWrite)};
}
target = *swapchainTarget;
targetExtent = renderContext.getExtent();
} else {
RenderTarget& renderTarget = renderTargetPool.get(view.target);
auto [it, inserted] = viewTargets.try_emplace(view.target);
firstOnTarget = inserted;
if (inserted) {
it->second = {
graph.importImage(renderTarget.getImage(), Access::SampledRead),
graph.importImage(renderTarget.getDepthImage(),
Access::DepthWrite)};
}
target = it->second;
targetExtent = renderTarget.getExtent();
}
if (!firstOnTarget && hasFlag(view.clearFlags, ClearFlags::Color) &&
viewsOverwritingOthers.insert(handle).second) {
log::warning(
"scene view clears its target after another view already "
"rendered to it this frame, hiding that view (give it clearFlags "
"without Color, or render it first with a lower priority)");
} }
const vec4& color = view.clearColor; const vec4& color = view.clearColor;
graph.addPass( graph.addPass(
"scene view", "scene view " + std::to_string(handle.index),
[&](RenderGraph::PassBuilder& pass) { [&](RenderGraph::PassBuilder& pass) {
pass.writeColor(it->second, pass.writeColor(target.color,
hasFlag(view.clearFlags, ClearFlags::Color) hasFlag(view.clearFlags, ClearFlags::Color)
? vk::AttachmentLoadOp::eClear ? vk::AttachmentLoadOp::eClear
: vk::AttachmentLoadOp::eLoad, : vk::AttachmentLoadOp::eLoad,
{color.r, color.g, color.b, color.a}); {color.r, color.g, color.b, color.a});
pass.writeDepth(target.depth,
hasFlag(view.clearFlags, ClearFlags::Depth)
? vk::AttachmentLoadOp::eClear
: vk::AttachmentLoadOp::eLoad);
}, },
[&sceneRenderer, &sceneRegistry, &sceneViewRegistry, [&sceneRenderer, &sceneRegistry, &sceneViewRegistry,
&viewsWithoutCamera, handle](vk::CommandBuffer commandBuffer) { &viewsWithoutCamera, handle,
targetExtent](vk::CommandBuffer commandBuffer) {
ZoneScopedN("scene view pass"); ZoneScopedN("scene view pass");
const SceneView& view = sceneViewRegistry.peek(handle); const SceneView& view = sceneViewRegistry.peek(handle);
Entity camera = Entity camera =
@@ -198,22 +246,26 @@ class Application::Impl {
} }
return; return;
} }
sceneRenderer.render(commandBuffer, view, camera); sceneRenderer.render(commandBuffer, view, camera, targetExtent);
}); });
} }
graph.addPass( graph.addPass(
"main", "main",
[&](RenderGraph::PassBuilder& pass) { [&](RenderGraph::PassBuilder& pass) {
pass.writeColor(backbuffer, vk::AttachmentLoadOp::eClear, // Keep what scene views drew into the backbuffer.
pass.writeColor(backbuffer,
swapchainTarget ? vk::AttachmentLoadOp::eLoad
: vk::AttachmentLoadOp::eClear,
CLEAR_COLOR); CLEAR_COLOR);
for (const auto& [handle, target] : viewTargets) { for (const auto& [handle, target] : viewTargets) {
pass.sample(target); pass.sample(target.color);
} }
}, },
[&](vk::CommandBuffer commandBuffer) { [&](vk::CommandBuffer commandBuffer) {
ZoneScopedN("main pass"); ZoneScopedN("main pass");
modelRenderSystem.render(commandBuffer, instances, frameInfo); modelRenderSystem.render(commandBuffer, instances,
canvasProjection);
imguiRenderer.endFrame(commandBuffer); imguiRenderer.endFrame(commandBuffer);
}); });
@@ -239,14 +291,15 @@ class Application::Impl {
} }
void pushMesh(std::vector<CanvasVertex> vertices, std::vector<u32> indices) { void pushMesh(std::vector<CanvasVertex> vertices, std::vector<u32> indices) {
Mesh::Builder builder{}; MeshData data;
builder.vertices.reserve(vertices.size()); data.vertices.reserve(vertices.size());
for (const CanvasVertex& vertex : vertices) { for (const CanvasVertex& vertex : vertices) {
builder.vertices.push_back({vertex.position, vertex.color}); data.vertices.push_back(
{.position = {vertex.position, 0.f}, .color = {vertex.color, 1.f}});
} }
builder.indices = std::move(indices); data.indices = std::move(indices);
this->meshes.push_back(std::make_unique<Mesh>(this->device, builder)); this->meshes.push_back(std::make_unique<Mesh>(this->device, data));
} }
void clearMeshes() { void clearMeshes() {
+153
View File
@@ -0,0 +1,153 @@
#include "renderer/mesh_render_system.hpp"
#include <algorithm>
#include <sophia/util/logger.hpp>
#include <tracy/Tracy.hpp>
#include "shaders/mesh.frag.spv.h"
#include "shaders/mesh.vert.spv.h"
namespace sophia {
namespace {
// Mesh::getAttributeDescriptions() takes locations below this.
constexpr u32 FIRST_INSTANCE_LOCATION = 4;
} // namespace
MeshRenderSystem::MeshRenderSystem(Device& device, vk::Format colorFormat,
vk::Format depthFormat,
ResourceRegistry& resources)
: device{device}, resources{resources} {
createPipelineLayout();
createPipeline(colorFormat, depthFormat);
createInstanceBuffers();
}
MeshRenderSystem::~MeshRenderSystem() {
log::trace("destroyed sophia::MeshRenderSystem");
}
void MeshRenderSystem::beginFrame(u32 frameIndex) {
this->frameIndex = frameIndex;
this->instanceCursor = 0;
}
void MeshRenderSystem::render(vk::CommandBuffer commandBuffer,
const glm::mat4& viewProjection,
std::span<const Instance> instances,
std::span<const Batch> batches) {
ZoneScoped;
if (instances.empty()) {
return;
}
u32 available = MAX_INSTANCES_PER_FRAME - this->instanceCursor;
if (instances.size() > available) {
log::warning("MeshRenderSystem: frame instance buffer full, dropping",
instances.size() - available, "meshes");
}
Buffer& buffer = *this->instanceBuffers[this->frameIndex];
u32 base = this->instanceCursor;
u32 count = std::min(static_cast<u32>(instances.size()), available);
buffer.writeToBuffer(const_cast<Instance*>(instances.data()),
count * sizeof(Instance), base * sizeof(Instance));
this->instanceCursor += count;
this->graphicsPipeline->bind(commandBuffer);
PushConstantData push{viewProjection};
commandBuffer.pushConstants(this->pipelineLayout.get(),
vk::ShaderStageFlagBits::eVertex, 0,
sizeof(PushConstantData), &push);
// Binding 1 stays bound across batches; each mesh rebinds binding 0.
vk::Buffer instanceBuffer = buffer.getBuffer();
vk::DeviceSize offset = 0;
commandBuffer.bindVertexBuffers(1, 1, &instanceBuffer, &offset);
for (const Batch& batch : batches) {
if (batch.firstInstance >= count) {
break;
}
u32 batchCount = std::min(batch.instanceCount, count - batch.firstInstance);
const Mesh& mesh = this->resources.meshPool.get(batch.mesh);
mesh.bind(commandBuffer);
mesh.draw(commandBuffer, batchCount, base + batch.firstInstance);
}
}
void MeshRenderSystem::createPipelineLayout() {
vk::PushConstantRange pushConstantRange{};
pushConstantRange.stageFlags = vk::ShaderStageFlagBits::eVertex;
pushConstantRange.offset = 0;
pushConstantRange.size = sizeof(PushConstantData);
vk::PipelineLayoutCreateInfo pipelineLayoutInfo = {};
pipelineLayoutInfo.setLayoutCount = 0;
pipelineLayoutInfo.pushConstantRangeCount = 1;
pipelineLayoutInfo.pPushConstantRanges = &pushConstantRange;
try {
this->pipelineLayout =
this->device.get()->createPipelineLayoutUnique(pipelineLayoutInfo);
log::trace("created vk::PipelineLayout");
} catch (const vk::SystemError& err) {
log::fatal("failed to create vk::PipelineLayout");
throw std::runtime_error("failed to create vk::PipelineLayout");
}
}
void MeshRenderSystem::createPipeline(vk::Format colorFormat,
vk::Format depthFormat) {
auto vertexShader = Shader::Builder()
.fromSPIRV(shaders::mesh_vert_spv)
.setStage(ShaderStage::VERTEX)
.build();
auto fragmentShader = Shader::Builder()
.fromSPIRV(shaders::mesh_frag_spv)
.setStage(ShaderStage::FRAGMENT)
.build();
std::vector<vk::VertexInputBindingDescription> bindings =
Mesh::getBindingDescriptions();
bindings.push_back({1, sizeof(Instance), vk::VertexInputRate::eInstance});
std::vector<vk::VertexInputAttributeDescription> attributes =
Mesh::getAttributeDescriptions();
// A mat4 attribute takes four consecutive locations, one per column.
for (u32 column = 0; column < 4; column++) {
attributes.push_back({FIRST_INSTANCE_LOCATION + column, 1,
vk::Format::eR32G32B32A32Sfloat,
static_cast<u32>(offsetof(Instance, model) +
column * sizeof(glm::vec4))});
}
attributes.push_back({FIRST_INSTANCE_LOCATION + 4, 1,
vk::Format::eR32G32B32A32Sfloat,
static_cast<u32>(offsetof(Instance, color))});
this->graphicsPipeline = GraphicsPipeline::Builder(this->device)
.setVertexShader(*vertexShader)
.setFragmentShader(*fragmentShader)
.setPipelineLayout(this->pipelineLayout.get())
.setColorAttachmentFormats({colorFormat})
.setDepthAttachmentFormat(depthFormat)
.setVertexInput(bindings, attributes)
.setDepthTest(true, true)
.setCullMode(vk::CullModeFlagBits::eBack)
.build();
}
void MeshRenderSystem::createInstanceBuffers() {
for (std::unique_ptr<Buffer>& buffer : this->instanceBuffers) {
buffer = std::make_unique<Buffer>(
this->device, sizeof(Instance), MAX_INSTANCES_PER_FRAME,
vk::BufferUsageFlagBits::eVertexBuffer,
vk::MemoryPropertyFlagBits::eHostVisible |
vk::MemoryPropertyFlagBits::eHostCoherent);
buffer->map();
}
}
} // namespace sophia
@@ -0,0 +1,80 @@
#pragma once
#include <array>
#include <memory>
#include <sophia/handles.hpp>
#include <sophia/math.hpp>
#include <span>
#include "renderer/render_context.hpp"
#include "resources/resource_registry.hpp"
#include "vulkan/buffer.hpp"
#include "vulkan/device.hpp"
#include "vulkan/pipelines/graphics_pipeline.hpp"
namespace sophia {
/**
* Draws batches of opaque meshes, depth tested and back-face culled. Knows
* nothing about scenes: callers hand it instances grouped into runs that
* share a mesh.
*
* Unlit for now: each pixel is the vertex color times the instance color.
* Normals and UVs reach the vertex shader but aren't used yet.
*
* Instances are written to a per-frame host-visible buffer, so every
* render() call within one frame appends to it; call beginFrame() once per
* frame before any render().
*/
class MeshRenderSystem {
public:
// Layout must match the per-instance inputs in mesh.vert.
struct Instance {
glm::mat4 model{1.f};
glm::vec4 color{1.f};
};
// A run of consecutive instances of the same mesh.
struct Batch {
MeshHandle mesh;
u32 firstInstance;
u32 instanceCount;
};
static constexpr u32 MAX_INSTANCES_PER_FRAME = 32768;
MeshRenderSystem(const MeshRenderSystem&) = delete;
MeshRenderSystem& operator=(const MeshRenderSystem&) = delete;
MeshRenderSystem(Device& device, vk::Format colorFormat,
vk::Format depthFormat, ResourceRegistry& resources);
~MeshRenderSystem();
void beginFrame(u32 frameIndex);
void render(vk::CommandBuffer commandBuffer, const glm::mat4& viewProjection,
std::span<const Instance> instances,
std::span<const Batch> batches);
private:
struct PushConstantData {
glm::mat4 viewProjection{1.f};
};
void createPipelineLayout();
void createPipeline(vk::Format colorFormat, vk::Format depthFormat);
void createInstanceBuffers();
Device& device;
ResourceRegistry& resources;
vk::UniquePipelineLayout pipelineLayout;
std::unique_ptr<GraphicsPipeline> graphicsPipeline;
std::array<std::unique_ptr<Buffer>, RenderContext::MAX_FRAMES_IN_FLIGHT>
instanceBuffers;
u32 frameIndex = 0;
u32 instanceCursor = 0;
};
} // namespace sophia
+3 -5
View File
@@ -25,8 +25,8 @@ ModelRenderSystem::ModelRenderSystem(Device& device,
.setStage(ShaderStage::FRAGMENT) .setStage(ShaderStage::FRAGMENT)
.build(); .build();
auto bindingDescriptions = Mesh::Vertex::getBindingDescriptions(); auto bindingDescriptions = Mesh::getBindingDescriptions();
auto attributeDescriptions = Mesh::Vertex::getAttributeDescriptions(); auto attributeDescriptions = Mesh::getAttributeDescriptions();
this->graphicsPipeline = this->graphicsPipeline =
GraphicsPipeline::Builder(device) GraphicsPipeline::Builder(device)
@@ -44,10 +44,8 @@ ModelRenderSystem::~ModelRenderSystem() {
void ModelRenderSystem::render(vk::CommandBuffer commandBuffer, void ModelRenderSystem::render(vk::CommandBuffer commandBuffer,
std::span<const MeshInstance> instances, std::span<const MeshInstance> instances,
const FrameInfo& frameInfo) { const glm::mat4& viewProjection) {
ZoneScoped; ZoneScoped;
glm::mat4 viewProjection = frameInfo.camera.getViewProjection();
this->graphicsPipeline->bind(commandBuffer); this->graphicsPipeline->bind(commandBuffer);
for (const MeshInstance& instance : instances) { for (const MeshInstance& instance : instances) {
+2 -2
View File
@@ -1,7 +1,7 @@
#pragma once #pragma once
#include <memory> #include <memory>
#include <sophia/frame_info.hpp> #include <sophia/math.hpp>
#include <span> #include <span>
#include "renderer/render_context.hpp" #include "renderer/render_context.hpp"
@@ -32,7 +32,7 @@ class ModelRenderSystem {
void render(vk::CommandBuffer commandBuffer, void render(vk::CommandBuffer commandBuffer,
std::span<const MeshInstance> instances, std::span<const MeshInstance> instances,
const FrameInfo& frameInfo); const glm::mat4& viewProjection);
private: private:
void createPipelineLayout(); void createPipelineLayout();
+29 -1
View File
@@ -14,9 +14,13 @@ RenderContext::RenderContext(Window& window, Device& device)
: window{window}, device{device} { : window{window}, device{device} {
createFrames(); createFrames();
recreateSwapchain(); recreateSwapchain();
createTracyContext();
} }
RenderContext::~RenderContext() { this->device.waitIdle(); } RenderContext::~RenderContext() {
this->device.waitIdle();
TracyVkDestroy(this->tracyContext);
}
vk::CommandBuffer RenderContext::beginFrame() { vk::CommandBuffer RenderContext::beginFrame() {
ZoneScoped; ZoneScoped;
@@ -75,6 +79,9 @@ vk::CommandBuffer RenderContext::beginFrame() {
throw std::runtime_error("failed to begin recording command buffer"); throw std::runtime_error("failed to begin recording command buffer");
} }
TracyVkCollect(this->tracyContext,
static_cast<VkCommandBuffer>(frame.commandBuffer));
return frame.commandBuffer; return frame.commandBuffer;
} }
@@ -158,6 +165,27 @@ void RenderContext::recreateSwapchain() {
this->imagesInFlight.assign(this->swapchain->imageCount(), vk::Fence{}); this->imagesInFlight.assign(this->swapchain->imageCount(), vk::Fence{});
} }
void RenderContext::createTracyContext() {
#ifdef TRACY_ENABLE
// Tracy records and submits a one-off command buffer to line up GPU and CPU
// clocks, and waits for it.
vk::CommandBufferAllocateInfo allocInfo{this->device.getCommandPool(),
vk::CommandBufferLevel::ePrimary, 1};
vk::CommandBuffer commandBuffer =
this->device.get()->allocateCommandBuffers(allocInfo).front();
this->tracyContext = TracyVkContext(
static_cast<VkPhysicalDevice>(this->device.getPhysicalDevice()),
static_cast<VkDevice>(*this->device.get()),
static_cast<VkQueue>(this->device.getGraphicsQueue()),
static_cast<VkCommandBuffer>(commandBuffer));
TracyVkContextName(this->tracyContext, "Graphics", 8);
this->device.get()->freeCommandBuffers(this->device.getCommandPool(),
commandBuffer);
#endif
}
void RenderContext::createFrames() { void RenderContext::createFrames() {
vk::CommandBufferAllocateInfo allocInfo{this->device.getCommandPool(), vk::CommandBufferAllocateInfo allocInfo{this->device.getCommandPool(),
vk::CommandBufferLevel::ePrimary, vk::CommandBufferLevel::ePrimary,
+12
View File
@@ -6,6 +6,9 @@
#include <vector> #include <vector>
#include <vulkan/vulkan.hpp> #include <vulkan/vulkan.hpp>
/* AFTER VULKAN */
#include <tracy/TracyVulkan.hpp>
#include "vulkan/device.hpp" #include "vulkan/device.hpp"
#include "vulkan/swapchain.hpp" #include "vulkan/swapchain.hpp"
#include "window.hpp" #include "window.hpp"
@@ -35,11 +38,17 @@ class RenderContext {
return this->swapchain->getImage(static_cast<int>(this->currentImageIndex)); return this->swapchain->getImage(static_cast<int>(this->currentImageIndex));
} }
Image& getCurrentDepthImage() const {
return this->swapchain->getDepthImage(
static_cast<int>(this->currentImageIndex));
}
vk::Extent2D getExtent() const { return this->swapchain->getExtent(); } vk::Extent2D getExtent() const { return this->swapchain->getExtent(); }
vk::Format getImageFormat() const { vk::Format getImageFormat() const {
return this->swapchain->getImageFormat(); return this->swapchain->getImageFormat();
} }
u32 getFrameIndex() const { return this->currentFrameIndex; } u32 getFrameIndex() const { return this->currentFrameIndex; }
TracyVkCtx getTracyContext() const { return this->tracyContext; }
u32 getImageCount() const { u32 getImageCount() const {
return static_cast<u32>(this->swapchain->imageCount()); return static_cast<u32>(this->swapchain->imageCount());
} }
@@ -47,6 +56,7 @@ class RenderContext {
private: private:
void recreateSwapchain(); void recreateSwapchain();
void createFrames(); void createFrames();
void createTracyContext();
Window& window; Window& window;
Device& device; Device& device;
@@ -58,6 +68,8 @@ class RenderContext {
std::vector<vk::UniqueSemaphore> renderFinishedSemaphores; std::vector<vk::UniqueSemaphore> renderFinishedSemaphores;
std::vector<vk::Fence> imagesInFlight; std::vector<vk::Fence> imagesInFlight;
TracyVkCtx tracyContext = nullptr;
u32 currentFrameIndex = 0; u32 currentFrameIndex = 0;
u32 currentImageIndex = 0; u32 currentImageIndex = 0;
bool isFrameStarted = false; bool isFrameStarted = false;
+36 -4
View File
@@ -71,6 +71,10 @@ void RenderGraph::flush(vk::CommandBuffer commandBuffer,
} }
void RenderGraph::recordPass(vk::CommandBuffer commandBuffer, Pass& pass) { void RenderGraph::recordPass(vk::CommandBuffer commandBuffer, Pass& pass) {
ZoneTransientN(cpuZone, pass.name.c_str(), true);
TracyVkZoneTransient(this->tracyContext, gpuZone,
static_cast<VkCommandBuffer>(commandBuffer),
pass.name.c_str(), this->tracyContext != nullptr);
beginDebugLabel(commandBuffer, pass.name.c_str()); beginDebugLabel(commandBuffer, pass.name.c_str());
PendingBarriers pending; PendingBarriers pending;
@@ -79,7 +83,7 @@ void RenderGraph::recordPass(vk::CommandBuffer commandBuffer, Pass& pass) {
} }
this->flush(commandBuffer, pending); this->flush(commandBuffer, pending);
if (pass.colorAttachments.empty()) { if (pass.colorAttachments.empty() && !pass.depthAttachment) {
pass.execute(commandBuffer); pass.execute(commandBuffer);
} else { } else {
this->recordRendering(commandBuffer, pass); this->recordRendering(commandBuffer, pass);
@@ -92,9 +96,10 @@ void RenderGraph::recordRendering(vk::CommandBuffer commandBuffer, Pass& pass) {
std::vector<vk::RenderingAttachmentInfo> attachments; std::vector<vk::RenderingAttachmentInfo> attachments;
attachments.reserve(pass.colorAttachments.size()); attachments.reserve(pass.colorAttachments.size());
vk::Extent3D imageExtent = ImageRef sizedBy = pass.colorAttachments.empty()
this->images[pass.colorAttachments.front().image.index] ? pass.depthAttachment->image
.image->getExtent(); : pass.colorAttachments.front().image;
vk::Extent3D imageExtent = this->images[sizedBy.index].image->getExtent();
vk::Extent2D extent{imageExtent.width, imageExtent.height}; vk::Extent2D extent{imageExtent.width, imageExtent.height};
for (const ColorAttachment& color : pass.colorAttachments) { for (const ColorAttachment& color : pass.colorAttachments) {
@@ -119,6 +124,24 @@ void RenderGraph::recordRendering(vk::CommandBuffer commandBuffer, Pass& pass) {
renderingInfo.colorAttachmentCount = static_cast<u32>(attachments.size()); renderingInfo.colorAttachmentCount = static_cast<u32>(attachments.size());
renderingInfo.pColorAttachments = attachments.data(); renderingInfo.pColorAttachments = attachments.data();
vk::RenderingAttachmentInfo depthAttachment{};
if (pass.depthAttachment) {
const Image& image = *this->images[pass.depthAttachment->image.index].image;
SOPHIA_ASSERT(image.getExtent().width == extent.width &&
image.getExtent().height == extent.height,
"Pass", pass.name,
"has a depth attachment of a different size");
depthAttachment.imageView = image.getView();
depthAttachment.imageLayout =
vk::ImageLayout::eDepthStencilAttachmentOptimal;
depthAttachment.loadOp = pass.depthAttachment->loadOp;
depthAttachment.storeOp = vk::AttachmentStoreOp::eStore;
depthAttachment.clearValue.depthStencil =
vk::ClearDepthStencilValue{pass.depthAttachment->clearDepth, 0};
renderingInfo.pDepthAttachment = &depthAttachment;
}
commandBuffer.beginRendering(renderingInfo); commandBuffer.beginRendering(renderingInfo);
vk::Viewport viewport{0.0f, vk::Viewport viewport{0.0f,
@@ -143,6 +166,15 @@ RenderGraph::PassBuilder& RenderGraph::PassBuilder::writeColor(
return *this; return *this;
} }
RenderGraph::PassBuilder& RenderGraph::PassBuilder::writeDepth(
ImageRef image, vk::AttachmentLoadOp loadOp, float clearDepth) {
SOPHIA_ASSERT(!this->pass.depthAttachment, "Pass", this->pass.name,
"already has a depth attachment");
this->use(image, Access::DepthWrite);
this->pass.depthAttachment = DepthAttachment{image, loadOp, clearDepth};
return *this;
}
RenderGraph::PassBuilder& RenderGraph::PassBuilder::sample(ImageRef image) { RenderGraph::PassBuilder& RenderGraph::PassBuilder::sample(ImageRef image) {
return this->use(image, Access::SampledRead); return this->use(image, Access::SampledRead);
} }
+25 -4
View File
@@ -2,10 +2,13 @@
#include <array> #include <array>
#include <functional> #include <functional>
#include <optional>
#include <sophia/types.hpp> #include <sophia/types.hpp>
#include <string> #include <string>
#include <vector> #include <vector>
#include <vulkan/vulkan.hpp> #include <vulkan/vulkan.hpp>
// After Vulkan, which it expects to already be declared.
#include <tracy/TracyVulkan.hpp>
#include "vulkan/access.hpp" #include "vulkan/access.hpp"
#include "vulkan/image.hpp" #include "vulkan/image.hpp"
@@ -31,7 +34,7 @@ using ClearColor = std::array<float, 4>;
* buffer. Passes run in the order they were added; the graph's job is to * buffer. Passes run in the order they were added; the graph's job is to
* work out the barriers and layout transitions between them from what each * work out the barriers and layout transitions between them from what each
* pass says it does to each image, and to begin/end rendering for passes that * pass says it does to each image, and to begin/end rendering for passes that
* write color attachments. * write color or depth attachments.
* *
* Built fresh every frame: * Built fresh every frame:
* *
@@ -53,7 +56,8 @@ class RenderGraph {
using SetupFn = std::function<void(PassBuilder&)>; using SetupFn = std::function<void(PassBuilder&)>;
using ExecuteFn = std::function<void(vk::CommandBuffer)>; using ExecuteFn = std::function<void(vk::CommandBuffer)>;
RenderGraph() = default; explicit RenderGraph(TracyVkCtx tracyContext = nullptr)
: tracyContext{tracyContext} {}
RenderGraph(const RenderGraph&) = delete; RenderGraph(const RenderGraph&) = delete;
RenderGraph& operator=(const RenderGraph&) = delete; RenderGraph& operator=(const RenderGraph&) = delete;
@@ -67,8 +71,8 @@ class RenderGraph {
/** /**
* Adds a pass. `setup` runs immediately and declares what the pass does to * Adds a pass. `setup` runs immediately and declares what the pass does to
* images; `execute` runs later, from execute(), with the barriers already * images; `execute` runs later, from execute(), with the barriers already
* recorded and (if the pass writes color) rendering begun. `name` labels * recorded and (if the pass writes color or depth) rendering begun. `name`
* the pass's commands in debugging tools such as RenderDoc. * labels the pass's commands in debugging tools such as RenderDoc.
*/ */
void addPass(std::string name, const SetupFn& setup, ExecuteFn execute); void addPass(std::string name, const SetupFn& setup, ExecuteFn execute);
@@ -89,10 +93,17 @@ class RenderGraph {
ClearColor clearColor; ClearColor clearColor;
}; };
struct DepthAttachment {
ImageRef image;
vk::AttachmentLoadOp loadOp;
float clearDepth;
};
struct Pass { struct Pass {
std::string name; std::string name;
std::vector<ImageUse> uses; std::vector<ImageUse> uses;
std::vector<ColorAttachment> colorAttachments; std::vector<ColorAttachment> colorAttachments;
std::optional<DepthAttachment> depthAttachment;
ExecuteFn execute; ExecuteFn execute;
}; };
@@ -113,6 +124,7 @@ class RenderGraph {
void recordPass(vk::CommandBuffer commandBuffer, Pass& pass); void recordPass(vk::CommandBuffer commandBuffer, Pass& pass);
void recordRendering(vk::CommandBuffer commandBuffer, Pass& pass); void recordRendering(vk::CommandBuffer commandBuffer, Pass& pass);
TracyVkCtx tracyContext;
std::vector<ImportedImage> images; std::vector<ImportedImage> images;
std::vector<Pass> passes; std::vector<Pass> passes;
}; };
@@ -129,6 +141,15 @@ class RenderGraph::PassBuilder {
vk::AttachmentLoadOp loadOp = vk::AttachmentLoadOp::eClear, vk::AttachmentLoadOp loadOp = vk::AttachmentLoadOp::eClear,
ClearColor clearColor = {0.f, 0.f, 0.f, 1.f}); ClearColor clearColor = {0.f, 0.f, 0.f, 1.f});
/**
* Renders with `image` as the depth attachment, which must be the same size
* as any color attachments. At most one per pass.
*/
PassBuilder& writeDepth(
ImageRef image,
vk::AttachmentLoadOp loadOp = vk::AttachmentLoadOp::eClear,
float clearDepth = 1.f);
/** Samples `image` in a shader. */ /** Samples `image` in a shader. */
PassBuilder& sample(ImageRef image); PassBuilder& sample(ImageRef image);
+9
View File
@@ -0,0 +1,9 @@
#include "renderer/renderer.hpp"
namespace sophia {
Renderer::Renderer() {}
Renderer::~Renderer() {}
} // namespace sophia
+91
View File
@@ -0,0 +1,91 @@
#pragma once
#include <sophia/handles.hpp>
#include <sophia/math.hpp>
#include <sophia/types.hpp>
#include <string>
#include <vector>
#include "vulkan/buffer.hpp"
namespace sophia {
struct DrawPacket {
/* Geometry */
// BufferHandle vertexBuffer;
// BufferHandle indexBuffer;
u32 indexCount;
u32 firstIndex;
/* Shader & state */
// PipelineHandle pipelineState; // Contains shaders, blend state, depth
// settings
/* Bindings */
// DescriptorSetHandle resourceBindings; // Textures, Uniform Buffers,
// Storage Buffers
/* Transform / Constants */
u32 pushConstantData[16];
};
struct RenderView {
mat4 view;
mat4 projection;
vec2 viewport;
RenderTargetHandle target;
};
// struct ComputePacket {
// ComputePipelineHandle pipeline;
// DescriptorSetHandle
// resourceBindings; // Includes storage buffers, uniforms, textures
// // Grid dimensions
// uint32_t groupCountX = 1;
// uint32_t groupCountY = 1;
// uint32_t groupCountZ = 1;
// // Optional push constants for dynamic per-dispatch parameters
// uint8_t pushConstants[64];
// uint32_t pushConstantSize = 0;
// };
// !TODO move to render graph
enum class RenderPassType { Graphics, Compute };
// !TODO move to render graph (???)
struct RenderPass {
std::string name;
RenderPassType type;
// Declared for automatic barrier injection & resource synchronization
std::vector<Buffer> readBuffers; // BufferHandle instead?
std::vector<Buffer> writeBuffers; // BufferHandle instead?
// Commands in this pass
// std::vector<ComputePacket> computeDispatches;
std::vector<DrawPacket> opaqueDrawPackets;
std::vector<DrawPacket> transparentDrawPackets;
};
struct RenderPacket {
RenderView view;
std::vector<RenderPass> passes;
};
class Renderer {
public:
Renderer(const Renderer&) = delete;
Renderer& operator=(const Renderer&) = delete;
Renderer();
~Renderer();
// PipelineHandle createPipeline();
private:
};
} // namespace sophia
+116 -40
View File
@@ -13,73 +13,149 @@ SceneRenderer::SceneRenderer(const SceneRegistry& sceneRegistry,
vk::Format colorFormat) vk::Format colorFormat)
: sceneRegistry{sceneRegistry}, : sceneRegistry{sceneRegistry},
resources{resources}, resources{resources},
spriteRenderSystem{device, colorFormat, resources} {} meshRenderSystem{device, colorFormat,
resources.renderTargetPool.getDepthFormat(), resources},
spriteRenderSystem{device, colorFormat,
resources.renderTargetPool.getDepthFormat(),
resources} {}
void SceneRenderer::beginFrame(u32 frameIndex) { void SceneRenderer::beginFrame(u32 frameIndex) {
ZoneScoped; ZoneScoped;
this->meshRenderSystem.beginFrame(frameIndex);
this->spriteRenderSystem.beginFrame(frameIndex); this->spriteRenderSystem.beginFrame(frameIndex);
} }
void SceneRenderer::render(vk::CommandBuffer commandBuffer, void SceneRenderer::render(vk::CommandBuffer commandBuffer,
const SceneView& sceneView, Entity camera) { const SceneView& sceneView, Entity camera,
vk::Extent2D extent) {
ZoneScoped; ZoneScoped;
SOPHIA_ASSERT(camera.hasComponent<CameraComponent>(), SOPHIA_ASSERT(camera.hasComponent<CameraComponent>(),
"camera does not have camera component!"); "camera does not have camera component!");
vk::Extent2D extent =
this->resources.renderTargetPool.get(sceneView.target).getExtent();
float aspect = float aspect =
static_cast<float>(extent.width) / static_cast<float>(extent.height); static_cast<float>(extent.width) / static_cast<float>(extent.height);
glm::mat4 viewProjection = glm::mat4 view =
camera.getComponent<CameraComponent>().getViewProjection( CameraComponent::getView(camera.getComponent<TransformComponent>());
camera.getComponent<TransformComponent>(), aspect); glm::mat4 projection =
camera.getComponent<CameraComponent>().getProjection(aspect);
renderSprites(commandBuffer, this->sceneRegistry.get(sceneView.scene), const Scene& scene = this->sceneRegistry.get(sceneView.scene);
viewProjection); glm::mat4 viewProjection = projection * view;
// TODO: meshes. Group MeshComponent entities by mesh and draw each group // Opaque first, so sprites blend over the meshes behind them.
// (instanced), as sprites are grouped by texture. renderMeshes(commandBuffer, scene, viewProjection);
renderSprites(commandBuffer, scene, view, viewProjection);
}
void SceneRenderer::renderMeshes(vk::CommandBuffer commandBuffer,
const Scene& scene,
const glm::mat4& viewProjection) {
ZoneScoped;
this->meshDrawItems.clear();
{
ZoneScopedN("meshes: gather");
auto meshes =
scene.registry.view<const TransformComponent, const MeshComponent>();
for (auto [entity, transform, mesh] : meshes.each()) {
if (!this->resources.meshPool.isAlive(mesh.handle)) {
continue;
}
this->meshDrawItems.push_back(
{mesh.handle, {transform.mat4(), mesh.color}});
}
}
{
ZoneScopedN("meshes: sort");
// Opaque, so order only matters for batching: one batch per mesh.
std::ranges::sort(this->meshDrawItems, {},
[](const MeshDrawItem& item) { return item.mesh; });
}
{
ZoneScopedN("meshes: batch");
this->meshInstances.clear();
this->meshBatches.clear();
for (const MeshDrawItem& item : this->meshDrawItems) {
auto index = static_cast<u32>(this->meshInstances.size());
this->meshInstances.push_back(item.instance);
if (this->meshBatches.empty() ||
this->meshBatches.back().mesh != item.mesh) {
this->meshBatches.push_back({item.mesh, index, 0});
}
this->meshBatches.back().instanceCount++;
}
}
TracyPlot("mesh instances", static_cast<int64_t>(this->meshInstances.size()));
TracyPlot("mesh batches", static_cast<int64_t>(this->meshBatches.size()));
this->meshRenderSystem.render(commandBuffer, viewProjection,
this->meshInstances, this->meshBatches);
} }
void SceneRenderer::renderSprites(vk::CommandBuffer commandBuffer, void SceneRenderer::renderSprites(vk::CommandBuffer commandBuffer,
const Scene& scene, const Scene& scene, const glm::mat4& view,
const glm::mat4& viewProjection) { const glm::mat4& viewProjection) {
ZoneScoped;
this->spriteDrawItems.clear(); this->spriteDrawItems.clear();
auto view = {
scene.registry.view<const TransformComponent, const SpriteComponent>(); ZoneScopedN("sprites: gather");
for (auto [entity, transform, sprite] : view.each()) { auto sprites =
TextureHandle texture = this->resources.texturePool.isAlive(sprite.texture) scene.registry.view<const TransformComponent, const SpriteComponent>();
? sprite.texture for (auto [entity, transform, sprite] : sprites.each()) {
: this->resources.whiteTexture; TextureHandle texture =
this->resources.texturePool.isAlive(sprite.texture)
? sprite.texture
: this->resources.whiteTexture;
this->spriteDrawItems.push_back( // Distance in front of the camera, which looks down view-space -Z.
{sprite.sortOrder, float depth = -(view * glm::vec4{transform.position, 1.f}).z;
transform.position.z,
texture,
{sprite.getModel(transform), sprite.color, sprite.uvRect}});
}
// Texture is the last key so that sprites whose order doesn't matter this->spriteDrawItems.push_back(
// relative to each other still end up in as few batches as possible. {sprite.sortOrder,
std::ranges::sort(this->spriteDrawItems, {}, [](const SpriteDrawItem& item) { depth,
return std::tuple{item.sortOrder, item.depth, item.texture}; texture,
}); {sprite.getModel(transform), sprite.color, sprite.uvRect}});
this->spriteInstances.clear();
this->spriteBatches.clear();
for (const SpriteDrawItem& item : this->spriteDrawItems) {
auto index = static_cast<u32>(this->spriteInstances.size());
this->spriteInstances.push_back(item.instance);
if (this->spriteBatches.empty() ||
this->spriteBatches.back().texture != item.texture) {
this->spriteBatches.push_back({item.texture, index, 0});
} }
this->spriteBatches.back().instanceCount++;
} }
{
ZoneScopedN("sprites: sort");
// Within a sortOrder, back to front, so nearer sprites blend over farther
// ones. Texture is the last key so that sprites whose order doesn't
// matter relative to each other still end up in as few batches as
// possible.
std::ranges::sort(
this->spriteDrawItems, {}, [](const SpriteDrawItem& item) {
return std::tuple{item.sortOrder, -item.depth, item.texture};
});
}
{
ZoneScopedN("sprites: batch");
this->spriteInstances.clear();
this->spriteBatches.clear();
for (const SpriteDrawItem& item : this->spriteDrawItems) {
auto index = static_cast<u32>(this->spriteInstances.size());
this->spriteInstances.push_back(item.instance);
if (this->spriteBatches.empty() ||
this->spriteBatches.back().texture != item.texture) {
this->spriteBatches.push_back({item.texture, index, 0});
}
this->spriteBatches.back().instanceCount++;
}
}
TracyPlot("sprite instances",
static_cast<int64_t>(this->spriteInstances.size()));
TracyPlot("sprite batches", static_cast<int64_t>(this->spriteBatches.size()));
this->spriteRenderSystem.render(commandBuffer, viewProjection, this->spriteRenderSystem.render(commandBuffer, viewProjection,
this->spriteInstances, this->spriteBatches); this->spriteInstances, this->spriteBatches);
} }
+16 -3
View File
@@ -4,6 +4,7 @@
#include <sophia/scene_view.hpp> #include <sophia/scene_view.hpp>
#include <vector> #include <vector>
#include "renderer/mesh_render_system.hpp"
#include "renderer/sprite_render_system.hpp" #include "renderer/sprite_render_system.hpp"
#include "resources/resource_registry.hpp" #include "resources/resource_registry.hpp"
@@ -27,27 +28,39 @@ class SceneRenderer {
// Records `sceneView` as seen through `camera` (an entity of the view's // Records `sceneView` as seen through `camera` (an entity of the view's
// scene with a CameraComponent) into `commandBuffer`. Rendering into the // scene with a CameraComponent) into `commandBuffer`. Rendering into the
// view's target must already have begun. // view's target, which is `extent` in size, must already have begun.
void render(vk::CommandBuffer commandBuffer, const SceneView& sceneView, void render(vk::CommandBuffer commandBuffer, const SceneView& sceneView,
Entity camera); Entity camera, vk::Extent2D extent);
private: private:
struct MeshDrawItem {
MeshHandle mesh;
MeshRenderSystem::Instance instance;
};
struct SpriteDrawItem { struct SpriteDrawItem {
s32 sortOrder; s32 sortOrder;
// Distance in front of the camera.
float depth; float depth;
TextureHandle texture; TextureHandle texture;
SpriteRenderSystem::Instance instance; SpriteRenderSystem::Instance instance;
}; };
void renderMeshes(vk::CommandBuffer commandBuffer, const Scene& scene,
const glm::mat4& viewProjection);
void renderSprites(vk::CommandBuffer commandBuffer, const Scene& scene, void renderSprites(vk::CommandBuffer commandBuffer, const Scene& scene,
const glm::mat4& viewProjection); const glm::mat4& view, const glm::mat4& viewProjection);
const SceneRegistry& sceneRegistry; const SceneRegistry& sceneRegistry;
ResourceRegistry& resources; ResourceRegistry& resources;
MeshRenderSystem meshRenderSystem;
SpriteRenderSystem spriteRenderSystem; SpriteRenderSystem spriteRenderSystem;
// Reused across frames to avoid reallocating. // Reused across frames to avoid reallocating.
std::vector<MeshDrawItem> meshDrawItems;
std::vector<MeshRenderSystem::Instance> meshInstances;
std::vector<MeshRenderSystem::Batch> meshBatches;
std::vector<SpriteDrawItem> spriteDrawItems; std::vector<SpriteDrawItem> spriteDrawItems;
std::vector<SpriteRenderSystem::Instance> spriteInstances; std::vector<SpriteRenderSystem::Instance> spriteInstances;
std::vector<SpriteRenderSystem::Batch> spriteBatches; std::vector<SpriteRenderSystem::Batch> spriteBatches;
+15 -10
View File
@@ -16,11 +16,12 @@ constexpr u32 VERTICES_PER_SPRITE = 6;
} // namespace } // namespace
SpriteRenderSystem::SpriteRenderSystem(Device& device, vk::Format colorFormat, SpriteRenderSystem::SpriteRenderSystem(Device& device, vk::Format colorFormat,
vk::Format depthFormat,
ResourceRegistry& resources) ResourceRegistry& resources)
: device{device}, resources{resources} { : device{device}, resources{resources} {
createDescriptors(); createDescriptors();
createPipelineLayout(); createPipelineLayout();
createPipeline(colorFormat); createPipeline(colorFormat, depthFormat);
createInstanceBuffers(); createInstanceBuffers();
} }
@@ -147,7 +148,8 @@ void SpriteRenderSystem::createPipelineLayout() {
} }
} }
void SpriteRenderSystem::createPipeline(vk::Format colorFormat) { void SpriteRenderSystem::createPipeline(vk::Format colorFormat,
vk::Format depthFormat) {
auto vertexShader = Shader::Builder() auto vertexShader = Shader::Builder()
.fromSPIRV(shaders::sprite_vert_spv) .fromSPIRV(shaders::sprite_vert_spv)
.setStage(ShaderStage::VERTEX) .setStage(ShaderStage::VERTEX)
@@ -172,14 +174,17 @@ void SpriteRenderSystem::createPipeline(vk::Format colorFormat) {
attributes.push_back({5, 0, vk::Format::eR32G32B32A32Sfloat, attributes.push_back({5, 0, vk::Format::eR32G32B32A32Sfloat,
static_cast<u32>(offsetof(Instance, uvRect))}); static_cast<u32>(offsetof(Instance, uvRect))});
this->graphicsPipeline = GraphicsPipeline::Builder(this->device) this->graphicsPipeline =
.setVertexShader(*vertexShader) GraphicsPipeline::Builder(this->device)
.setFragmentShader(*fragmentShader) .setVertexShader(*vertexShader)
.setPipelineLayout(this->pipelineLayout.get()) .setFragmentShader(*fragmentShader)
.setColorAttachmentFormats({colorFormat}) .setPipelineLayout(this->pipelineLayout.get())
.setVertexInput(bindings, attributes) .setColorAttachmentFormats({colorFormat})
.setAlphaBlending() .setDepthAttachmentFormat(depthFormat)
.build(); .setVertexInput(bindings, attributes)
.setAlphaBlending()
.setDepthTest(true, false, vk::CompareOp::eLessOrEqual)
.build();
} }
void SpriteRenderSystem::createInstanceBuffers() { void SpriteRenderSystem::createInstanceBuffers() {
+4 -2
View File
@@ -20,6 +20,8 @@ namespace sophia {
/** /**
* Draws batches of textured quads. Knows nothing about scenes: callers hand * Draws batches of textured quads. Knows nothing about scenes: callers hand
* it instances already in draw order, grouped into runs that share a texture. * it instances already in draw order, grouped into runs that share a texture.
* Sprites are depth tested, so opaque geometry in front hides them, but
* don't write depth, since they blend.
* *
* Instances are written to a per-frame host-visible buffer, so every * Instances are written to a per-frame host-visible buffer, so every
* render() call within one frame appends to it; call beginFrame() once per * render() call within one frame appends to it; call beginFrame() once per
@@ -47,7 +49,7 @@ class SpriteRenderSystem {
SpriteRenderSystem& operator=(const SpriteRenderSystem&) = delete; SpriteRenderSystem& operator=(const SpriteRenderSystem&) = delete;
SpriteRenderSystem(Device& device, vk::Format colorFormat, SpriteRenderSystem(Device& device, vk::Format colorFormat,
ResourceRegistry& resources); vk::Format depthFormat, ResourceRegistry& resources);
~SpriteRenderSystem(); ~SpriteRenderSystem();
void beginFrame(u32 frameIndex); void beginFrame(u32 frameIndex);
@@ -68,7 +70,7 @@ class SpriteRenderSystem {
void createDescriptors(); void createDescriptors();
void createPipelineLayout(); void createPipelineLayout();
void createPipeline(vk::Format colorFormat); void createPipeline(vk::Format colorFormat, vk::Format depthFormat);
void createInstanceBuffers(); void createInstanceBuffers();
vk::DescriptorSet getDescriptorSet(TextureHandle handle); vk::DescriptorSet getDescriptorSet(TextureHandle handle);
+27 -28
View File
@@ -5,35 +5,32 @@
namespace sophia { namespace sophia {
std::vector<vk::VertexInputBindingDescription> std::vector<vk::VertexInputBindingDescription> Mesh::getBindingDescriptions() {
Mesh::Vertex::getBindingDescriptions() { return {{0, sizeof(MeshVertex), vk::VertexInputRate::eVertex}};
std::vector<vk::VertexInputBindingDescription> bindingDescriptions(1);
bindingDescriptions[0].binding = 0;
bindingDescriptions[0].stride = sizeof(Vertex);
bindingDescriptions[0].inputRate = vk::VertexInputRate::eVertex;
return bindingDescriptions;
} }
std::vector<vk::VertexInputAttributeDescription> std::vector<vk::VertexInputAttributeDescription>
Mesh::Vertex::getAttributeDescriptions() { Mesh::getAttributeDescriptions() {
std::vector<vk::VertexInputAttributeDescription> attributeDescriptions{}; return {
{0, 0, vk::Format::eR32G32B32Sfloat, offsetof(MeshVertex, position)},
attributeDescriptions.push_back( {1, 0, vk::Format::eR32G32B32Sfloat, offsetof(MeshVertex, normal)},
{0, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, position)}); {2, 0, vk::Format::eR32G32Sfloat, offsetof(MeshVertex, uv)},
attributeDescriptions.push_back( {3, 0, vk::Format::eR32G32B32A32Sfloat, offsetof(MeshVertex, color)},
{1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color)}); };
// attributeDescriptions.push_back(
// {2, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, normal)});
// attributeDescriptions.push_back(
// {3, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, uv)});
return attributeDescriptions;
} }
Mesh::Mesh(Device& device, const Builder& builder) : device{device} { Mesh::Mesh(Device& device, const MeshData& data) : device{device} {
createVertexBuffers(builder.vertices); if (!data.vertices.empty()) {
createIndexBuffers(builder.indices); this->bounds = {data.vertices.front().position,
data.vertices.front().position};
for (const MeshVertex& vertex : data.vertices) {
this->bounds.min = glm::min(this->bounds.min, vertex.position);
this->bounds.max = glm::max(this->bounds.max, vertex.position);
}
}
createVertexBuffers(data.vertices);
createIndexBuffers(data.indices);
} }
Mesh::~Mesh() = default; Mesh::~Mesh() = default;
@@ -49,15 +46,17 @@ void Mesh::bind(vk::CommandBuffer commandBuffer) const {
} }
} }
void Mesh::draw(vk::CommandBuffer commandBuffer) const { void Mesh::draw(vk::CommandBuffer commandBuffer, u32 instanceCount,
u32 firstInstance) const {
if (this->hasIndexBuffer) { if (this->hasIndexBuffer) {
commandBuffer.drawIndexed(this->indexCount, 1, 0, 0, 0); commandBuffer.drawIndexed(this->indexCount, instanceCount, 0, 0,
firstInstance);
} else { } else {
commandBuffer.draw(this->vertexCount, 1, 0, 0); commandBuffer.draw(this->vertexCount, instanceCount, 0, firstInstance);
} }
} }
void Mesh::createVertexBuffers(const std::vector<Vertex>& vertices) { void Mesh::createVertexBuffers(const std::vector<MeshVertex>& vertices) {
this->vertexCount = static_cast<u32>(vertices.size()); this->vertexCount = static_cast<u32>(vertices.size());
SOPHIA_ASSERT(this->vertexCount >= 3, "vertex count must be at least 3"); SOPHIA_ASSERT(this->vertexCount >= 3, "vertex count must be at least 3");
+15 -27
View File
@@ -1,8 +1,10 @@
#pragma once #pragma once
#include <memory> #include <memory>
#include <sophia/aabb.hpp>
#include <sophia/handles.hpp> #include <sophia/handles.hpp>
#include <sophia/math.hpp> #include <sophia/math.hpp>
#include <sophia/mesh_data.hpp>
#include <sophia/types.hpp> #include <sophia/types.hpp>
#include <vector> #include <vector>
@@ -13,47 +15,33 @@ namespace sophia {
class Mesh { class Mesh {
public: public:
struct Vertex { // Binding 0 (per vertex, MeshVertex) for pipelines that draw meshes.
glm::vec2 position{}; static std::vector<vk::VertexInputBindingDescription>
glm::vec3 color{}; getBindingDescriptions();
// glm::vec3 normal{}; static std::vector<vk::VertexInputAttributeDescription>
// glm::vec2 uv{}; getAttributeDescriptions();
static std::vector<vk::VertexInputBindingDescription> Mesh(Device& device, const MeshData& data);
getBindingDescriptions();
static std::vector<vk::VertexInputAttributeDescription>
getAttributeDescriptions();
// bool operator==(const Vertex& other) const {
// return position == other.position && color == other.color;
// }
};
struct Builder {
std::vector<Vertex> vertices{};
std::vector<uint32_t> indices{};
// void loadModel(const std::string& filepath);
};
Mesh(Device& device, const Mesh::Builder& builder);
~Mesh(); ~Mesh();
Mesh(const Mesh&) = delete; Mesh(const Mesh&) = delete;
Mesh& operator=(const Mesh&) = delete; Mesh& operator=(const Mesh&) = delete;
// static std::unique_ptr<Mesh> createMeshFromFile( // Bounds of the vertices, in the mesh's own space.
// Device& device, const std::string& filepath); const AABB& getBounds() const { return this->bounds; }
void bind(vk::CommandBuffer commandBuffer) const; void bind(vk::CommandBuffer commandBuffer) const;
void draw(vk::CommandBuffer commandBuffer) const; void draw(vk::CommandBuffer commandBuffer, u32 instanceCount = 1,
u32 firstInstance = 0) const;
private: private:
void createVertexBuffers(const std::vector<Vertex>& vertices); void createVertexBuffers(const std::vector<MeshVertex>& vertices);
void createIndexBuffers(const std::vector<u32>& indices); void createIndexBuffers(const std::vector<u32>& indices);
Device& device; Device& device;
AABB bounds;
std::unique_ptr<Buffer> vertexBuffer; std::unique_ptr<Buffer> vertexBuffer;
u32 vertexCount; u32 vertexCount;
@@ -5,10 +5,16 @@
namespace sophia { namespace sophia {
RenderTargetPool::RenderTargetPool(Device& device, vk::Format format) RenderTargetPool::RenderTargetPool(Device& device, vk::Format format)
: device{device}, format{format} {} : device{device},
format{format},
// Depth-only formats, so the images have no stencil aspect to track.
depthFormat{device.findSupportedFormat(
{vk::Format::eD32Sfloat, vk::Format::eX8D24UnormPack32},
vk::ImageTiling::eOptimal,
vk::FormatFeatureFlagBits::eDepthStencilAttachment)} {}
RenderTargetHandle RenderTargetPool::create(u32 width, u32 height) { RenderTargetHandle RenderTargetPool::create(u32 width, u32 height) {
return pool.create(device, format, width, height); return pool.create(device, format, depthFormat, width, height);
} }
void RenderTargetPool::release(RenderTargetHandle handle) { void RenderTargetPool::release(RenderTargetHandle handle) {
@@ -24,11 +24,15 @@ class RenderTargetPool {
bool isAlive(RenderTargetHandle handle) const { return pool.isAlive(handle); } bool isAlive(RenderTargetHandle handle) const { return pool.isAlive(handle); }
vk::Format getFormat() const { return format; }
vk::Format getDepthFormat() const { return depthFormat; }
void applyPendingResizes(); void applyPendingResizes();
private: private:
Device& device; Device& device;
vk::Format format; vk::Format format;
vk::Format depthFormat;
Pool<RenderTarget> pool{"RenderTarget"}; Pool<RenderTarget> pool{"RenderTarget"};
}; };
+55
View File
@@ -0,0 +1,55 @@
#include <array>
#include <sophia/primitives.hpp>
namespace sophia::primitives {
MeshData cube(float size, vec4 color) {
struct Face {
vec3 normal;
// Axes across the face, with u x v = normal, so corners taken
// counter-clockwise in (u, v) wind counter-clockwise seen from outside.
vec3 u;
vec3 v;
};
constexpr std::array<Face, 6> FACES{{
{{1.f, 0.f, 0.f}, {0.f, 0.f, -1.f}, {0.f, 1.f, 0.f}},
{{-1.f, 0.f, 0.f}, {0.f, 0.f, 1.f}, {0.f, 1.f, 0.f}},
{{0.f, 1.f, 0.f}, {1.f, 0.f, 0.f}, {0.f, 0.f, -1.f}},
{{0.f, -1.f, 0.f}, {1.f, 0.f, 0.f}, {0.f, 0.f, 1.f}},
{{0.f, 0.f, 1.f}, {1.f, 0.f, 0.f}, {0.f, 1.f, 0.f}},
{{0.f, 0.f, -1.f}, {-1.f, 0.f, 0.f}, {0.f, 1.f, 0.f}},
}};
constexpr std::array<vec2, 4> CORNERS{vec2{-1.f, -1.f}, vec2{1.f, -1.f},
vec2{1.f, 1.f}, vec2{-1.f, 1.f}};
float half = size / 2.f;
MeshData data;
data.vertices.reserve(FACES.size() * CORNERS.size());
data.indices.reserve(FACES.size() * 6);
for (const Face& face : FACES) {
auto base = static_cast<u32>(data.vertices.size());
for (vec2 corner : CORNERS) {
MeshVertex vertex;
vertex.position =
(face.normal + corner.x * face.u + corner.y * face.v) * half;
vertex.normal = face.normal;
// Texture v runs down while the face's v runs up.
vertex.uv = {(corner.x + 1.f) / 2.f, (1.f - corner.y) / 2.f};
vertex.color = color;
data.vertices.push_back(vertex);
}
for (u32 index : {0u, 1u, 2u, 2u, 3u, 0u}) {
data.indices.push_back(base + index);
}
}
return data;
}
} // namespace sophia::primitives
+13 -4
View File
@@ -4,9 +4,9 @@
namespace sophia { namespace sophia {
RenderTarget::RenderTarget(Device& device, vk::Format format, u32 width, RenderTarget::RenderTarget(Device& device, vk::Format format,
u32 height) vk::Format depthFormat, u32 width, u32 height)
: device{device}, format{format} { : device{device}, format{format}, depthFormat{depthFormat} {
create(std::max(width, 1u), std::max(height, 1u)); create(std::max(width, 1u), std::max(height, 1u));
} }
@@ -46,8 +46,17 @@ void RenderTarget::create(u32 width, u32 height) {
.asColorAttachment() .asColorAttachment()
.withView() .withView()
.build(); .build();
this->depthImage = Image::Builder(this->device)
.setExtent(width, height)
.asDepthAttachment()
.setFormat(this->depthFormat)
.withView()
.build();
} }
void RenderTarget::destroy() { this->image.reset(); } void RenderTarget::destroy() {
this->image.reset();
this->depthImage.reset();
}
} // namespace sophia } // namespace sophia
+7 -3
View File
@@ -11,20 +11,22 @@
namespace sophia { namespace sophia {
/** /**
* A place rendering can go. Currently always backed by an offscreen color * A place rendering can go: an offscreen color image, plus a depth image of
* image. * the same size.
* *
* Owned by the ResourceRegistry and addressed through a RenderTargetHandle. * Owned by the ResourceRegistry and addressed through a RenderTargetHandle.
*/ */
class RenderTarget { class RenderTarget {
public: public:
RenderTarget(Device& device, vk::Format format, u32 width, u32 height); RenderTarget(Device& device, vk::Format format, vk::Format depthFormat,
u32 width, u32 height);
~RenderTarget(); ~RenderTarget();
RenderTarget(const RenderTarget&) = delete; RenderTarget(const RenderTarget&) = delete;
RenderTarget& operator=(const RenderTarget&) = delete; RenderTarget& operator=(const RenderTarget&) = delete;
Image& getImage() const { return *this->image; } Image& getImage() const { return *this->image; }
Image& getDepthImage() const { return *this->depthImage; }
vk::Extent2D getExtent() const; vk::Extent2D getExtent() const;
u32 getVersion() const { return this->version; } u32 getVersion() const { return this->version; }
@@ -43,8 +45,10 @@ class RenderTarget {
Device& device; Device& device;
vk::Format format; vk::Format format;
vk::Format depthFormat;
std::unique_ptr<Image> image; std::unique_ptr<Image> image;
std::unique_ptr<Image> depthImage;
std::optional<vk::Extent2D> pendingExtent; std::optional<vk::Extent2D> pendingExtent;
u32 version = 0; u32 version = 0;
+14 -12
View File
@@ -14,7 +14,7 @@ MeshHandle ResourceRegistryRef::load<Mesh>(const std::string& path) {
(void)path; (void)path;
SOPHIA_ASSERT(false, SOPHIA_ASSERT(false,
"Mesh::load(path) is not implemented yet - no model file " "Mesh::load(path) is not implemented yet - no model file "
"format is supported; build one via Mesh::Builder for now"); "format is supported; build a MeshData instead");
return MeshHandle{}; return MeshHandle{};
} }
@@ -40,6 +40,8 @@ TextureHandle ResourceRegistryRef::load<Texture>(const std::string& path) {
template <> template <>
void ResourceRegistryRef::release<Mesh>(MeshHandle handle) { void ResourceRegistryRef::release<Mesh>(MeshHandle handle) {
// Frames in flight may still be drawing it.
registry->device.waitIdle();
registry->meshPool.release(handle); registry->meshPool.release(handle);
} }
@@ -57,17 +59,9 @@ void ResourceRegistryRef::release<Texture>(TextureHandle handle) {
registry->texturePool.release(handle); registry->texturePool.release(handle);
} }
MeshHandle ResourceRegistryRef::createResource( MeshHandle ResourceRegistryRef::createResource(std::type_identity<Mesh>,
std::type_identity<Mesh>, std::vector<CanvasVertex> vertices, const MeshData& data) {
std::vector<u32> indices) { return registry->meshPool.create(registry->device, data);
Mesh::Builder builder{};
builder.vertices.reserve(vertices.size());
for (const CanvasVertex& vertex : vertices) {
builder.vertices.push_back({vertex.position, vertex.color});
}
builder.indices = std::move(indices);
return registry->meshPool.create(registry->device, builder);
} }
RenderTargetHandle ResourceRegistryRef::createResource( RenderTargetHandle ResourceRegistryRef::createResource(
@@ -92,6 +86,14 @@ glm::vec2 ResourceRegistryRef::getExtent(RenderTargetHandle handle) const {
return {static_cast<float>(extent.width), static_cast<float>(extent.height)}; return {static_cast<float>(extent.width), static_cast<float>(extent.height)};
} }
AABB ResourceRegistryRef::getBounds(MeshHandle handle) const {
return registry->meshPool.get(handle).getBounds();
}
bool ResourceRegistryRef::isAlive(MeshHandle handle) const {
return registry->meshPool.isAlive(handle);
}
bool ResourceRegistryRef::isAlive(RenderTargetHandle handle) const { bool ResourceRegistryRef::isAlive(RenderTargetHandle handle) const {
return registry->renderTargetPool.isAlive(handle); return registry->renderTargetPool.isAlive(handle);
} }
@@ -0,0 +1,9 @@
#include "scene/scene_render_packet_builder.hpp"
namespace sophia {
void SceneRenderPacketBuilder::build(SceneHandle sceneHandle,
RenderTargetHandle renderTargetHandle,
RenderPacket& renderPacket) {}
} // namespace sophia
@@ -0,0 +1,16 @@
#pragma once
#include <sophia/handles.hpp>
#include "renderer/renderer.hpp"
namespace sophia {
class SceneRenderPacketBuilder {
public:
static void build(SceneHandle sceneHandle,
RenderTargetHandle renderTargetHandle,
RenderPacket& renderPacket);
};
} // namespace sophia
+60 -89
View File
@@ -6,95 +6,66 @@
namespace sophia::shaders { namespace sophia::shaders {
inline constexpr std::array<uint32_t, 355> fullscreen_vert_spv = { inline constexpr std::array<uint32_t, 355> fullscreen_vert_spv = {
0x07230203, 0x00010000, 0x000d000b, 0x00000032, 0x07230203, 0x00010000, 0x000d000b, 0x00000032, 0x00000000, 0x00020011,
0x00000000, 0x00020011, 0x00000001, 0x0006000b, 0x00000001, 0x0006000b, 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e,
0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000000, 0x0003000e, 0x00000000, 0x00000001, 0x0008000f, 0x00000000,
0x00000000, 0x0003000e, 0x00000000, 0x00000001, 0x00000004, 0x6e69616d, 0x00000000, 0x00000018, 0x00000022, 0x0000002c,
0x0008000f, 0x00000000, 0x00000004, 0x6e69616d, 0x00030003, 0x00000002, 0x000001c2, 0x000a0004, 0x475f4c47, 0x4c474f4f,
0x00000000, 0x00000018, 0x00000022, 0x0000002c, 0x70635f45, 0x74735f70, 0x5f656c79, 0x656e696c, 0x7269645f, 0x69746365,
0x00030003, 0x00000002, 0x000001c2, 0x000a0004, 0x00006576, 0x00080004, 0x475f4c47, 0x4c474f4f, 0x6e695f45, 0x64756c63,
0x475f4c47, 0x4c474f4f, 0x70635f45, 0x74735f70, 0x69645f65, 0x74636572, 0x00657669, 0x00040005, 0x00000004, 0x6e69616d,
0x5f656c79, 0x656e696c, 0x7269645f, 0x69746365, 0x00000000, 0x00050005, 0x0000000c, 0x69736f70, 0x6e6f6974, 0x00000073,
0x00006576, 0x00080004, 0x475f4c47, 0x4c474f4f, 0x00030005, 0x00000015, 0x00736f70, 0x00060005, 0x00000018, 0x565f6c67,
0x6e695f45, 0x64756c63, 0x69645f65, 0x74636572, 0x65747265, 0x646e4978, 0x00007865, 0x00060005, 0x00000020, 0x505f6c67,
0x00657669, 0x00040005, 0x00000004, 0x6e69616d, 0x65567265, 0x78657472, 0x00000000, 0x00060006, 0x00000020, 0x00000000,
0x00000000, 0x00050005, 0x0000000c, 0x69736f70, 0x505f6c67, 0x7469736f, 0x006e6f69, 0x00070006, 0x00000020, 0x00000001,
0x6e6f6974, 0x00000073, 0x00030005, 0x00000015, 0x505f6c67, 0x746e696f, 0x657a6953, 0x00000000, 0x00070006, 0x00000020,
0x00736f70, 0x00060005, 0x00000018, 0x565f6c67, 0x00000002, 0x435f6c67, 0x4470696c, 0x61747369, 0x0065636e, 0x00070006,
0x65747265, 0x646e4978, 0x00007865, 0x00060005, 0x00000020, 0x00000003, 0x435f6c67, 0x446c6c75, 0x61747369, 0x0065636e,
0x00000020, 0x505f6c67, 0x65567265, 0x78657472, 0x00030005, 0x00000022, 0x00000000, 0x00030005, 0x0000002c, 0x00007675,
0x00000000, 0x00060006, 0x00000020, 0x00000000, 0x00040047, 0x00000018, 0x0000000b, 0x0000002a, 0x00030047, 0x00000020,
0x505f6c67, 0x7469736f, 0x006e6f69, 0x00070006, 0x00000002, 0x00050048, 0x00000020, 0x00000000, 0x0000000b, 0x00000000,
0x00000020, 0x00000001, 0x505f6c67, 0x746e696f, 0x00050048, 0x00000020, 0x00000001, 0x0000000b, 0x00000001, 0x00050048,
0x657a6953, 0x00000000, 0x00070006, 0x00000020, 0x00000020, 0x00000002, 0x0000000b, 0x00000003, 0x00050048, 0x00000020,
0x00000002, 0x435f6c67, 0x4470696c, 0x61747369, 0x00000003, 0x0000000b, 0x00000004, 0x00040047, 0x0000002c, 0x0000001e,
0x0065636e, 0x00070006, 0x00000020, 0x00000003, 0x00000000, 0x00020013, 0x00000002, 0x00030021, 0x00000003, 0x00000002,
0x435f6c67, 0x446c6c75, 0x61747369, 0x0065636e, 0x00030016, 0x00000006, 0x00000020, 0x00040017, 0x00000007, 0x00000006,
0x00030005, 0x00000022, 0x00000000, 0x00030005, 0x00000002, 0x00040015, 0x00000008, 0x00000020, 0x00000000, 0x0004002b,
0x0000002c, 0x00007675, 0x00040047, 0x00000018, 0x00000008, 0x00000009, 0x00000006, 0x0004001c, 0x0000000a, 0x00000007,
0x0000000b, 0x0000002a, 0x00030047, 0x00000020, 0x00000009, 0x00040020, 0x0000000b, 0x00000006, 0x0000000a, 0x0004003b,
0x00000002, 0x00050048, 0x00000020, 0x00000000, 0x0000000b, 0x0000000c, 0x00000006, 0x0004002b, 0x00000006, 0x0000000d,
0x0000000b, 0x00000000, 0x00050048, 0x00000020, 0xbf800000, 0x0005002c, 0x00000007, 0x0000000e, 0x0000000d, 0x0000000d,
0x00000001, 0x0000000b, 0x00000001, 0x00050048, 0x0004002b, 0x00000006, 0x0000000f, 0x3f800000, 0x0005002c, 0x00000007,
0x00000020, 0x00000002, 0x0000000b, 0x00000003, 0x00000010, 0x0000000f, 0x0000000d, 0x0005002c, 0x00000007, 0x00000011,
0x00050048, 0x00000020, 0x00000003, 0x0000000b, 0x0000000d, 0x0000000f, 0x0005002c, 0x00000007, 0x00000012, 0x0000000f,
0x00000004, 0x00040047, 0x0000002c, 0x0000001e, 0x0000000f, 0x0009002c, 0x0000000a, 0x00000013, 0x0000000e, 0x00000010,
0x00000000, 0x00020013, 0x00000002, 0x00030021, 0x00000011, 0x00000011, 0x00000010, 0x00000012, 0x00040020, 0x00000014,
0x00000003, 0x00000002, 0x00030016, 0x00000006, 0x00000007, 0x00000007, 0x00040015, 0x00000016, 0x00000020, 0x00000001,
0x00000020, 0x00040017, 0x00000007, 0x00000006, 0x00040020, 0x00000017, 0x00000001, 0x00000016, 0x0004003b, 0x00000017,
0x00000002, 0x00040015, 0x00000008, 0x00000020, 0x00000018, 0x00000001, 0x00040020, 0x0000001a, 0x00000006, 0x00000007,
0x00000000, 0x0004002b, 0x00000008, 0x00000009, 0x00040017, 0x0000001d, 0x00000006, 0x00000004, 0x0004002b, 0x00000008,
0x00000006, 0x0004001c, 0x0000000a, 0x00000007, 0x0000001e, 0x00000001, 0x0004001c, 0x0000001f, 0x00000006, 0x0000001e,
0x00000009, 0x00040020, 0x0000000b, 0x00000006, 0x0006001e, 0x00000020, 0x0000001d, 0x00000006, 0x0000001f, 0x0000001f,
0x0000000a, 0x0004003b, 0x0000000b, 0x0000000c, 0x00040020, 0x00000021, 0x00000003, 0x00000020, 0x0004003b, 0x00000021,
0x00000006, 0x0004002b, 0x00000006, 0x0000000d, 0x00000022, 0x00000003, 0x0004002b, 0x00000016, 0x00000023, 0x00000000,
0xbf800000, 0x0005002c, 0x00000007, 0x0000000e, 0x0004002b, 0x00000006, 0x00000025, 0x00000000, 0x00040020, 0x00000029,
0x0000000d, 0x0000000d, 0x0004002b, 0x00000006, 0x00000003, 0x0000001d, 0x00040020, 0x0000002b, 0x00000003, 0x00000007,
0x0000000f, 0x3f800000, 0x0005002c, 0x00000007, 0x0004003b, 0x0000002b, 0x0000002c, 0x00000003, 0x0004002b, 0x00000006,
0x00000010, 0x0000000f, 0x0000000d, 0x0005002c, 0x0000002e, 0x3f000000, 0x00050036, 0x00000002, 0x00000004, 0x00000000,
0x00000007, 0x00000011, 0x0000000d, 0x0000000f, 0x00000003, 0x000200f8, 0x00000005, 0x0004003b, 0x00000014, 0x00000015,
0x0005002c, 0x00000007, 0x00000012, 0x0000000f, 0x00000007, 0x0003003e, 0x0000000c, 0x00000013, 0x0004003d, 0x00000016,
0x0000000f, 0x0009002c, 0x0000000a, 0x00000013, 0x00000019, 0x00000018, 0x00050041, 0x0000001a, 0x0000001b, 0x0000000c,
0x0000000e, 0x00000010, 0x00000011, 0x00000011, 0x00000019, 0x0004003d, 0x00000007, 0x0000001c, 0x0000001b, 0x0003003e,
0x00000010, 0x00000012, 0x00040020, 0x00000014, 0x00000015, 0x0000001c, 0x0004003d, 0x00000007, 0x00000024, 0x00000015,
0x00000007, 0x00000007, 0x00040015, 0x00000016, 0x00050051, 0x00000006, 0x00000026, 0x00000024, 0x00000000, 0x00050051,
0x00000020, 0x00000001, 0x00040020, 0x00000017, 0x00000006, 0x00000027, 0x00000024, 0x00000001, 0x00070050, 0x0000001d,
0x00000001, 0x00000016, 0x0004003b, 0x00000017, 0x00000028, 0x00000026, 0x00000027, 0x00000025, 0x0000000f, 0x00050041,
0x00000018, 0x00000001, 0x00040020, 0x0000001a, 0x00000029, 0x0000002a, 0x00000022, 0x00000023, 0x0003003e, 0x0000002a,
0x00000006, 0x00000007, 0x00040017, 0x0000001d, 0x00000028, 0x0004003d, 0x00000007, 0x0000002d, 0x00000015, 0x0005008e,
0x00000006, 0x00000004, 0x0004002b, 0x00000008, 0x00000007, 0x0000002f, 0x0000002d, 0x0000002e, 0x00050050, 0x00000007,
0x0000001e, 0x00000001, 0x0004001c, 0x0000001f, 0x00000030, 0x0000002e, 0x0000002e, 0x00050081, 0x00000007, 0x00000031,
0x00000006, 0x0000001e, 0x0006001e, 0x00000020, 0x0000002f, 0x00000030, 0x0003003e, 0x0000002c, 0x00000031, 0x000100fd,
0x0000001d, 0x00000006, 0x0000001f, 0x0000001f, 0x00010038,
0x00040020, 0x00000021, 0x00000003, 0x00000020,
0x0004003b, 0x00000021, 0x00000022, 0x00000003,
0x0004002b, 0x00000016, 0x00000023, 0x00000000,
0x0004002b, 0x00000006, 0x00000025, 0x00000000,
0x00040020, 0x00000029, 0x00000003, 0x0000001d,
0x00040020, 0x0000002b, 0x00000003, 0x00000007,
0x0004003b, 0x0000002b, 0x0000002c, 0x00000003,
0x0004002b, 0x00000006, 0x0000002e, 0x3f000000,
0x00050036, 0x00000002, 0x00000004, 0x00000000,
0x00000003, 0x000200f8, 0x00000005, 0x0004003b,
0x00000014, 0x00000015, 0x00000007, 0x0003003e,
0x0000000c, 0x00000013, 0x0004003d, 0x00000016,
0x00000019, 0x00000018, 0x00050041, 0x0000001a,
0x0000001b, 0x0000000c, 0x00000019, 0x0004003d,
0x00000007, 0x0000001c, 0x0000001b, 0x0003003e,
0x00000015, 0x0000001c, 0x0004003d, 0x00000007,
0x00000024, 0x00000015, 0x00050051, 0x00000006,
0x00000026, 0x00000024, 0x00000000, 0x00050051,
0x00000006, 0x00000027, 0x00000024, 0x00000001,
0x00070050, 0x0000001d, 0x00000028, 0x00000026,
0x00000027, 0x00000025, 0x0000000f, 0x00050041,
0x00000029, 0x0000002a, 0x00000022, 0x00000023,
0x0003003e, 0x0000002a, 0x00000028, 0x0004003d,
0x00000007, 0x0000002d, 0x00000015, 0x0005008e,
0x00000007, 0x0000002f, 0x0000002d, 0x0000002e,
0x00050050, 0x00000007, 0x00000030, 0x0000002e,
0x0000002e, 0x00050081, 0x00000007, 0x00000031,
0x0000002f, 0x00000030, 0x0003003e, 0x0000002c,
0x00000031, 0x000100fd, 0x00010038,
}; };
} // namespace sophia::shaders } // namespace sophia::shaders
+9
View File
@@ -0,0 +1,9 @@
#version 450
layout (location = 0) in vec4 fragColor;
layout (location = 0) out vec4 outColor;
void main() {
outColor = vec4(fragColor.rgb, 1.0);
}
+37
View File
@@ -0,0 +1,37 @@
#pragma once
#include <array>
#include <cstdint>
namespace sophia::shaders {
inline constexpr std::array<uint32_t, 151> mesh_frag_spv = {
0x07230203, 0x00010000, 0x000d000b, 0x00000014, 0x00000000, 0x00020011,
0x00000001, 0x0006000b, 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e,
0x00000000, 0x0003000e, 0x00000000, 0x00000001, 0x0007000f, 0x00000004,
0x00000004, 0x6e69616d, 0x00000000, 0x00000009, 0x0000000b, 0x00030010,
0x00000004, 0x00000007, 0x00030003, 0x00000002, 0x000001c2, 0x000a0004,
0x475f4c47, 0x4c474f4f, 0x70635f45, 0x74735f70, 0x5f656c79, 0x656e696c,
0x7269645f, 0x69746365, 0x00006576, 0x00080004, 0x475f4c47, 0x4c474f4f,
0x6e695f45, 0x64756c63, 0x69645f65, 0x74636572, 0x00657669, 0x00040005,
0x00000004, 0x6e69616d, 0x00000000, 0x00050005, 0x00000009, 0x4374756f,
0x726f6c6f, 0x00000000, 0x00050005, 0x0000000b, 0x67617266, 0x6f6c6f43,
0x00000072, 0x00040047, 0x00000009, 0x0000001e, 0x00000000, 0x00040047,
0x0000000b, 0x0000001e, 0x00000000, 0x00020013, 0x00000002, 0x00030021,
0x00000003, 0x00000002, 0x00030016, 0x00000006, 0x00000020, 0x00040017,
0x00000007, 0x00000006, 0x00000004, 0x00040020, 0x00000008, 0x00000003,
0x00000007, 0x0004003b, 0x00000008, 0x00000009, 0x00000003, 0x00040020,
0x0000000a, 0x00000001, 0x00000007, 0x0004003b, 0x0000000a, 0x0000000b,
0x00000001, 0x00040017, 0x0000000c, 0x00000006, 0x00000003, 0x0004002b,
0x00000006, 0x0000000f, 0x3f800000, 0x00050036, 0x00000002, 0x00000004,
0x00000000, 0x00000003, 0x000200f8, 0x00000005, 0x0004003d, 0x00000007,
0x0000000d, 0x0000000b, 0x0008004f, 0x0000000c, 0x0000000e, 0x0000000d,
0x0000000d, 0x00000000, 0x00000001, 0x00000002, 0x00050051, 0x00000006,
0x00000010, 0x0000000e, 0x00000000, 0x00050051, 0x00000006, 0x00000011,
0x0000000e, 0x00000001, 0x00050051, 0x00000006, 0x00000012, 0x0000000e,
0x00000002, 0x00070050, 0x00000007, 0x00000013, 0x00000010, 0x00000011,
0x00000012, 0x0000000f, 0x0003003e, 0x00000009, 0x00000013, 0x000100fd,
0x00010038,
};
} // namespace sophia::shaders
+23
View File
@@ -0,0 +1,23 @@
#version 450
// Per vertex; layout must match MeshVertex. Normal and UV are unused until
// there is lighting and texturing.
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 normal;
layout (location = 2) in vec2 uv;
layout (location = 3) in vec4 color;
// Per instance; layout must match MeshRenderSystem::Instance.
layout (location = 4) in mat4 model;
layout (location = 8) in vec4 instanceColor;
layout (location = 0) out vec4 fragColor;
layout (push_constant) uniform Push {
mat4 viewProjection;
} push;
void main() {
gl_Position = push.viewProjection * model * vec4(position, 1.0);
fragColor = color * instanceColor;
}
+80
View File
@@ -0,0 +1,80 @@
#pragma once
#include <array>
#include <cstdint>
namespace sophia::shaders {
inline constexpr std::array<uint32_t, 411> mesh_vert_spv = {
0x07230203, 0x00010000, 0x000d000b, 0x00000032, 0x00000000, 0x00020011,
0x00000001, 0x0006000b, 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e,
0x00000000, 0x0003000e, 0x00000000, 0x00000001, 0x000d000f, 0x00000000,
0x00000004, 0x6e69616d, 0x00000000, 0x0000000d, 0x00000018, 0x0000001d,
0x00000027, 0x00000029, 0x0000002b, 0x0000002e, 0x00000031, 0x00030003,
0x00000002, 0x000001c2, 0x000a0004, 0x475f4c47, 0x4c474f4f, 0x70635f45,
0x74735f70, 0x5f656c79, 0x656e696c, 0x7269645f, 0x69746365, 0x00006576,
0x00080004, 0x475f4c47, 0x4c474f4f, 0x6e695f45, 0x64756c63, 0x69645f65,
0x74636572, 0x00657669, 0x00040005, 0x00000004, 0x6e69616d, 0x00000000,
0x00060005, 0x0000000b, 0x505f6c67, 0x65567265, 0x78657472, 0x00000000,
0x00060006, 0x0000000b, 0x00000000, 0x505f6c67, 0x7469736f, 0x006e6f69,
0x00070006, 0x0000000b, 0x00000001, 0x505f6c67, 0x746e696f, 0x657a6953,
0x00000000, 0x00070006, 0x0000000b, 0x00000002, 0x435f6c67, 0x4470696c,
0x61747369, 0x0065636e, 0x00070006, 0x0000000b, 0x00000003, 0x435f6c67,
0x446c6c75, 0x61747369, 0x0065636e, 0x00030005, 0x0000000d, 0x00000000,
0x00040005, 0x00000011, 0x68737550, 0x00000000, 0x00070006, 0x00000011,
0x00000000, 0x77656976, 0x6a6f7250, 0x69746365, 0x00006e6f, 0x00040005,
0x00000013, 0x68737570, 0x00000000, 0x00040005, 0x00000018, 0x65646f6d,
0x0000006c, 0x00050005, 0x0000001d, 0x69736f70, 0x6e6f6974, 0x00000000,
0x00050005, 0x00000027, 0x67617266, 0x6f6c6f43, 0x00000072, 0x00040005,
0x00000029, 0x6f6c6f63, 0x00000072, 0x00060005, 0x0000002b, 0x74736e69,
0x65636e61, 0x6f6c6f43, 0x00000072, 0x00040005, 0x0000002e, 0x6d726f6e,
0x00006c61, 0x00030005, 0x00000031, 0x00007675, 0x00030047, 0x0000000b,
0x00000002, 0x00050048, 0x0000000b, 0x00000000, 0x0000000b, 0x00000000,
0x00050048, 0x0000000b, 0x00000001, 0x0000000b, 0x00000001, 0x00050048,
0x0000000b, 0x00000002, 0x0000000b, 0x00000003, 0x00050048, 0x0000000b,
0x00000003, 0x0000000b, 0x00000004, 0x00030047, 0x00000011, 0x00000002,
0x00040048, 0x00000011, 0x00000000, 0x00000005, 0x00050048, 0x00000011,
0x00000000, 0x00000007, 0x00000010, 0x00050048, 0x00000011, 0x00000000,
0x00000023, 0x00000000, 0x00040047, 0x00000018, 0x0000001e, 0x00000004,
0x00040047, 0x0000001d, 0x0000001e, 0x00000000, 0x00040047, 0x00000027,
0x0000001e, 0x00000000, 0x00040047, 0x00000029, 0x0000001e, 0x00000003,
0x00040047, 0x0000002b, 0x0000001e, 0x00000008, 0x00040047, 0x0000002e,
0x0000001e, 0x00000001, 0x00040047, 0x00000031, 0x0000001e, 0x00000002,
0x00020013, 0x00000002, 0x00030021, 0x00000003, 0x00000002, 0x00030016,
0x00000006, 0x00000020, 0x00040017, 0x00000007, 0x00000006, 0x00000004,
0x00040015, 0x00000008, 0x00000020, 0x00000000, 0x0004002b, 0x00000008,
0x00000009, 0x00000001, 0x0004001c, 0x0000000a, 0x00000006, 0x00000009,
0x0006001e, 0x0000000b, 0x00000007, 0x00000006, 0x0000000a, 0x0000000a,
0x00040020, 0x0000000c, 0x00000003, 0x0000000b, 0x0004003b, 0x0000000c,
0x0000000d, 0x00000003, 0x00040015, 0x0000000e, 0x00000020, 0x00000001,
0x0004002b, 0x0000000e, 0x0000000f, 0x00000000, 0x00040018, 0x00000010,
0x00000007, 0x00000004, 0x0003001e, 0x00000011, 0x00000010, 0x00040020,
0x00000012, 0x00000009, 0x00000011, 0x0004003b, 0x00000012, 0x00000013,
0x00000009, 0x00040020, 0x00000014, 0x00000009, 0x00000010, 0x00040020,
0x00000017, 0x00000001, 0x00000010, 0x0004003b, 0x00000017, 0x00000018,
0x00000001, 0x00040017, 0x0000001b, 0x00000006, 0x00000003, 0x00040020,
0x0000001c, 0x00000001, 0x0000001b, 0x0004003b, 0x0000001c, 0x0000001d,
0x00000001, 0x0004002b, 0x00000006, 0x0000001f, 0x3f800000, 0x00040020,
0x00000025, 0x00000003, 0x00000007, 0x0004003b, 0x00000025, 0x00000027,
0x00000003, 0x00040020, 0x00000028, 0x00000001, 0x00000007, 0x0004003b,
0x00000028, 0x00000029, 0x00000001, 0x0004003b, 0x00000028, 0x0000002b,
0x00000001, 0x0004003b, 0x0000001c, 0x0000002e, 0x00000001, 0x00040017,
0x0000002f, 0x00000006, 0x00000002, 0x00040020, 0x00000030, 0x00000001,
0x0000002f, 0x0004003b, 0x00000030, 0x00000031, 0x00000001, 0x00050036,
0x00000002, 0x00000004, 0x00000000, 0x00000003, 0x000200f8, 0x00000005,
0x00050041, 0x00000014, 0x00000015, 0x00000013, 0x0000000f, 0x0004003d,
0x00000010, 0x00000016, 0x00000015, 0x0004003d, 0x00000010, 0x00000019,
0x00000018, 0x00050092, 0x00000010, 0x0000001a, 0x00000016, 0x00000019,
0x0004003d, 0x0000001b, 0x0000001e, 0x0000001d, 0x00050051, 0x00000006,
0x00000020, 0x0000001e, 0x00000000, 0x00050051, 0x00000006, 0x00000021,
0x0000001e, 0x00000001, 0x00050051, 0x00000006, 0x00000022, 0x0000001e,
0x00000002, 0x00070050, 0x00000007, 0x00000023, 0x00000020, 0x00000021,
0x00000022, 0x0000001f, 0x00050091, 0x00000007, 0x00000024, 0x0000001a,
0x00000023, 0x00050041, 0x00000025, 0x00000026, 0x0000000d, 0x0000000f,
0x0003003e, 0x00000026, 0x00000024, 0x0004003d, 0x00000007, 0x0000002a,
0x00000029, 0x0004003d, 0x00000007, 0x0000002c, 0x0000002b, 0x00050085,
0x00000007, 0x0000002d, 0x0000002a, 0x0000002c, 0x0003003e, 0x00000027,
0x0000002d, 0x000100fd, 0x00010038,
};
} // namespace sophia::shaders
+2 -2
View File
@@ -1,9 +1,9 @@
#version 450 #version 450
layout (location = 0) in vec3 fragColor; layout (location = 0) in vec4 fragColor;
layout (location = 0) out vec4 outColor; layout (location = 0) out vec4 outColor;
void main() { void main() {
outColor = vec4(fragColor, 1.0); outColor = fragColor;
} }
@@ -5,31 +5,36 @@
namespace sophia::shaders { namespace sophia::shaders {
inline constexpr std::array<uint32_t, 143> model_render_system_frag_spv = { inline constexpr std::array<uint32_t, 113> model_render_system_frag_spv = {
0x07230203, 0x00010000, 0x000d000b, 0x00000013, 0x00000000, 0x00020011, 0x07230203, 0x00010000, 0x000d000b, 0x0000000d,
0x00000001, 0x0006000b, 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000000, 0x00020011, 0x00000001, 0x0006000b,
0x00000000, 0x0003000e, 0x00000000, 0x00000001, 0x0007000f, 0x00000004, 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e,
0x00000004, 0x6e69616d, 0x00000000, 0x00000009, 0x0000000c, 0x00030010, 0x00000000, 0x0003000e, 0x00000000, 0x00000001,
0x00000004, 0x00000007, 0x00030003, 0x00000002, 0x000001c2, 0x000a0004, 0x0007000f, 0x00000004, 0x00000004, 0x6e69616d,
0x475f4c47, 0x4c474f4f, 0x70635f45, 0x74735f70, 0x5f656c79, 0x656e696c, 0x00000000, 0x00000009, 0x0000000b, 0x00030010,
0x7269645f, 0x69746365, 0x00006576, 0x00080004, 0x475f4c47, 0x4c474f4f, 0x00000004, 0x00000007, 0x00030003, 0x00000002,
0x6e695f45, 0x64756c63, 0x69645f65, 0x74636572, 0x00657669, 0x00040005, 0x000001c2, 0x000a0004, 0x475f4c47, 0x4c474f4f,
0x00000004, 0x6e69616d, 0x00000000, 0x00050005, 0x00000009, 0x4374756f, 0x70635f45, 0x74735f70, 0x5f656c79, 0x656e696c,
0x726f6c6f, 0x00000000, 0x00050005, 0x0000000c, 0x67617266, 0x6f6c6f43, 0x7269645f, 0x69746365, 0x00006576, 0x00080004,
0x00000072, 0x00040047, 0x00000009, 0x0000001e, 0x00000000, 0x00040047, 0x475f4c47, 0x4c474f4f, 0x6e695f45, 0x64756c63,
0x0000000c, 0x0000001e, 0x00000000, 0x00020013, 0x00000002, 0x00030021, 0x69645f65, 0x74636572, 0x00657669, 0x00040005,
0x00000003, 0x00000002, 0x00030016, 0x00000006, 0x00000020, 0x00040017, 0x00000004, 0x6e69616d, 0x00000000, 0x00050005,
0x00000007, 0x00000006, 0x00000004, 0x00040020, 0x00000008, 0x00000003, 0x00000009, 0x4374756f, 0x726f6c6f, 0x00000000,
0x00000007, 0x0004003b, 0x00000008, 0x00000009, 0x00000003, 0x00040017, 0x00050005, 0x0000000b, 0x67617266, 0x6f6c6f43,
0x0000000a, 0x00000006, 0x00000003, 0x00040020, 0x0000000b, 0x00000001, 0x00000072, 0x00040047, 0x00000009, 0x0000001e,
0x0000000a, 0x0004003b, 0x0000000b, 0x0000000c, 0x00000001, 0x0004002b, 0x00000000, 0x00040047, 0x0000000b, 0x0000001e,
0x00000006, 0x0000000e, 0x3f800000, 0x00050036, 0x00000002, 0x00000004, 0x00000000, 0x00020013, 0x00000002, 0x00030021,
0x00000000, 0x00000003, 0x000200f8, 0x00000005, 0x0004003d, 0x0000000a, 0x00000003, 0x00000002, 0x00030016, 0x00000006,
0x0000000d, 0x0000000c, 0x00050051, 0x00000006, 0x0000000f, 0x0000000d, 0x00000020, 0x00040017, 0x00000007, 0x00000006,
0x00000000, 0x00050051, 0x00000006, 0x00000010, 0x0000000d, 0x00000001, 0x00000004, 0x00040020, 0x00000008, 0x00000003,
0x00050051, 0x00000006, 0x00000011, 0x0000000d, 0x00000002, 0x00070050, 0x00000007, 0x0004003b, 0x00000008, 0x00000009,
0x00000007, 0x00000012, 0x0000000f, 0x00000010, 0x00000011, 0x0000000e, 0x00000003, 0x00040020, 0x0000000a, 0x00000001,
0x0003003e, 0x00000009, 0x00000012, 0x000100fd, 0x00010038, 0x00000007, 0x0004003b, 0x0000000a, 0x0000000b,
0x00000001, 0x00050036, 0x00000002, 0x00000004,
0x00000000, 0x00000003, 0x000200f8, 0x00000005,
0x0004003d, 0x00000007, 0x0000000c, 0x0000000b,
0x0003003e, 0x00000009, 0x0000000c, 0x000100fd,
0x00010038,
}; };
} // namespace sophia::shaders } // namespace sophia::shaders
+5 -4
View File
@@ -1,9 +1,10 @@
#version 450 #version 450
layout (location = 0) in vec2 position; // Per vertex; locations match Mesh::getAttributeDescriptions().
layout (location = 1) in vec3 color; layout (location = 0) in vec3 position;
layout (location = 3) in vec4 color;
layout (location = 0) out vec3 fragColor; layout (location = 0) out vec4 fragColor;
layout (push_constant) uniform Push { layout (push_constant) uniform Push {
mat4 transform; mat4 transform;
@@ -11,6 +12,6 @@ layout (push_constant) uniform Push {
} push; } push;
void main() { void main() {
gl_Position = vec4(push.transform * vec4(position, 0.0, 1.0)); gl_Position = push.transform * vec4(position, 1.0);
fragColor = color; fragColor = color;
} }
@@ -5,12 +5,12 @@
namespace sophia::shaders { namespace sophia::shaders {
inline constexpr std::array<uint32_t, 345> model_render_system_vert_spv = { inline constexpr std::array<uint32_t, 338> model_render_system_vert_spv = {
0x07230203, 0x00010000, 0x000d000b, 0x00000029, 0x00000000, 0x00020011, 0x07230203, 0x00010000, 0x000d000b, 0x00000027, 0x00000000, 0x00020011,
0x00000001, 0x0006000b, 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000001, 0x0006000b, 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e,
0x00000000, 0x0003000e, 0x00000000, 0x00000001, 0x0009000f, 0x00000000, 0x00000000, 0x0003000e, 0x00000000, 0x00000001, 0x0009000f, 0x00000000,
0x00000004, 0x6e69616d, 0x00000000, 0x0000000d, 0x00000019, 0x00000025, 0x00000004, 0x6e69616d, 0x00000000, 0x0000000d, 0x00000019, 0x00000023,
0x00000027, 0x00030003, 0x00000002, 0x000001c2, 0x000a0004, 0x475f4c47, 0x00000025, 0x00030003, 0x00000002, 0x000001c2, 0x000a0004, 0x475f4c47,
0x4c474f4f, 0x70635f45, 0x74735f70, 0x5f656c79, 0x656e696c, 0x7269645f, 0x4c474f4f, 0x70635f45, 0x74735f70, 0x5f656c79, 0x656e696c, 0x7269645f,
0x69746365, 0x00006576, 0x00080004, 0x475f4c47, 0x4c474f4f, 0x6e695f45, 0x69746365, 0x00006576, 0x00080004, 0x475f4c47, 0x4c474f4f, 0x6e695f45,
0x64756c63, 0x69645f65, 0x74636572, 0x00657669, 0x00040005, 0x00000004, 0x64756c63, 0x69645f65, 0x74636572, 0x00657669, 0x00040005, 0x00000004,
@@ -24,8 +24,8 @@ inline constexpr std::array<uint32_t, 345> model_render_system_vert_spv = {
0x00060006, 0x00000011, 0x00000000, 0x6e617274, 0x726f6673, 0x0000006d, 0x00060006, 0x00000011, 0x00000000, 0x6e617274, 0x726f6673, 0x0000006d,
0x00050006, 0x00000011, 0x00000001, 0x6f6c6f63, 0x00000072, 0x00040005, 0x00050006, 0x00000011, 0x00000001, 0x6f6c6f63, 0x00000072, 0x00040005,
0x00000013, 0x68737570, 0x00000000, 0x00050005, 0x00000019, 0x69736f70, 0x00000013, 0x68737570, 0x00000000, 0x00050005, 0x00000019, 0x69736f70,
0x6e6f6974, 0x00000000, 0x00050005, 0x00000025, 0x67617266, 0x6f6c6f43, 0x6e6f6974, 0x00000000, 0x00050005, 0x00000023, 0x67617266, 0x6f6c6f43,
0x00000072, 0x00040005, 0x00000027, 0x6f6c6f63, 0x00000072, 0x00030047, 0x00000072, 0x00040005, 0x00000025, 0x6f6c6f63, 0x00000072, 0x00030047,
0x0000000b, 0x00000002, 0x00050048, 0x0000000b, 0x00000000, 0x0000000b, 0x0000000b, 0x00000002, 0x00050048, 0x0000000b, 0x00000000, 0x0000000b,
0x00000000, 0x00050048, 0x0000000b, 0x00000001, 0x0000000b, 0x00000001, 0x00000000, 0x00050048, 0x0000000b, 0x00000001, 0x0000000b, 0x00000001,
0x00050048, 0x0000000b, 0x00000002, 0x0000000b, 0x00000003, 0x00050048, 0x00050048, 0x0000000b, 0x00000002, 0x0000000b, 0x00000003, 0x00050048,
@@ -34,8 +34,8 @@ inline constexpr std::array<uint32_t, 345> model_render_system_vert_spv = {
0x00000011, 0x00000000, 0x00000007, 0x00000010, 0x00050048, 0x00000011, 0x00000011, 0x00000000, 0x00000007, 0x00000010, 0x00050048, 0x00000011,
0x00000000, 0x00000023, 0x00000000, 0x00050048, 0x00000011, 0x00000001, 0x00000000, 0x00000023, 0x00000000, 0x00050048, 0x00000011, 0x00000001,
0x00000023, 0x00000040, 0x00040047, 0x00000019, 0x0000001e, 0x00000000, 0x00000023, 0x00000040, 0x00040047, 0x00000019, 0x0000001e, 0x00000000,
0x00040047, 0x00000025, 0x0000001e, 0x00000000, 0x00040047, 0x00000027, 0x00040047, 0x00000023, 0x0000001e, 0x00000000, 0x00040047, 0x00000025,
0x0000001e, 0x00000001, 0x00020013, 0x00000002, 0x00030021, 0x00000003, 0x0000001e, 0x00000003, 0x00020013, 0x00000002, 0x00030021, 0x00000003,
0x00000002, 0x00030016, 0x00000006, 0x00000020, 0x00040017, 0x00000007, 0x00000002, 0x00030016, 0x00000006, 0x00000020, 0x00040017, 0x00000007,
0x00000006, 0x00000004, 0x00040015, 0x00000008, 0x00000020, 0x00000000, 0x00000006, 0x00000004, 0x00040015, 0x00000008, 0x00000020, 0x00000000,
0x0004002b, 0x00000008, 0x00000009, 0x00000001, 0x0004001c, 0x0000000a, 0x0004002b, 0x00000008, 0x00000009, 0x00000001, 0x0004001c, 0x0000000a,
@@ -46,24 +46,23 @@ inline constexpr std::array<uint32_t, 345> model_render_system_vert_spv = {
0x00040018, 0x00000010, 0x00000007, 0x00000004, 0x0004001e, 0x00000011, 0x00040018, 0x00000010, 0x00000007, 0x00000004, 0x0004001e, 0x00000011,
0x00000010, 0x00000007, 0x00040020, 0x00000012, 0x00000009, 0x00000011, 0x00000010, 0x00000007, 0x00040020, 0x00000012, 0x00000009, 0x00000011,
0x0004003b, 0x00000012, 0x00000013, 0x00000009, 0x00040020, 0x00000014, 0x0004003b, 0x00000012, 0x00000013, 0x00000009, 0x00040020, 0x00000014,
0x00000009, 0x00000010, 0x00040017, 0x00000017, 0x00000006, 0x00000002, 0x00000009, 0x00000010, 0x00040017, 0x00000017, 0x00000006, 0x00000003,
0x00040020, 0x00000018, 0x00000001, 0x00000017, 0x0004003b, 0x00000018, 0x00040020, 0x00000018, 0x00000001, 0x00000017, 0x0004003b, 0x00000018,
0x00000019, 0x00000001, 0x0004002b, 0x00000006, 0x0000001b, 0x00000000, 0x00000019, 0x00000001, 0x0004002b, 0x00000006, 0x0000001b, 0x3f800000,
0x0004002b, 0x00000006, 0x0000001c, 0x3f800000, 0x00040020, 0x00000021, 0x00040020, 0x00000021, 0x00000003, 0x00000007, 0x0004003b, 0x00000021,
0x00000003, 0x00000007, 0x00040017, 0x00000023, 0x00000006, 0x00000003, 0x00000023, 0x00000003, 0x00040020, 0x00000024, 0x00000001, 0x00000007,
0x00040020, 0x00000024, 0x00000003, 0x00000023, 0x0004003b, 0x00000024, 0x0004003b, 0x00000024, 0x00000025, 0x00000001, 0x00050036, 0x00000002,
0x00000025, 0x00000003, 0x00040020, 0x00000026, 0x00000001, 0x00000023,
0x0004003b, 0x00000026, 0x00000027, 0x00000001, 0x00050036, 0x00000002,
0x00000004, 0x00000000, 0x00000003, 0x000200f8, 0x00000005, 0x00050041, 0x00000004, 0x00000000, 0x00000003, 0x000200f8, 0x00000005, 0x00050041,
0x00000014, 0x00000015, 0x00000013, 0x0000000f, 0x0004003d, 0x00000010, 0x00000014, 0x00000015, 0x00000013, 0x0000000f, 0x0004003d, 0x00000010,
0x00000016, 0x00000015, 0x0004003d, 0x00000017, 0x0000001a, 0x00000019, 0x00000016, 0x00000015, 0x0004003d, 0x00000017, 0x0000001a, 0x00000019,
0x00050051, 0x00000006, 0x0000001d, 0x0000001a, 0x00000000, 0x00050051, 0x00050051, 0x00000006, 0x0000001c, 0x0000001a, 0x00000000, 0x00050051,
0x00000006, 0x0000001e, 0x0000001a, 0x00000001, 0x00070050, 0x00000007, 0x00000006, 0x0000001d, 0x0000001a, 0x00000001, 0x00050051, 0x00000006,
0x0000001f, 0x0000001d, 0x0000001e, 0x0000001b, 0x0000001c, 0x00050091, 0x0000001e, 0x0000001a, 0x00000002, 0x00070050, 0x00000007, 0x0000001f,
0x00000007, 0x00000020, 0x00000016, 0x0000001f, 0x00050041, 0x00000021, 0x0000001c, 0x0000001d, 0x0000001e, 0x0000001b, 0x00050091, 0x00000007,
0x00000022, 0x0000000d, 0x0000000f, 0x0003003e, 0x00000022, 0x00000020, 0x00000020, 0x00000016, 0x0000001f, 0x00050041, 0x00000021, 0x00000022,
0x0004003d, 0x00000023, 0x00000028, 0x00000027, 0x0003003e, 0x00000025, 0x0000000d, 0x0000000f, 0x0003003e, 0x00000022, 0x00000020, 0x0004003d,
0x00000028, 0x000100fd, 0x00010038, 0x00000007, 0x00000026, 0x00000025, 0x0003003e, 0x00000023, 0x00000026,
0x000100fd, 0x00010038,
}; };
} // namespace sophia::shaders } // namespace sophia::shaders
+2 -4
View File
@@ -22,10 +22,8 @@ void main() {
vec2 corner = CORNERS[gl_VertexIndex]; vec2 corner = CORNERS[gl_VertexIndex];
gl_Position = push.viewProjection * model * vec4(corner, 0.0, 1.0); gl_Position = push.viewProjection * model * vec4(corner, 0.0, 1.0);
// Draw order comes from the CPU-side sort, so flatten depth; sprites should
// never be clipped by the camera's near/far planes.
gl_Position.z = 0.0;
fragColor = color; fragColor = color;
fragUv = mix(uvRect.xy, uvRect.zw, corner); // The quad's y runs up and the texture's v runs down.
fragUv = mix(uvRect.xy, uvRect.zw, vec2(corner.x, 1.0 - corner.y));
} }
+27 -25
View File
@@ -5,12 +5,12 @@
namespace sophia::shaders { namespace sophia::shaders {
inline constexpr std::array<uint32_t, 519> sprite_vert_spv = { inline constexpr std::array<uint32_t, 534> sprite_vert_spv = {
0x07230203, 0x00010000, 0x000d000b, 0x00000045, 0x00000000, 0x00020011, 0x07230203, 0x00010000, 0x000d000b, 0x00000049, 0x00000000, 0x00020011,
0x00000001, 0x0006000b, 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000001, 0x0006000b, 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e,
0x00000000, 0x0003000e, 0x00000000, 0x00000001, 0x000c000f, 0x00000000, 0x00000000, 0x0003000e, 0x00000000, 0x00000001, 0x000c000f, 0x00000000,
0x00000004, 0x6e69616d, 0x00000000, 0x00000016, 0x00000021, 0x0000002b, 0x00000004, 0x6e69616d, 0x00000000, 0x00000016, 0x00000021, 0x0000002b,
0x00000038, 0x0000003a, 0x0000003d, 0x0000003e, 0x00030003, 0x00000002, 0x00000035, 0x00000037, 0x0000003a, 0x0000003b, 0x00030003, 0x00000002,
0x000001c2, 0x000a0004, 0x475f4c47, 0x4c474f4f, 0x70635f45, 0x74735f70, 0x000001c2, 0x000a0004, 0x475f4c47, 0x4c474f4f, 0x70635f45, 0x74735f70,
0x5f656c79, 0x656e696c, 0x7269645f, 0x69746365, 0x00006576, 0x00080004, 0x5f656c79, 0x656e696c, 0x7269645f, 0x69746365, 0x00006576, 0x00080004,
0x475f4c47, 0x4c474f4f, 0x6e695f45, 0x64756c63, 0x69645f65, 0x74636572, 0x475f4c47, 0x4c474f4f, 0x6e695f45, 0x64756c63, 0x69645f65, 0x74636572,
@@ -26,9 +26,9 @@ inline constexpr std::array<uint32_t, 519> sprite_vert_spv = {
0x00000021, 0x00000000, 0x00040005, 0x00000024, 0x68737550, 0x00000000, 0x00000021, 0x00000000, 0x00040005, 0x00000024, 0x68737550, 0x00000000,
0x00070006, 0x00000024, 0x00000000, 0x77656976, 0x6a6f7250, 0x69746365, 0x00070006, 0x00000024, 0x00000000, 0x77656976, 0x6a6f7250, 0x69746365,
0x00006e6f, 0x00040005, 0x00000026, 0x68737570, 0x00000000, 0x00040005, 0x00006e6f, 0x00040005, 0x00000026, 0x68737570, 0x00000000, 0x00040005,
0x0000002b, 0x65646f6d, 0x0000006c, 0x00050005, 0x00000038, 0x67617266, 0x0000002b, 0x65646f6d, 0x0000006c, 0x00050005, 0x00000035, 0x67617266,
0x6f6c6f43, 0x00000072, 0x00040005, 0x0000003a, 0x6f6c6f63, 0x00000072, 0x6f6c6f43, 0x00000072, 0x00040005, 0x00000037, 0x6f6c6f63, 0x00000072,
0x00040005, 0x0000003d, 0x67617266, 0x00007655, 0x00040005, 0x0000003e, 0x00040005, 0x0000003a, 0x67617266, 0x00007655, 0x00040005, 0x0000003b,
0x65527675, 0x00007463, 0x00040047, 0x00000016, 0x0000000b, 0x0000002a, 0x65527675, 0x00007463, 0x00040047, 0x00000016, 0x0000000b, 0x0000002a,
0x00030047, 0x0000001f, 0x00000002, 0x00050048, 0x0000001f, 0x00000000, 0x00030047, 0x0000001f, 0x00000002, 0x00050048, 0x0000001f, 0x00000000,
0x0000000b, 0x00000000, 0x00050048, 0x0000001f, 0x00000001, 0x0000000b, 0x0000000b, 0x00000000, 0x00050048, 0x0000001f, 0x00000001, 0x0000000b,
@@ -37,9 +37,9 @@ inline constexpr std::array<uint32_t, 519> sprite_vert_spv = {
0x00000024, 0x00000002, 0x00040048, 0x00000024, 0x00000000, 0x00000005, 0x00000024, 0x00000002, 0x00040048, 0x00000024, 0x00000000, 0x00000005,
0x00050048, 0x00000024, 0x00000000, 0x00000007, 0x00000010, 0x00050048, 0x00050048, 0x00000024, 0x00000000, 0x00000007, 0x00000010, 0x00050048,
0x00000024, 0x00000000, 0x00000023, 0x00000000, 0x00040047, 0x0000002b, 0x00000024, 0x00000000, 0x00000023, 0x00000000, 0x00040047, 0x0000002b,
0x0000001e, 0x00000000, 0x00040047, 0x00000038, 0x0000001e, 0x00000000, 0x0000001e, 0x00000000, 0x00040047, 0x00000035, 0x0000001e, 0x00000000,
0x00040047, 0x0000003a, 0x0000001e, 0x00000004, 0x00040047, 0x0000003d, 0x00040047, 0x00000037, 0x0000001e, 0x00000004, 0x00040047, 0x0000003a,
0x0000001e, 0x00000001, 0x00040047, 0x0000003e, 0x0000001e, 0x00000005, 0x0000001e, 0x00000001, 0x00040047, 0x0000003b, 0x0000001e, 0x00000005,
0x00020013, 0x00000002, 0x00030021, 0x00000003, 0x00000002, 0x00030016, 0x00020013, 0x00000002, 0x00030021, 0x00000003, 0x00000002, 0x00030016,
0x00000006, 0x00000020, 0x00040017, 0x00000007, 0x00000006, 0x00000002, 0x00000006, 0x00000020, 0x00040017, 0x00000007, 0x00000006, 0x00000002,
0x00040020, 0x00000008, 0x00000007, 0x00000007, 0x00040015, 0x0000000a, 0x00040020, 0x00000008, 0x00000007, 0x00000007, 0x00040015, 0x0000000a,
@@ -63,12 +63,12 @@ inline constexpr std::array<uint32_t, 519> sprite_vert_spv = {
0x00000009, 0x00000024, 0x0004003b, 0x00000025, 0x00000026, 0x00000009, 0x00000009, 0x00000024, 0x0004003b, 0x00000025, 0x00000026, 0x00000009,
0x00040020, 0x00000027, 0x00000009, 0x00000023, 0x00040020, 0x0000002a, 0x00040020, 0x00000027, 0x00000009, 0x00000023, 0x00040020, 0x0000002a,
0x00000001, 0x00000023, 0x0004003b, 0x0000002a, 0x0000002b, 0x00000001, 0x00000001, 0x00000023, 0x0004003b, 0x0000002a, 0x0000002b, 0x00000001,
0x00040020, 0x00000033, 0x00000003, 0x0000001c, 0x0004002b, 0x0000000a, 0x00040020, 0x00000033, 0x00000003, 0x0000001c, 0x0004003b, 0x00000033,
0x00000035, 0x00000002, 0x00040020, 0x00000036, 0x00000003, 0x00000006, 0x00000035, 0x00000003, 0x00040020, 0x00000036, 0x00000001, 0x0000001c,
0x0004003b, 0x00000033, 0x00000038, 0x00000003, 0x00040020, 0x00000039, 0x0004003b, 0x00000036, 0x00000037, 0x00000001, 0x00040020, 0x00000039,
0x00000001, 0x0000001c, 0x0004003b, 0x00000039, 0x0000003a, 0x00000001, 0x00000003, 0x00000007, 0x0004003b, 0x00000039, 0x0000003a, 0x00000003,
0x00040020, 0x0000003c, 0x00000003, 0x00000007, 0x0004003b, 0x0000003c, 0x0004003b, 0x00000036, 0x0000003b, 0x00000001, 0x0004002b, 0x0000000a,
0x0000003d, 0x00000003, 0x0004003b, 0x00000039, 0x0000003e, 0x00000001, 0x00000040, 0x00000000, 0x00040020, 0x00000041, 0x00000007, 0x00000006,
0x00050036, 0x00000002, 0x00000004, 0x00000000, 0x00000003, 0x000200f8, 0x00050036, 0x00000002, 0x00000004, 0x00000000, 0x00000003, 0x000200f8,
0x00000005, 0x0004003b, 0x00000008, 0x00000009, 0x00000007, 0x0004003b, 0x00000005, 0x0004003b, 0x00000008, 0x00000009, 0x00000007, 0x0004003b,
0x00000018, 0x00000019, 0x00000007, 0x0004003d, 0x00000014, 0x00000017, 0x00000018, 0x00000019, 0x00000007, 0x0004003d, 0x00000014, 0x00000017,
@@ -83,16 +83,18 @@ inline constexpr std::array<uint32_t, 519> sprite_vert_spv = {
0x00070050, 0x0000001c, 0x00000031, 0x0000002f, 0x00000030, 0x0000000d, 0x00070050, 0x0000001c, 0x00000031, 0x0000002f, 0x00000030, 0x0000000d,
0x0000000f, 0x00050091, 0x0000001c, 0x00000032, 0x0000002d, 0x00000031, 0x0000000f, 0x00050091, 0x0000001c, 0x00000032, 0x0000002d, 0x00000031,
0x00050041, 0x00000033, 0x00000034, 0x00000021, 0x00000022, 0x0003003e, 0x00050041, 0x00000033, 0x00000034, 0x00000021, 0x00000022, 0x0003003e,
0x00000034, 0x00000032, 0x00060041, 0x00000036, 0x00000037, 0x00000021, 0x00000034, 0x00000032, 0x0004003d, 0x0000001c, 0x00000038, 0x00000037,
0x00000022, 0x00000035, 0x0003003e, 0x00000037, 0x0000000d, 0x0004003d, 0x0003003e, 0x00000035, 0x00000038, 0x0004003d, 0x0000001c, 0x0000003c,
0x0000001c, 0x0000003b, 0x0000003a, 0x0003003e, 0x00000038, 0x0000003b, 0x0000003b, 0x0007004f, 0x00000007, 0x0000003d, 0x0000003c, 0x0000003c,
0x0004003d, 0x0000001c, 0x0000003f, 0x0000003e, 0x0007004f, 0x00000007, 0x00000000, 0x00000001, 0x0004003d, 0x0000001c, 0x0000003e, 0x0000003b,
0x00000040, 0x0000003f, 0x0000003f, 0x00000000, 0x00000001, 0x0004003d, 0x0007004f, 0x00000007, 0x0000003f, 0x0000003e, 0x0000003e, 0x00000002,
0x0000001c, 0x00000041, 0x0000003e, 0x0007004f, 0x00000007, 0x00000042, 0x00000003, 0x00050041, 0x00000041, 0x00000042, 0x00000009, 0x00000040,
0x00000041, 0x00000041, 0x00000002, 0x00000003, 0x0004003d, 0x00000007, 0x0004003d, 0x00000006, 0x00000043, 0x00000042, 0x00050041, 0x00000041,
0x00000043, 0x00000009, 0x0008000c, 0x00000007, 0x00000044, 0x00000001, 0x00000044, 0x00000009, 0x0000001d, 0x0004003d, 0x00000006, 0x00000045,
0x0000002e, 0x00000040, 0x00000042, 0x00000043, 0x0003003e, 0x0000003d, 0x00000044, 0x00050083, 0x00000006, 0x00000046, 0x0000000f, 0x00000045,
0x00000044, 0x000100fd, 0x00010038, 0x00050050, 0x00000007, 0x00000047, 0x00000043, 0x00000046, 0x0008000c,
0x00000007, 0x00000048, 0x00000001, 0x0000002e, 0x0000003d, 0x0000003f,
0x00000047, 0x0003003e, 0x0000003a, 0x00000048, 0x000100fd, 0x00010038,
}; };
} // namespace sophia::shaders } // namespace sophia::shaders
+3 -2
View File
@@ -47,8 +47,9 @@ void Panel::draw(const FrameInfo& frameInfo) {
} }
bool open = true; bool open = true;
ImGui::Begin(config.name.c_str(), config.hasCloseButton() ? &open : nullptr, this->shown = ImGui::Begin(config.name.c_str(),
config.getWindowFlags()); config.hasCloseButton() ? &open : nullptr,
config.getWindowFlags());
onUpdate(frameInfo); onUpdate(frameInfo);
ImGui::End(); ImGui::End();
+1
View File
@@ -48,6 +48,7 @@ class Device {
void waitIdle() { this->device->waitIdle(); } void waitIdle() { this->device->waitIdle(); }
vk::PhysicalDevice getPhysicalDevice() const { return this->physicalDevice; }
VmaAllocator getAllocator() const { return this->allocator; } VmaAllocator getAllocator() const { return this->allocator; }
vk::CommandPool getCommandPool() const { return this->commandPool; } vk::CommandPool getCommandPool() const { return this->commandPool; }
vk::SurfaceKHR getSurface() const { return surface; } vk::SurfaceKHR getSurface() const { return surface; }
@@ -101,6 +101,21 @@ GraphicsPipeline::Builder& GraphicsPipeline::Builder::setAlphaBlending(
return *this; return *this;
} }
GraphicsPipeline::Builder& GraphicsPipeline::Builder::setDepthTest(
bool test, bool write, vk::CompareOp compareOp) {
this->depthTest = test;
this->depthWrite = write;
this->depthCompareOp = compareOp;
return *this;
}
GraphicsPipeline::Builder& GraphicsPipeline::Builder::setCullMode(
vk::CullModeFlags cullMode, vk::FrontFace frontFace) {
this->cullMode = cullMode;
this->frontFace = frontFace;
return *this;
}
std::unique_ptr<GraphicsPipeline> GraphicsPipeline::Builder::build() const { std::unique_ptr<GraphicsPipeline> GraphicsPipeline::Builder::build() const {
if (this->vertexShader == nullptr || this->fragmentShader == nullptr) { if (this->vertexShader == nullptr || this->fragmentShader == nullptr) {
log::fatal( log::fatal(
@@ -130,26 +145,14 @@ std::unique_ptr<GraphicsPipeline> GraphicsPipeline::Builder::build() const {
"setDepthAttachmentFormat() was not called"); "setDepthAttachmentFormat() was not called");
} }
return std::unique_ptr<GraphicsPipeline>(new GraphicsPipeline( return std::unique_ptr<GraphicsPipeline>(new GraphicsPipeline(*this));
this->device, *this->vertexShader, *this->fragmentShader,
this->pipelineLayout, this->colorAttachmentFormats,
this->depthAttachmentFormat, this->stencilAttachmentFormat,
this->bindingDescriptions, this->attributeDescriptions,
this->alphaBlending));
} }
GraphicsPipeline::GraphicsPipeline( GraphicsPipeline::GraphicsPipeline(const Builder& builder)
Device& device, Shader& vertexShader, Shader& fragmentShader, : Pipeline(builder.device, vk::PipelineBindPoint::eGraphics) {
vk::PipelineLayout pipelineLayout,
std::vector<vk::Format> colorAttachmentFormats,
vk::Format depthAttachmentFormat, vk::Format stencilAttachmentFormat,
std::vector<vk::VertexInputBindingDescription> bindingDescriptions,
std::vector<vk::VertexInputAttributeDescription> attributeDescriptions,
bool alphaBlending)
: Pipeline(device, vk::PipelineBindPoint::eGraphics) {
setDefaultGraphicsPipelineConfig(); setDefaultGraphicsPipelineConfig();
if (alphaBlending) { if (builder.alphaBlending) {
vk::PipelineColorBlendAttachmentState& blend = vk::PipelineColorBlendAttachmentState& blend =
this->config.colorBlendAttachment; this->config.colorBlendAttachment;
blend.blendEnable = vk::True; blend.blendEnable = vk::True;
@@ -159,17 +162,24 @@ GraphicsPipeline::GraphicsPipeline(
blend.dstAlphaBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha; blend.dstAlphaBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha;
} }
this->config.bindingDescriptions = std::move(bindingDescriptions); this->config.depthStencilInfo.depthTestEnable = builder.depthTest;
this->config.attributeDescriptions = std::move(attributeDescriptions); this->config.depthStencilInfo.depthWriteEnable = builder.depthWrite;
this->config.pipelineLayout = pipelineLayout; this->config.depthStencilInfo.depthCompareOp = builder.depthCompareOp;
this->config.colorAttachmentFormats = std::move(colorAttachmentFormats);
this->config.depthAttachmentFormat = depthAttachmentFormat; this->config.rasterizationInfo.cullMode = builder.cullMode;
this->config.stencilAttachmentFormat = stencilAttachmentFormat; this->config.rasterizationInfo.frontFace = builder.frontFace;
this->config.bindingDescriptions = builder.bindingDescriptions;
this->config.attributeDescriptions = builder.attributeDescriptions;
this->config.pipelineLayout = builder.pipelineLayout;
this->config.colorAttachmentFormats = builder.colorAttachmentFormats;
this->config.depthAttachmentFormat = builder.depthAttachmentFormat;
this->config.stencilAttachmentFormat = builder.stencilAttachmentFormat;
vk::UniqueShaderModule vertexShaderModule = vk::UniqueShaderModule vertexShaderModule =
createShaderModule(vertexShader.getSpirv()); createShaderModule(builder.vertexShader->getSpirv());
vk::UniqueShaderModule fragmentShaderModule = vk::UniqueShaderModule fragmentShaderModule =
createShaderModule(fragmentShader.getSpirv()); createShaderModule(builder.fragmentShader->getSpirv());
log::trace("created graphics shader modules"); log::trace("created graphics shader modules");
vk::PipelineShaderStageCreateInfo shaderStages[] = { vk::PipelineShaderStageCreateInfo shaderStages[] = {
@@ -245,7 +255,7 @@ void GraphicsPipeline::setDefaultGraphicsPipelineConfig() {
this->config.rasterizationInfo.polygonMode = vk::PolygonMode::eFill; this->config.rasterizationInfo.polygonMode = vk::PolygonMode::eFill;
this->config.rasterizationInfo.lineWidth = 1.0f; this->config.rasterizationInfo.lineWidth = 1.0f;
this->config.rasterizationInfo.cullMode = vk::CullModeFlagBits::eNone; this->config.rasterizationInfo.cullMode = vk::CullModeFlagBits::eNone;
this->config.rasterizationInfo.frontFace = vk::FrontFace::eClockwise; this->config.rasterizationInfo.frontFace = vk::FrontFace::eCounterClockwise;
this->config.rasterizationInfo.depthBiasEnable = vk::False; this->config.rasterizationInfo.depthBiasEnable = vk::False;
this->config.rasterizationInfo.depthBiasConstantFactor = 0.0f; this->config.rasterizationInfo.depthBiasConstantFactor = 0.0f;
this->config.rasterizationInfo.depthBiasClamp = 0.0f; this->config.rasterizationInfo.depthBiasClamp = 0.0f;
@@ -41,14 +41,7 @@ class GraphicsPipeline : public Pipeline {
~GraphicsPipeline() override; ~GraphicsPipeline() override;
private: private:
GraphicsPipeline( explicit GraphicsPipeline(const Builder& builder);
Device& device, Shader& vertexShader, Shader& fragmentShader,
vk::PipelineLayout pipelineLayout,
std::vector<vk::Format> colorAttachmentFormats,
vk::Format depthAttachmentFormat, vk::Format stencilAttachmentFormat,
std::vector<vk::VertexInputBindingDescription> bindingDescriptions,
std::vector<vk::VertexInputAttributeDescription> attributeDescriptions,
bool alphaBlending);
void setDefaultGraphicsPipelineConfig(); void setDefaultGraphicsPipelineConfig();
@@ -72,6 +65,14 @@ class GraphicsPipeline::Builder {
std::vector<vk::VertexInputAttributeDescription> attributeDescriptions); std::vector<vk::VertexInputAttributeDescription> attributeDescriptions);
// Straight (non-premultiplied) "over" blending on every color attachment. // Straight (non-premultiplied) "over" blending on every color attachment.
Builder& setAlphaBlending(bool enabled = true); Builder& setAlphaBlending(bool enabled = true);
// Only matters with a depth attachment. Defaults to testing and writing
// with eLess.
Builder& setDepthTest(bool test, bool write,
vk::CompareOp compareOp = vk::CompareOp::eLess);
// Defaults to no culling, with counter-clockwise triangles facing front.
Builder& setCullMode(
vk::CullModeFlags cullMode,
vk::FrontFace frontFace = vk::FrontFace::eCounterClockwise);
std::unique_ptr<GraphicsPipeline> build() const; std::unique_ptr<GraphicsPipeline> build() const;
@@ -87,6 +88,13 @@ class GraphicsPipeline::Builder {
std::vector<vk::VertexInputBindingDescription> bindingDescriptions{}; std::vector<vk::VertexInputBindingDescription> bindingDescriptions{};
std::vector<vk::VertexInputAttributeDescription> attributeDescriptions{}; std::vector<vk::VertexInputAttributeDescription> attributeDescriptions{};
bool alphaBlending = false; bool alphaBlending = false;
bool depthTest = true;
bool depthWrite = true;
vk::CompareOp depthCompareOp = vk::CompareOp::eLess;
vk::CullModeFlags cullMode = vk::CullModeFlagBits::eNone;
vk::FrontFace frontFace = vk::FrontFace::eCounterClockwise;
friend class GraphicsPipeline;
}; };
} // namespace sophia } // namespace sophia
+1 -2
View File
@@ -215,8 +215,7 @@ vk::Extent2D Swapchain::chooseExtent(
vk::Format Swapchain::findDepthFormat() { vk::Format Swapchain::findDepthFormat() {
return this->device.findSupportedFormat( return this->device.findSupportedFormat(
{vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, {vk::Format::eD32Sfloat, vk::Format::eX8D24UnormPack32},
vk::Format::eD24UnormS8Uint},
vk::ImageTiling::eOptimal, vk::ImageTiling::eOptimal,
vk::FormatFeatureFlagBits::eDepthStencilAttachment); vk::FormatFeatureFlagBits::eDepthStencilAttachment);
} }
+2 -2
View File
@@ -141,8 +141,8 @@ class Boids2D : public sophia::Application {
cameraEntity.getComponent<sophia::TransformComponent>().position; cameraEntity.getComponent<sophia::TransformComponent>().position;
if (scrollLeft) cameraPosition.x -= scrollDistance; if (scrollLeft) cameraPosition.x -= scrollDistance;
if (scrollRight) cameraPosition.x += scrollDistance; if (scrollRight) cameraPosition.x += scrollDistance;
if (scrollUp) cameraPosition.y -= scrollDistance; if (scrollUp) cameraPosition.y += scrollDistance;
if (scrollDown) cameraPosition.y += scrollDistance; if (scrollDown) cameraPosition.y -= scrollDistance;
// One world unit per pixel, whatever size the viewport is. // One world unit per pixel, whatever size the viewport is.
cameraEntity.getComponent<sophia::CameraComponent>().ortho.height = cameraEntity.getComponent<sophia::CameraComponent>().ortho.height =
+3
View File
@@ -0,0 +1,3 @@
add_executable(cube main.cpp)
target_link_libraries(cube PRIVATE ${PROJECT_NAME})
+50
View File
@@ -0,0 +1,50 @@
#include <glm/gtc/quaternion.hpp>
#include <sophia/application.hpp>
#include <sophia/components.hpp>
#include <sophia/entity.hpp>
#include <sophia/primitives.hpp>
#include <sophia/scene_view.hpp>
// A spinning cube seen through a perspective camera.
class Cube : public sophia::Application {
public:
Cube() : Application{{.width = 800, .height = 600, .name = "Cube"}} {
debugUI().showFrameStats(true);
}
void onCreate() override {
sophia::SceneHandle sceneHandle = scenes().create();
sophia::Scene& scene = scenes().get(sceneHandle);
sophia::Entity camera = scene.createCamera();
camera.getComponent<sophia::CameraComponent>().projection =
sophia::CameraComponent::Projection::Perspective;
camera.getComponent<sophia::TransformComponent>().position = {0.f, 0.f,
3.f};
// Unlit, so color each face by its normal to tell them apart.
sophia::MeshData data = sophia::primitives::cube();
for (sophia::MeshVertex& vertex : data.vertices) {
vertex.color = glm::vec4{vertex.normal * 0.5f + 0.5f, 1.f};
}
cube = scene.createEntity("Cube");
cube.addComponent<sophia::MeshComponent>(sophia::MeshComponent{
.handle = resources().create<sophia::Mesh>(data)});
sceneViews().create({
.scene = sceneHandle,
});
}
void onUpdate(const sophia::FrameInfo& frameInfo) override {
auto angle = static_cast<float>(frameInfo.elapsedTime);
cube.getComponent<sophia::TransformComponent>().rotation =
glm::angleAxis(angle, glm::normalize(glm::vec3{1.f, 1.f, 0.f}));
}
private:
sophia::Entity cube;
};
SOPHIA_MAIN(Cube)
+14
View File
@@ -78,3 +78,17 @@ target_link_libraries(imgui PRIVATE
if(UNIX AND NOT APPLE) if(UNIX AND NOT APPLE)
target_compile_options(imgui PRIVATE -fPIC) target_compile_options(imgui PRIVATE -fPIC)
endif() endif()
# ImGuizmo: only the transform gizmo. The repo's own CMakeLists also builds
# its other widgets, and doesn't link imgui when used as a subdirectory.
add_library(imguizmo STATIC
${CMAKE_CURRENT_SOURCE_DIR}/ImGuizmo/src/ImGuizmo.cpp
)
target_include_directories(imguizmo PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/ImGuizmo/src
)
target_link_libraries(imguizmo PUBLIC
imgui
)
Vendored Submodule
+1
Submodule third_party/ImGuizmo added at 18cef5e031