Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 17fe9de756 | |||
| 2f5268fef6 | |||
| 09a6c0283a | |||
| 3784d0fdec | |||
| bfc96bac2b | |||
| 0f0b0d1434 | |||
| 777e407d67 |
@@ -11,7 +11,7 @@ namespace sophia {
|
|||||||
|
|
||||||
struct TagComponent {
|
struct TagComponent {
|
||||||
std::string tag; // entt::hashed_string ?
|
std::string tag; // entt::hashed_string ?
|
||||||
uint32_t mask = 0;
|
u32 mask = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TransformComponent {
|
struct TransformComponent {
|
||||||
@@ -100,7 +100,7 @@ struct CameraComponent {
|
|||||||
float farPlane = 100.f;
|
float farPlane = 100.f;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class Projection { Orthographic, Perspective };
|
enum class Projection : u8 { Orthographic, Perspective };
|
||||||
|
|
||||||
Projection projection = Projection::Orthographic;
|
Projection projection = Projection::Orthographic;
|
||||||
Orthographic ortho;
|
Orthographic ortho;
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ Buffer::Buffer(Device& device, vk::DeviceSize instanceSize, u32 instanceCount,
|
|||||||
memoryPropertyFlags{memoryPropertyFlags} {
|
memoryPropertyFlags{memoryPropertyFlags} {
|
||||||
alignmentSize = getAlignment(instanceSize, minOffsetAlignment);
|
alignmentSize = getAlignment(instanceSize, minOffsetAlignment);
|
||||||
bufferSize = alignmentSize * instanceCount;
|
bufferSize = alignmentSize * instanceCount;
|
||||||
device.createBuffer(bufferSize, usageFlags, memoryPropertyFlags, buffer,
|
device.createBuffer(bufferSize, this->usageFlags, this->memoryPropertyFlags,
|
||||||
allocation);
|
this->buffer, this->allocation, this->info);
|
||||||
}
|
}
|
||||||
|
|
||||||
Buffer::~Buffer() {
|
Buffer::~Buffer() {
|
||||||
@@ -62,6 +62,9 @@ void Buffer::writeToBuffer(void* data, vk::DeviceSize size,
|
|||||||
if (size == VK_WHOLE_SIZE) {
|
if (size == VK_WHOLE_SIZE) {
|
||||||
memcpy(mapped, data, bufferSize);
|
memcpy(mapped, data, bufferSize);
|
||||||
} else {
|
} else {
|
||||||
|
SOPHIA_ASSERT(offset + size <= bufferSize,
|
||||||
|
"write of {} bytes at offset {} overflows buffer of {} bytes",
|
||||||
|
size, offset, bufferSize);
|
||||||
char* memOffset = (char*)mapped;
|
char* memOffset = (char*)mapped;
|
||||||
memOffset += offset;
|
memOffset += offset;
|
||||||
memcpy(memOffset, data, size);
|
memcpy(memOffset, data, size);
|
||||||
|
|||||||
@@ -36,9 +36,10 @@ class Buffer {
|
|||||||
vk::DescriptorBufferInfo descriptorInfoForIndex(int index);
|
vk::DescriptorBufferInfo descriptorInfoForIndex(int index);
|
||||||
vk::Result invalidateIndex(int index);
|
vk::Result invalidateIndex(int index);
|
||||||
|
|
||||||
vk::Buffer getBuffer() const { return buffer; }
|
void* getMappedMemory() const { return this->mapped; }
|
||||||
void* getMappedMemory() const { return mapped; }
|
vk::Buffer getBuffer() const { return this->buffer; }
|
||||||
uint32_t getInstanceCount() const { return instanceCount; }
|
const VmaAllocationInfo& getAllocationInfo() const { return this->info; }
|
||||||
|
u32 getInstanceCount() const { return instanceCount; }
|
||||||
vk::DeviceSize getInstanceSize() const { return instanceSize; }
|
vk::DeviceSize getInstanceSize() const { return instanceSize; }
|
||||||
vk::DeviceSize getAlignmentSize() const { return alignmentSize; }
|
vk::DeviceSize getAlignmentSize() const { return alignmentSize; }
|
||||||
vk::BufferUsageFlags getUsageFlags() const { return usageFlags; }
|
vk::BufferUsageFlags getUsageFlags() const { return usageFlags; }
|
||||||
@@ -47,6 +48,10 @@ class Buffer {
|
|||||||
}
|
}
|
||||||
vk::DeviceSize getBufferSize() const { return bufferSize; }
|
vk::DeviceSize getBufferSize() const { return bufferSize; }
|
||||||
|
|
||||||
|
vk::DeviceAddress getDeviceAddress() {
|
||||||
|
return this->device.get()->getBufferAddress(this->buffer);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static vk::DeviceSize getAlignment(vk::DeviceSize instanceSize,
|
static vk::DeviceSize getAlignment(vk::DeviceSize instanceSize,
|
||||||
vk::DeviceSize minOffsetAlignment);
|
vk::DeviceSize minOffsetAlignment);
|
||||||
@@ -55,9 +60,10 @@ class Buffer {
|
|||||||
void* mapped = nullptr;
|
void* mapped = nullptr;
|
||||||
vk::Buffer buffer = VK_NULL_HANDLE;
|
vk::Buffer buffer = VK_NULL_HANDLE;
|
||||||
VmaAllocation allocation = VK_NULL_HANDLE;
|
VmaAllocation allocation = VK_NULL_HANDLE;
|
||||||
|
VmaAllocationInfo info;
|
||||||
|
|
||||||
vk::DeviceSize bufferSize;
|
vk::DeviceSize bufferSize;
|
||||||
uint32_t instanceCount;
|
u32 instanceCount;
|
||||||
vk::DeviceSize instanceSize;
|
vk::DeviceSize instanceSize;
|
||||||
vk::DeviceSize alignmentSize;
|
vk::DeviceSize alignmentSize;
|
||||||
vk::BufferUsageFlags usageFlags;
|
vk::BufferUsageFlags usageFlags;
|
||||||
|
|||||||
@@ -184,7 +184,8 @@ vk::Format Device::findSupportedFormat(
|
|||||||
|
|
||||||
void Device::createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage,
|
void Device::createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage,
|
||||||
vk::MemoryPropertyFlags properties,
|
vk::MemoryPropertyFlags properties,
|
||||||
vk::Buffer& buffer, VmaAllocation& allocation) {
|
vk::Buffer& buffer, VmaAllocation& allocation,
|
||||||
|
VmaAllocationInfo& info) {
|
||||||
vk::BufferCreateInfo bufferInfo = {};
|
vk::BufferCreateInfo bufferInfo = {};
|
||||||
bufferInfo.size = size;
|
bufferInfo.size = size;
|
||||||
bufferInfo.usage = usage;
|
bufferInfo.usage = usage;
|
||||||
@@ -196,7 +197,7 @@ void Device::createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage,
|
|||||||
VkBuffer rawBuffer = VK_NULL_HANDLE;
|
VkBuffer rawBuffer = VK_NULL_HANDLE;
|
||||||
VkResult result = vmaCreateBuffer(
|
VkResult result = vmaCreateBuffer(
|
||||||
this->allocator, &static_cast<const VkBufferCreateInfo&>(bufferInfo),
|
this->allocator, &static_cast<const VkBufferCreateInfo&>(bufferInfo),
|
||||||
&allocInfo, &rawBuffer, &allocation, nullptr);
|
&allocInfo, &rawBuffer, &allocation, &info);
|
||||||
if (result != VK_SUCCESS) {
|
if (result != VK_SUCCESS) {
|
||||||
log::fatal("failed to create buffer. Error: {}",
|
log::fatal("failed to create buffer. Error: {}",
|
||||||
vk::to_string(static_cast<vk::Result>(result)));
|
vk::to_string(static_cast<vk::Result>(result)));
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ class Device {
|
|||||||
|
|
||||||
void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage,
|
void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage,
|
||||||
vk::MemoryPropertyFlags properties, vk::Buffer& buffer,
|
vk::MemoryPropertyFlags properties, vk::Buffer& buffer,
|
||||||
VmaAllocation& allocation);
|
VmaAllocation& allocation, VmaAllocationInfo& info);
|
||||||
|
|
||||||
vk::CommandBuffer beginSingleTimeCommands();
|
vk::CommandBuffer beginSingleTimeCommands();
|
||||||
|
|
||||||
|
|||||||
@@ -226,11 +226,12 @@ std::unique_ptr<Image> Image::Builder::build() {
|
|||||||
|
|
||||||
vk::Buffer stagingBuffer;
|
vk::Buffer stagingBuffer;
|
||||||
VmaAllocation stagingAllocation;
|
VmaAllocation stagingAllocation;
|
||||||
this->device.createBuffer(this->initialDataSize,
|
VmaAllocationInfo stagingAllocationInfo;
|
||||||
vk::BufferUsageFlagBits::eTransferSrc,
|
this->device.createBuffer(
|
||||||
vk::MemoryPropertyFlagBits::eHostVisible |
|
this->initialDataSize, vk::BufferUsageFlagBits::eTransferSrc,
|
||||||
vk::MemoryPropertyFlagBits::eHostCoherent,
|
vk::MemoryPropertyFlagBits::eHostVisible |
|
||||||
stagingBuffer, stagingAllocation);
|
vk::MemoryPropertyFlagBits::eHostCoherent,
|
||||||
|
stagingBuffer, stagingAllocation, stagingAllocationInfo);
|
||||||
|
|
||||||
void* mapped = nullptr;
|
void* mapped = nullptr;
|
||||||
vmaMapMemory(allocator, stagingAllocation, &mapped);
|
vmaMapMemory(allocator, stagingAllocation, &mapped);
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# sandbox
|
||||||
|
|
||||||
|
A playground for working out the implementation and architecture of new engine features. It can include the engine's internal backend headers (`engine/src`) as well as the public API, so you can use classes such as `Device`, `RenderContext`, `RenderGraph` and `SceneRenderer` directly.
|
||||||
|
|
||||||
|
Prototype a feature here first. Once its design settles, move it into the engine and expose it through the public API, which [testbed](../testbed/README.md) exercises.
|
||||||
|
|
||||||
|
Any `.cpp` file in this directory is compiled in automatically.
|
||||||
|
|
||||||
|
## Running
|
||||||
|
|
||||||
|
```sh
|
||||||
|
dev sandbox [args...]
|
||||||
|
```
|
||||||
|
|
||||||
|
This builds the `debug` preset and runs `build/debug/bin/sandbox`. Shaders are loaded from `shaders/` (`SANDBOX_SHADER_DIR`).
|
||||||
+152
-226
@@ -1,295 +1,221 @@
|
|||||||
#define GLFW_INCLUDE_VULKAN
|
#define GLFW_INCLUDE_VULKAN
|
||||||
#include <GLFW/glfw3.h>
|
#include <GLFW/glfw3.h>
|
||||||
#include <imgui.h>
|
#include <spirv_reflect.h>
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <array>
|
|
||||||
#include <chrono>
|
|
||||||
#include <cmath>
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstdio>
|
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <glm/glm.hpp>
|
|
||||||
#include <glm/gtc/matrix_transform.hpp>
|
|
||||||
#include <glm/mat4x4.hpp>
|
|
||||||
#include <glm/vec4.hpp>
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <map>
|
#include <sophia/components.hpp>
|
||||||
#include <memory>
|
|
||||||
#include <optional>
|
|
||||||
#include <set>
|
|
||||||
#include <sophia/application.hpp>
|
|
||||||
#include <sophia/canvas.hpp>
|
|
||||||
#include <sophia/console/console_command_registry.hpp>
|
|
||||||
#include <sophia/frame_info.hpp>
|
|
||||||
#include <sophia/math.hpp>
|
|
||||||
#include <sophia/resource_registry_ref.hpp>
|
|
||||||
#include <sophia/scene_registry.hpp>
|
#include <sophia/scene_registry.hpp>
|
||||||
#include <sophia/scene_view.hpp>
|
#include <sophia/scene_view.hpp>
|
||||||
#include <sophia/scene_view_registry.hpp>
|
|
||||||
#include <sophia/types.hpp>
|
|
||||||
#include <sophia/ui/debug_ui.hpp>
|
|
||||||
#include <sophia/ui/panel_registry.hpp>
|
|
||||||
#include <sophia/ui/ui.hpp>
|
|
||||||
#include <sophia/util/logger.hpp>
|
|
||||||
#include <string>
|
|
||||||
#include <tracy/Tracy.hpp>
|
#include <tracy/Tracy.hpp>
|
||||||
#include <utility>
|
|
||||||
#include <vector>
|
|
||||||
#include <vulkan/vulkan.hpp>
|
#include <vulkan/vulkan.hpp>
|
||||||
|
|
||||||
#include "renderer/imgui_renderer.hpp"
|
|
||||||
#include "renderer/model_render_system.hpp"
|
|
||||||
#include "renderer/render_context.hpp"
|
#include "renderer/render_context.hpp"
|
||||||
#include "renderer/render_graph.hpp"
|
#include "renderer/render_graph.hpp"
|
||||||
#include "renderer/scene_renderer.hpp"
|
#include "renderer/scene_renderer.hpp"
|
||||||
#include "resources/mesh.hpp"
|
|
||||||
#include "resources/render_target.hpp"
|
|
||||||
#include "resources/resource_registry.hpp"
|
#include "resources/resource_registry.hpp"
|
||||||
#include "vulkan/buffer.hpp"
|
#include "vulkan/buffer.hpp"
|
||||||
#include "vulkan/device.hpp"
|
#include "vulkan/device.hpp"
|
||||||
#include "vulkan/pipelines/graphics_pipeline.hpp"
|
|
||||||
#include "vulkan/shader.hpp"
|
#include "vulkan/shader.hpp"
|
||||||
#include "window.hpp"
|
#include "window.hpp"
|
||||||
|
|
||||||
|
// local includes
|
||||||
|
#include "material.hpp"
|
||||||
|
|
||||||
constexpr uint32_t WIDTH = 1280;
|
constexpr uint32_t WIDTH = 1280;
|
||||||
constexpr uint32_t HEIGHT = 720;
|
constexpr uint32_t HEIGHT = 720;
|
||||||
constexpr const char* NAME = "sandbox";
|
constexpr const char* NAME = "sandbox";
|
||||||
|
|
||||||
constexpr sophia::ClearColor CLEAR_COLOR = {0.01f, 0.01f, 0.02f, 1.0f};
|
void immediate_submit(sophia::Device& device,
|
||||||
|
std::function<void(VkCommandBuffer cmd)>&& function) {
|
||||||
bool hasFlag(sophia::ClearFlags flags, sophia::ClearFlags flag) {
|
auto command = device.beginSingleTimeCommands();
|
||||||
return (static_cast<sophia::u8>(flags) & static_cast<sophia::u8>(flag)) != 0;
|
function(command);
|
||||||
|
device.endSingleTimeCommands(command);
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, const char** argv) {
|
void triangle_test() {
|
||||||
std::vector<std::string> args;
|
|
||||||
args.assign(argv, argv + argc);
|
|
||||||
|
|
||||||
std::cout << __FILE__ << "::" << __LINE__ << '\n';
|
|
||||||
|
|
||||||
sophia::Window window{WIDTH, HEIGHT, NAME};
|
sophia::Window window{WIDTH, HEIGHT, NAME};
|
||||||
sophia::Device device{&window};
|
sophia::Device device{&window};
|
||||||
sophia::RenderContext renderContext{window, device};
|
sophia::RenderContext renderContext{window, device};
|
||||||
sophia::ResourceRegistry resourceRegistry{
|
sophia::ResourceRegistry resourceRegistry{device,
|
||||||
device, renderContext.getImageFormat()}; // remove?
|
renderContext.getImageFormat()};
|
||||||
|
|
||||||
// remove?
|
|
||||||
std::vector<std::unique_ptr<sophia::Mesh>> meshes;
|
|
||||||
|
|
||||||
// remove?
|
|
||||||
std::vector<sophia::ModelRenderSystem::MeshInstance> drawCalls;
|
|
||||||
|
|
||||||
// todo: figure out which to remove
|
|
||||||
sophia::PanelRegistry panelRegistry;
|
|
||||||
sophia::SceneRegistry sceneRegistry;
|
sophia::SceneRegistry sceneRegistry;
|
||||||
sophia::SceneViewRegistry sceneViewRegistry;
|
|
||||||
sophia::ConsoleCommandRegistry consoleCommandRegistry;
|
|
||||||
|
|
||||||
sophia::DebugUI debugUI{panelRegistry, consoleCommandRegistry};
|
|
||||||
debugUI.showFrameStats(true);
|
|
||||||
|
|
||||||
sophia::ModelRenderSystem modelRenderSystem{device, renderContext};
|
|
||||||
sophia::SceneRenderer sceneRenderer{sceneRegistry, resourceRegistry, device,
|
sophia::SceneRenderer sceneRenderer{sceneRegistry, resourceRegistry, device,
|
||||||
renderContext.getImageFormat()};
|
renderContext.getImageFormat()};
|
||||||
sophia::ImGuiRenderer imguiRenderer{
|
|
||||||
window, device, renderContext.getImageFormat(),
|
|
||||||
renderContext.getImageCount(), resourceRegistry};
|
|
||||||
|
|
||||||
consoleCommandRegistry.registerCommand(
|
sophia::SceneHandle sceneHandle = sceneRegistry.create();
|
||||||
"reset_layout", "Restore the default panel layout",
|
sophia::Scene& scene = sceneRegistry.get(sceneHandle);
|
||||||
[&imguiRenderer](const std::vector<std::string>&) {
|
sophia::Entity camera = scene.createCamera();
|
||||||
imguiRenderer.resetLayout();
|
|
||||||
return std::string{"Layout reset"};
|
|
||||||
});
|
|
||||||
|
|
||||||
auto& meshPool = resourceRegistry.meshPool;
|
// {
|
||||||
auto& renderTargetPool = resourceRegistry.renderTargetPool;
|
// auto& transform = camera.getComponent<sophia::TransformComponent>();
|
||||||
|
// transform.position.z += 60;
|
||||||
|
// }
|
||||||
|
|
||||||
imguiRenderer.setDefaultLayout(panelRegistry.getDefaultLayout());
|
sophia::SceneView view;
|
||||||
|
view.scene = sceneHandle;
|
||||||
|
view.clearColor = {0.01f, 0.01f, 0.02f, 1.0f};
|
||||||
|
view.camera = camera;
|
||||||
|
|
||||||
// Views already warned about, so each warns once.
|
auto vertexShader = sophia::Shader::Builder()
|
||||||
std::set<sophia::SceneViewHandle> viewsWithoutCamera;
|
.fromGLSL(SANDBOX_SHADER_DIR "/triangle.vert")
|
||||||
std::set<sophia::SceneViewHandle> viewsOverwritingOthers;
|
.setStage(sophia::ShaderStage::VERTEX)
|
||||||
|
.build();
|
||||||
|
auto fragmentShader = sophia::Shader::Builder()
|
||||||
|
.fromGLSL(SANDBOX_SHADER_DIR "/triangle.frag")
|
||||||
|
.setStage(sophia::ShaderStage::FRAGMENT)
|
||||||
|
.build();
|
||||||
|
vk::Format depthFormat = resourceRegistry.renderTargetPool.getDepthFormat();
|
||||||
|
sophia::Material material{device, std::move(vertexShader),
|
||||||
|
std::move(fragmentShader),
|
||||||
|
renderContext.getImageFormat(), depthFormat};
|
||||||
|
|
||||||
auto startTime = std::chrono::high_resolution_clock::now();
|
struct TriangleVertex {
|
||||||
auto currentTime = startTime;
|
sophia::vec4 vertex;
|
||||||
sophia::u32 frameIndex = 0;
|
sophia::vec4 color;
|
||||||
|
};
|
||||||
|
|
||||||
|
TriangleVertex verticies[] = {
|
||||||
|
{{-1.f, 1.f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f, 0.0f}},
|
||||||
|
{{1.f, 1.f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f, 0.0f}},
|
||||||
|
{{0.f, -1.f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f, 0.0f}}};
|
||||||
|
sophia::u32 triangleVerticiesCount = 3;
|
||||||
|
|
||||||
|
sophia::Buffer triangleBuffer{device, sizeof(TriangleVertex),
|
||||||
|
triangleVerticiesCount,
|
||||||
|
vk::BufferUsageFlagBits::eShaderDeviceAddress,
|
||||||
|
vk::MemoryPropertyFlagBits::eHostVisible |
|
||||||
|
vk::MemoryPropertyFlagBits::eHostCoherent};
|
||||||
|
triangleBuffer.map();
|
||||||
|
|
||||||
|
triangleBuffer.writeToBuffer(verticies);
|
||||||
|
|
||||||
|
vk::DeviceAddress address = triangleBuffer.getDeviceAddress();
|
||||||
|
|
||||||
while (!window.shouldClose()) {
|
while (!window.shouldClose()) {
|
||||||
glfwPollEvents();
|
glfwPollEvents();
|
||||||
|
|
||||||
auto newTime = std::chrono::high_resolution_clock::now();
|
|
||||||
double deltaTime =
|
|
||||||
std::chrono::duration<double>(newTime - currentTime).count();
|
|
||||||
currentTime = newTime;
|
|
||||||
|
|
||||||
double elapsedTime =
|
|
||||||
std::chrono::duration<double>(currentTime - startTime).count();
|
|
||||||
|
|
||||||
// Resizing waits for the GPU, so it has to happen before any of this
|
|
||||||
// frame's work (or ImGui's references to the targets) is recorded.
|
|
||||||
renderTargetPool.applyPendingResizes();
|
|
||||||
|
|
||||||
vk::Extent2D extent = renderContext.getExtent();
|
|
||||||
sophia::FrameInfo frameInfo{frameIndex, elapsedTime, deltaTime,
|
|
||||||
glm::vec2(static_cast<float>(extent.width),
|
|
||||||
static_cast<float>(extent.height))};
|
|
||||||
|
|
||||||
// Canvas draws in window pixels, origin at the center.
|
|
||||||
// TODO: replace with Debug/Gizmos drawn into scene views through their
|
|
||||||
// cameras.
|
|
||||||
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) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
imguiRenderer.beginFrame();
|
|
||||||
sceneRenderer.beginFrame(renderContext.getFrameIndex());
|
sceneRenderer.beginFrame(renderContext.getFrameIndex());
|
||||||
|
|
||||||
panelRegistry.update(frameInfo);
|
|
||||||
|
|
||||||
std::vector<sophia::ModelRenderSystem::MeshInstance> instances;
|
|
||||||
instances.reserve(meshes.size() + drawCalls.size());
|
|
||||||
for (const std::unique_ptr<sophia::Mesh>& mesh : meshes) {
|
|
||||||
instances.push_back({mesh.get(), glm::mat4{1.f}});
|
|
||||||
}
|
|
||||||
instances.insert(instances.end(), drawCalls.begin(), drawCalls.end());
|
|
||||||
drawCalls.clear();
|
|
||||||
|
|
||||||
sophia::RenderGraph graph{renderContext.getTracyContext()};
|
sophia::RenderGraph graph{renderContext.getTracyContext()};
|
||||||
sophia::ImageRef backbuffer = graph.importImage(
|
sophia::ImageRef color = graph.importImage(renderContext.getCurrentImage(),
|
||||||
renderContext.getCurrentImage(), sophia::Access::Present);
|
sophia::Access::Present);
|
||||||
|
sophia::ImageRef depth = graph.importImage(
|
||||||
struct ViewTarget {
|
renderContext.getCurrentDepthImage(), sophia::Access::DepthWrite);
|
||||||
sophia::ImageRef color;
|
|
||||||
sophia::ImageRef depth;
|
|
||||||
};
|
|
||||||
std::map<sophia::RenderTargetHandle, ViewTarget> viewTargets;
|
|
||||||
// Views without a target render straight into the backbuffer.
|
|
||||||
std::optional<ViewTarget> swapchainTarget;
|
|
||||||
for (sophia::SceneViewHandle handle : sceneViewRegistry.GetRenderQueue()) {
|
|
||||||
const sophia::SceneView& view = sceneViewRegistry.peek(handle);
|
|
||||||
bool toSwapchain = !view.target.isValid();
|
|
||||||
if (!sceneRegistry.isAlive(view.scene) ||
|
|
||||||
!sceneRegistry.get(view.scene).isVisible() ||
|
|
||||||
(!toSwapchain && !renderTargetPool.isAlive(view.target))) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
ViewTarget target;
|
|
||||||
vk::Extent2D targetExtent;
|
|
||||||
// Whether this is the first view this frame to render to its target.
|
|
||||||
bool firstOnTarget = false;
|
|
||||||
if (toSwapchain) {
|
|
||||||
firstOnTarget = !swapchainTarget;
|
|
||||||
if (!swapchainTarget) {
|
|
||||||
swapchainTarget = {
|
|
||||||
backbuffer,
|
|
||||||
graph.importImage(renderContext.getCurrentDepthImage(),
|
|
||||||
sophia::Access::DepthWrite)};
|
|
||||||
}
|
|
||||||
target = *swapchainTarget;
|
|
||||||
targetExtent = renderContext.getExtent();
|
|
||||||
} else {
|
|
||||||
sophia::RenderTarget& renderTarget = renderTargetPool.get(view.target);
|
|
||||||
auto [it, inserted] = viewTargets.try_emplace(view.target);
|
|
||||||
firstOnTarget = inserted;
|
|
||||||
if (inserted) {
|
|
||||||
it->second = {graph.importImage(renderTarget.getImage(),
|
|
||||||
sophia::Access::SampledRead),
|
|
||||||
graph.importImage(renderTarget.getDepthImage(),
|
|
||||||
sophia::Access::DepthWrite)};
|
|
||||||
}
|
|
||||||
target = it->second;
|
|
||||||
targetExtent = renderTarget.getExtent();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!firstOnTarget &&
|
|
||||||
hasFlag(view.clearFlags, sophia::ClearFlags::Color) &&
|
|
||||||
viewsOverwritingOthers.insert(handle).second) {
|
|
||||||
sophia::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 sophia::vec4& color = view.clearColor;
|
|
||||||
graph.addPass(
|
|
||||||
"scene view " + std::to_string(handle.index),
|
|
||||||
[&](sophia::RenderGraph::PassBuilder& pass) {
|
|
||||||
pass.writeColor(target.color,
|
|
||||||
hasFlag(view.clearFlags, sophia::ClearFlags::Color)
|
|
||||||
? vk::AttachmentLoadOp::eClear
|
|
||||||
: vk::AttachmentLoadOp::eLoad,
|
|
||||||
{color.r, color.g, color.b, color.a});
|
|
||||||
pass.writeDepth(target.depth,
|
|
||||||
hasFlag(view.clearFlags, sophia::ClearFlags::Depth)
|
|
||||||
? vk::AttachmentLoadOp::eClear
|
|
||||||
: vk::AttachmentLoadOp::eLoad);
|
|
||||||
},
|
|
||||||
[&sceneRenderer, &sceneRegistry, &sceneViewRegistry,
|
|
||||||
&viewsWithoutCamera, handle,
|
|
||||||
targetExtent](vk::CommandBuffer commandBuffer) {
|
|
||||||
ZoneScopedN("scene view pass");
|
|
||||||
const sophia::SceneView& view = sceneViewRegistry.peek(handle);
|
|
||||||
sophia::Entity camera =
|
|
||||||
view.camera ? view.camera
|
|
||||||
: sceneRegistry.get(view.scene).getActiveCamera();
|
|
||||||
// No camera to look through: the target is just cleared.
|
|
||||||
if (!camera) {
|
|
||||||
if (viewsWithoutCamera.insert(handle).second) {
|
|
||||||
sophia::log::warning(
|
|
||||||
"scene view has no camera and its scene has no active "
|
|
||||||
"camera; it will only be cleared (see "
|
|
||||||
"Scene::createCamera)");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
sceneRenderer.render(commandBuffer, view, camera, targetExtent);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
graph.addPass(
|
graph.addPass(
|
||||||
"main",
|
"sandbox",
|
||||||
[&](sophia::RenderGraph::PassBuilder& pass) {
|
[&](sophia::RenderGraph::PassBuilder& pass) {
|
||||||
// Keep what scene views drew into the backbuffer.
|
const sophia::vec4& c = view.clearColor;
|
||||||
pass.writeColor(backbuffer,
|
pass.writeColor(color, vk::AttachmentLoadOp::eClear,
|
||||||
swapchainTarget ? vk::AttachmentLoadOp::eLoad
|
{c.r, c.g, c.b, c.a});
|
||||||
: vk::AttachmentLoadOp::eClear,
|
pass.writeDepth(depth, vk::AttachmentLoadOp::eClear);
|
||||||
CLEAR_COLOR);
|
|
||||||
for (const auto& [handle, target] : viewTargets) {
|
|
||||||
pass.sample(target.color);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[&](vk::CommandBuffer commandBuffer) {
|
[&](vk::CommandBuffer commandBuffer) {
|
||||||
ZoneScopedN("main pass");
|
ZoneScopedN("main sandbox pass");
|
||||||
modelRenderSystem.render(commandBuffer, instances, canvasProjection);
|
material.bind(commandBuffer);
|
||||||
|
sophia::Material::PushConstants push;
|
||||||
|
push.data0 = address;
|
||||||
|
|
||||||
imguiRenderer.endFrame(commandBuffer);
|
commandBuffer.pushConstants(
|
||||||
|
material.getLayout(), vk::ShaderStageFlagBits::eVertex, 0,
|
||||||
|
sizeof(sophia::Material::PushConstants), &push);
|
||||||
|
commandBuffer.draw(3, 1, 0, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
graph.addPass(
|
||||||
|
"scene",
|
||||||
|
[&](sophia::RenderGraph::PassBuilder& pass) {
|
||||||
|
const sophia::vec4& c = view.clearColor;
|
||||||
|
pass.writeColor(color, vk::AttachmentLoadOp::eClear,
|
||||||
|
{c.r, c.g, c.b, c.a});
|
||||||
|
pass.writeDepth(depth, vk::AttachmentLoadOp::eClear);
|
||||||
|
},
|
||||||
|
[&](vk::CommandBuffer commandBuffer) {
|
||||||
|
ZoneScopedN("scene pass");
|
||||||
|
sceneRenderer.render(commandBuffer, view, view.camera,
|
||||||
|
renderContext.getExtent());
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
graph.execute(commandBuffer);
|
graph.execute(commandBuffer);
|
||||||
|
|
||||||
renderContext.endFrame();
|
renderContext.endFrame();
|
||||||
FrameMark;
|
FrameMark;
|
||||||
|
|
||||||
frameIndex++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
device.waitIdle();
|
device.waitIdle();
|
||||||
|
}
|
||||||
|
|
||||||
auto endTime = std::chrono::high_resolution_clock::now();
|
int main() {
|
||||||
double totalRuntime =
|
{
|
||||||
std::chrono::duration<double>(endTime - startTime).count();
|
using namespace sophia;
|
||||||
sophia::log::info("Application ran for {:.2f}s", totalRuntime);
|
|
||||||
|
|
||||||
|
auto shader = Shader::Builder()
|
||||||
|
.fromGLSL(SANDBOX_SHADER_DIR "/shader.comp")
|
||||||
|
.setStage(ShaderStage::VERTEX)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
std::vector<u32> spirv = shader.get()->getSpirv();
|
||||||
|
size_t spirv_size = spirv.size() * sizeof(u32);
|
||||||
|
void* spirv_code = spirv.data();
|
||||||
|
|
||||||
|
SpvReflectShaderModule module;
|
||||||
|
SpvReflectResult result =
|
||||||
|
spvReflectCreateShaderModule(spirv_size, spirv_code, &module);
|
||||||
|
assert(result == SPV_REFLECT_RESULT_SUCCESS);
|
||||||
|
|
||||||
|
{
|
||||||
|
log::print();
|
||||||
|
log::info("Reflecting Input Variables");
|
||||||
|
|
||||||
|
uint32_t count = 0;
|
||||||
|
result = spvReflectEnumerateInputVariables(&module, &count, NULL);
|
||||||
|
assert(result == SPV_REFLECT_RESULT_SUCCESS);
|
||||||
|
SpvReflectInterfaceVariable** vars =
|
||||||
|
(SpvReflectInterfaceVariable**)malloc(
|
||||||
|
count * sizeof(SpvReflectInterfaceVariable*));
|
||||||
|
result = spvReflectEnumerateInputVariables(&module, &count, vars);
|
||||||
|
assert(result == SPV_REFLECT_RESULT_SUCCESS);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < count; i++) {
|
||||||
|
SpvReflectInterfaceVariable* var = vars[i];
|
||||||
|
assert(var != nullptr);
|
||||||
|
|
||||||
|
std::cout << "{" << std::endl;
|
||||||
|
log::print("\tname: {},", var->name);
|
||||||
|
std::cout << "}" << std::endl;
|
||||||
|
std::cout << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
log::print();
|
||||||
|
log::info("Reflecting Output Variables");
|
||||||
|
|
||||||
|
u32 count = 0;
|
||||||
|
result = spvReflectEnumerateOutputVariables(&module, &count, NULL);
|
||||||
|
assert(result == SPV_REFLECT_RESULT_SUCCESS);
|
||||||
|
|
||||||
|
SpvReflectInterfaceVariable** vars =
|
||||||
|
(SpvReflectInterfaceVariable**)malloc(
|
||||||
|
count * sizeof(SpvReflectInterfaceVariable*));
|
||||||
|
|
||||||
|
result = spvReflectEnumerateOutputVariables(&module, &count, vars);
|
||||||
|
assert(result == SPV_REFLECT_RESULT_SUCCESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
spvReflectDestroyShaderModule(&module);
|
||||||
|
}
|
||||||
|
|
||||||
|
// triangle_test();
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
#include "material.hpp"
|
||||||
|
|
||||||
|
#include <sophia/util/logger.hpp>
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
Material::Material(Device& device, std::unique_ptr<Shader> vertexShader,
|
||||||
|
std::unique_ptr<Shader> fragmentShader,
|
||||||
|
vk::Format colorFormat, vk::Format depthFormat) {
|
||||||
|
createPipelineLayout(device);
|
||||||
|
this->pipeline = GraphicsPipeline::Builder(device)
|
||||||
|
.setVertexShader(*vertexShader)
|
||||||
|
.setFragmentShader(*fragmentShader)
|
||||||
|
.setPipelineLayout(this->pipelineLayout.get())
|
||||||
|
.setColorAttachmentFormats({colorFormat})
|
||||||
|
.setDepthAttachmentFormat(depthFormat)
|
||||||
|
.setDepthTest(true, true)
|
||||||
|
.setCullMode(vk::CullModeFlagBits::eBack)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
Material::~Material() { log::trace("destroyed sophia::Material"); }
|
||||||
|
|
||||||
|
void Material::createPipelineLayout(Device& device) {
|
||||||
|
vk::PushConstantRange pushConstantRange{};
|
||||||
|
pushConstantRange.stageFlags = vk::ShaderStageFlagBits::eVertex;
|
||||||
|
pushConstantRange.offset = 0;
|
||||||
|
pushConstantRange.size = sizeof(PushConstants);
|
||||||
|
|
||||||
|
vk::PipelineLayoutCreateInfo pipelineLayoutInfo = {};
|
||||||
|
pipelineLayoutInfo.setLayoutCount = 0;
|
||||||
|
pipelineLayoutInfo.pushConstantRangeCount = 1;
|
||||||
|
pipelineLayoutInfo.pPushConstantRanges = &pushConstantRange;
|
||||||
|
|
||||||
|
try {
|
||||||
|
this->pipelineLayout =
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <sophia/math.hpp>
|
||||||
|
#include <vulkan/vulkan.hpp>
|
||||||
|
|
||||||
|
#include "vulkan/device.hpp"
|
||||||
|
#include "vulkan/pipelines/graphics_pipeline.hpp"
|
||||||
|
#include "vulkan/shader.hpp"
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
class Material {
|
||||||
|
public:
|
||||||
|
struct PushConstants {
|
||||||
|
vk::DeviceAddress data0;
|
||||||
|
u64 padding;
|
||||||
|
vec4 data1;
|
||||||
|
vec4 data2;
|
||||||
|
vec4 data3;
|
||||||
|
};
|
||||||
|
|
||||||
|
Material(const Material&) = delete;
|
||||||
|
Material& operator=(const Material&) = delete;
|
||||||
|
|
||||||
|
Material(Device& device, std::unique_ptr<Shader> vertexShader,
|
||||||
|
std::unique_ptr<Shader> fragmentShader, vk::Format colorFormat,
|
||||||
|
vk::Format depthFormat);
|
||||||
|
~Material();
|
||||||
|
|
||||||
|
void bind(vk::CommandBuffer commandBuffer) const {
|
||||||
|
pipeline->bind(commandBuffer);
|
||||||
|
}
|
||||||
|
vk::PipelineLayout getLayout() const { return pipelineLayout.get(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void createPipelineLayout(Device& device);
|
||||||
|
|
||||||
|
vk::UniquePipelineLayout pipelineLayout;
|
||||||
|
std::unique_ptr<GraphicsPipeline> pipeline;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MaterialInstance {
|
||||||
|
public:
|
||||||
|
void setBaseColor();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Material* material;
|
||||||
|
|
||||||
|
vec3 base_color = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
// !NOTE: eventually add ecs support for ecs
|
||||||
|
// struct MaterialComponent {
|
||||||
|
// MaterialInstanceHandle handle;
|
||||||
|
// };
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
#include "renderer.hpp"
|
||||||
|
|
||||||
|
namespace sophia {} // namespace sophia
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <sophia/math.hpp>
|
||||||
|
#include <vulkan/vulkan.hpp>
|
||||||
|
|
||||||
|
#include "material.hpp"
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
struct RenderObject {
|
||||||
|
uint32_t indexCount;
|
||||||
|
uint32_t firstIndex;
|
||||||
|
vk::Buffer indexBuffer;
|
||||||
|
|
||||||
|
MaterialInstance* material;
|
||||||
|
|
||||||
|
mat4 transform;
|
||||||
|
VkDeviceAddress vertexBufferAddress;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Renderer {
|
||||||
|
public:
|
||||||
|
private:
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
#version 450
|
||||||
|
#extension GL_EXT_buffer_reference : require
|
||||||
|
|
||||||
|
struct TriangleVertex {
|
||||||
|
vec4 vertex;
|
||||||
|
vec4 color;
|
||||||
|
};
|
||||||
|
|
||||||
|
layout(buffer_reference, std430) readonly buffer TriangleVertexBuffer {
|
||||||
|
TriangleVertex vertices[];
|
||||||
|
};
|
||||||
|
|
||||||
|
layout( push_constant ) uniform constants {
|
||||||
|
TriangleVertexBuffer triangleBuffer;
|
||||||
|
vec2 padding;
|
||||||
|
vec4 data1;
|
||||||
|
vec4 data2;
|
||||||
|
vec4 data3;
|
||||||
|
} PushConstants;
|
||||||
|
|
||||||
|
layout (location = 0) out vec3 outColor;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
//const array of positions for the triangle
|
||||||
|
const vec3 positions[3] = vec3[3](
|
||||||
|
vec3(-1.f,1.f, 0.0f),
|
||||||
|
vec3(1.f,1.f, 0.0f),
|
||||||
|
vec3(0.f,-1.f, 0.0f)
|
||||||
|
);
|
||||||
|
|
||||||
|
//const array of colors for the triangle
|
||||||
|
const vec3 colors[3] = vec3[3](
|
||||||
|
vec3(1.0f, 0.0f, 0.0f), //red
|
||||||
|
vec3(0.0f, 1.0f, 0.0f), //green
|
||||||
|
vec3(00.f, 0.0f, 1.0f) //blue
|
||||||
|
);
|
||||||
|
|
||||||
|
// Load vertex data from device address
|
||||||
|
TriangleVertex v = PushConstants.triangleBuffer.vertices[gl_VertexIndex];
|
||||||
|
|
||||||
|
// output the position of each vertex
|
||||||
|
gl_Position = vec4(v.vertex.xyz, 1.0);
|
||||||
|
outColor = vec3(v.color.xyz);
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
//shader input
|
||||||
|
layout (location = 0) in vec3 inColor;
|
||||||
|
|
||||||
|
//output write
|
||||||
|
layout (location = 0) out vec4 outFragColor;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
//return red
|
||||||
|
outFragColor = vec4(inColor,1.0f);
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
#version 450
|
||||||
|
#extension GL_EXT_buffer_reference : require
|
||||||
|
|
||||||
|
struct TriangleVertex {
|
||||||
|
vec4 vertex;
|
||||||
|
vec4 color;
|
||||||
|
};
|
||||||
|
|
||||||
|
layout(buffer_reference, std430) readonly buffer TriangleVertexBuffer {
|
||||||
|
TriangleVertex vertices[];
|
||||||
|
};
|
||||||
|
|
||||||
|
layout( push_constant ) uniform constants {
|
||||||
|
TriangleVertexBuffer triangleBuffer;
|
||||||
|
vec2 padding;
|
||||||
|
vec4 data1;
|
||||||
|
vec4 data2;
|
||||||
|
vec4 data3;
|
||||||
|
} PushConstants;
|
||||||
|
|
||||||
|
layout (location = 0) out vec3 outColor;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
//const array of positions for the triangle
|
||||||
|
const vec3 positions[3] = vec3[3](
|
||||||
|
vec3(-1.f,1.f, 0.0f),
|
||||||
|
vec3(1.f,1.f, 0.0f),
|
||||||
|
vec3(0.f,-1.f, 0.0f)
|
||||||
|
);
|
||||||
|
|
||||||
|
//const array of colors for the triangle
|
||||||
|
const vec3 colors[3] = vec3[3](
|
||||||
|
vec3(1.0f, 0.0f, 0.0f), //red
|
||||||
|
vec3(0.0f, 1.0f, 0.0f), //green
|
||||||
|
vec3(00.f, 0.0f, 1.0f) //blue
|
||||||
|
);
|
||||||
|
|
||||||
|
// Load vertex data from device address
|
||||||
|
TriangleVertex v = PushConstants.triangleBuffer.vertices[gl_VertexIndex];
|
||||||
|
|
||||||
|
// output the position of each vertex
|
||||||
|
gl_Position = vec4(v.vertex.xyz, 1.0);
|
||||||
|
outColor = vec3(v.color.xyz);
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#version 460
|
||||||
|
|
||||||
|
//size of a workgroup for compute
|
||||||
|
layout (local_size_x = 16, local_size_y = 16) in;
|
||||||
|
|
||||||
|
//descriptor bindings for the pipeline
|
||||||
|
layout(rgba16f,set = 0, binding = 0) uniform image2D image;
|
||||||
|
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
ivec2 texelCoord = ivec2(gl_GlobalInvocationID.xy);
|
||||||
|
ivec2 size = imageSize(image);
|
||||||
|
|
||||||
|
if(texelCoord.x < size.x && texelCoord.y < size.y)
|
||||||
|
{
|
||||||
|
vec4 color = vec4(0.0, 0.0, 0.0, 1.0);
|
||||||
|
|
||||||
|
if(gl_LocalInvocationID.x != 0 && gl_LocalInvocationID.y != 0)
|
||||||
|
{
|
||||||
|
color.x = float(texelCoord.x)/(size.x);
|
||||||
|
color.y = float(texelCoord.y)/(size.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
imageStore(image, texelCoord, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# testbed
|
||||||
|
|
||||||
|
Exercises the engine's frontend API. It includes only the public headers in `engine/include/sophia/`, so it uses the engine the way a game would.
|
||||||
|
|
||||||
|
Use it to check that public API changes work and feel right from the caller's side. It cannot include engine internals (`engine/src`). If something needs them, it belongs in [sandbox](../sandbox/README.md) instead.
|
||||||
|
|
||||||
|
## Running
|
||||||
|
|
||||||
|
```sh
|
||||||
|
dev testbed [args...]
|
||||||
|
```
|
||||||
|
|
||||||
|
This builds the `debug` preset and runs `build/debug/bin/testbed`. Shaders are loaded from `shaders/` (`TESTBED_SHADER_DIR`).
|
||||||
Reference in New Issue
Block a user