initial commit
This commit is contained in:
26
source/addon.cpp
Normal file
26
source/addon.cpp
Normal file
@@ -0,0 +1,26 @@
|
||||
|
||||
#include "iwa/addon.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace iwa
|
||||
{
|
||||
namespace
|
||||
{
|
||||
std::vector<Addon*>& getAddonsVector() noexcept
|
||||
{
|
||||
static std::vector<Addon*> addons;
|
||||
return addons;
|
||||
}
|
||||
}
|
||||
|
||||
Addon::Addon()
|
||||
{
|
||||
getAddonsVector().push_back(this);
|
||||
}
|
||||
|
||||
std::span<Addon* const> getAddons() noexcept
|
||||
{
|
||||
return getAddonsVector();
|
||||
}
|
||||
} // namespace iwa
|
||||
20
source/addons/imgui/SConscript
Normal file
20
source/addons/imgui/SConscript
Normal file
@@ -0,0 +1,20 @@
|
||||
|
||||
Import('env')
|
||||
|
||||
# Imgui
|
||||
lib_imgui = env.Cook('imgui', backends = ['vulkan', 'sdl2'], git_ref = 'refs/tags/v1.90')
|
||||
|
||||
src_files = Split("""
|
||||
fps_widget.cpp
|
||||
""")
|
||||
add_src_files = [env.File('addon.cpp')]
|
||||
|
||||
lib_iwa_imgui = env.UnityStaticLibrary(
|
||||
target = env['LIB_DIR'] + '/iwa_imgui',
|
||||
source = src_files,
|
||||
add_source = add_src_files,
|
||||
dependencies = [env['lib_iwa'], lib_imgui]
|
||||
)
|
||||
env['lib_iwa_imgui'] = lib_iwa_imgui
|
||||
|
||||
Return('env')
|
||||
299
source/addons/imgui/addon.cpp
Normal file
299
source/addons/imgui/addon.cpp
Normal file
@@ -0,0 +1,299 @@
|
||||
|
||||
#include "iwa/addons/imgui/addon.hpp"
|
||||
|
||||
#include <imgui.h>
|
||||
#include <backends/imgui_impl_sdl2.h>
|
||||
#include <backends/imgui_impl_vulkan.h>
|
||||
|
||||
namespace iwa
|
||||
{
|
||||
namespace
|
||||
{
|
||||
ImGuiAddon gImguiAddon;
|
||||
|
||||
PFN_vkVoidFunction imguiLoaderCallback(const char* functionName, void* userData)
|
||||
{
|
||||
return static_cast<Instance*>(userData)->getVkHandle().getProcAddr(functionName);
|
||||
}
|
||||
|
||||
ImGuiKey keyToImGui(const KeyCode keyCode)
|
||||
{
|
||||
switch (keyCode)
|
||||
{
|
||||
//<editor-fold desc="Many cases">
|
||||
case KeyCode::TAB: return ImGuiKey_Tab;
|
||||
case KeyCode::LEFT: return ImGuiKey_LeftArrow;
|
||||
case KeyCode::RIGHT: return ImGuiKey_RightArrow;
|
||||
case KeyCode::UP: return ImGuiKey_UpArrow;
|
||||
case KeyCode::DOWN: return ImGuiKey_DownArrow;
|
||||
case KeyCode::PAGEUP: return ImGuiKey_PageUp;
|
||||
case KeyCode::PAGEDOWN: return ImGuiKey_PageDown;
|
||||
case KeyCode::HOME: return ImGuiKey_Home;
|
||||
case KeyCode::END: return ImGuiKey_End;
|
||||
case KeyCode::INSERT: return ImGuiKey_Insert;
|
||||
case KeyCode::DELETE: return ImGuiKey_Delete;
|
||||
case KeyCode::BACKSPACE: return ImGuiKey_Backspace;
|
||||
case KeyCode::SPACE: return ImGuiKey_Space;
|
||||
case KeyCode::RETURN: return ImGuiKey_Enter;
|
||||
case KeyCode::ESCAPE: return ImGuiKey_Escape;
|
||||
case KeyCode::QUOTE: return ImGuiKey_Apostrophe;
|
||||
case KeyCode::COMMA: return ImGuiKey_Comma;
|
||||
case KeyCode::MINUS: return ImGuiKey_Minus;
|
||||
case KeyCode::PERIOD: return ImGuiKey_Period;
|
||||
case KeyCode::SLASH: return ImGuiKey_Slash;
|
||||
case KeyCode::SEMICOLON: return ImGuiKey_Semicolon;
|
||||
case KeyCode::EQUALS: return ImGuiKey_Equal;
|
||||
case KeyCode::LEFTBRACKET: return ImGuiKey_LeftBracket;
|
||||
case KeyCode::BACKSLASH: return ImGuiKey_Backslash;
|
||||
case KeyCode::RIGHTBRACKET: return ImGuiKey_RightBracket;
|
||||
case KeyCode::BACKQUOTE: return ImGuiKey_GraveAccent;
|
||||
case KeyCode::CAPSLOCK: return ImGuiKey_CapsLock;
|
||||
case KeyCode::SCROLLLOCK: return ImGuiKey_ScrollLock;
|
||||
case KeyCode::NUMLOCKCLEAR: return ImGuiKey_NumLock;
|
||||
case KeyCode::PRINTSCREEN: return ImGuiKey_PrintScreen;
|
||||
case KeyCode::PAUSE: return ImGuiKey_Pause;
|
||||
case KeyCode::KP_0: return ImGuiKey_Keypad0;
|
||||
case KeyCode::KP_1: return ImGuiKey_Keypad1;
|
||||
case KeyCode::KP_2: return ImGuiKey_Keypad2;
|
||||
case KeyCode::KP_3: return ImGuiKey_Keypad3;
|
||||
case KeyCode::KP_4: return ImGuiKey_Keypad4;
|
||||
case KeyCode::KP_5: return ImGuiKey_Keypad5;
|
||||
case KeyCode::KP_6: return ImGuiKey_Keypad6;
|
||||
case KeyCode::KP_7: return ImGuiKey_Keypad7;
|
||||
case KeyCode::KP_8: return ImGuiKey_Keypad8;
|
||||
case KeyCode::KP_9: return ImGuiKey_Keypad9;
|
||||
case KeyCode::KP_PERIOD: return ImGuiKey_KeypadDecimal;
|
||||
case KeyCode::KP_DIVIDE: return ImGuiKey_KeypadDivide;
|
||||
case KeyCode::KP_MULTIPLY: return ImGuiKey_KeypadMultiply;
|
||||
case KeyCode::KP_MINUS: return ImGuiKey_KeypadSubtract;
|
||||
case KeyCode::KP_PLUS: return ImGuiKey_KeypadAdd;
|
||||
case KeyCode::KP_ENTER: return ImGuiKey_KeypadEnter;
|
||||
case KeyCode::KP_EQUALS: return ImGuiKey_KeypadEqual;
|
||||
case KeyCode::LCTRL: return ImGuiKey_LeftCtrl;
|
||||
case KeyCode::LSHIFT: return ImGuiKey_LeftShift;
|
||||
case KeyCode::LALT: return ImGuiKey_LeftAlt;
|
||||
case KeyCode::LGUI: return ImGuiKey_LeftSuper;
|
||||
case KeyCode::RCTRL: return ImGuiKey_RightCtrl;
|
||||
case KeyCode::RSHIFT: return ImGuiKey_RightShift;
|
||||
case KeyCode::RALT: return ImGuiKey_RightAlt;
|
||||
case KeyCode::RGUI: return ImGuiKey_RightSuper;
|
||||
case KeyCode::APPLICATION: return ImGuiKey_Menu;
|
||||
case KeyCode::_0: return ImGuiKey_0;
|
||||
case KeyCode::_1: return ImGuiKey_1;
|
||||
case KeyCode::_2: return ImGuiKey_2;
|
||||
case KeyCode::_3: return ImGuiKey_3;
|
||||
case KeyCode::_4: return ImGuiKey_4;
|
||||
case KeyCode::_5: return ImGuiKey_5;
|
||||
case KeyCode::_6: return ImGuiKey_6;
|
||||
case KeyCode::_7: return ImGuiKey_7;
|
||||
case KeyCode::_8: return ImGuiKey_8;
|
||||
case KeyCode::_9: return ImGuiKey_9;
|
||||
case KeyCode::A: return ImGuiKey_A;
|
||||
case KeyCode::B: return ImGuiKey_B;
|
||||
case KeyCode::C: return ImGuiKey_C;
|
||||
case KeyCode::D: return ImGuiKey_D;
|
||||
case KeyCode::E: return ImGuiKey_E;
|
||||
case KeyCode::F: return ImGuiKey_F;
|
||||
case KeyCode::G: return ImGuiKey_G;
|
||||
case KeyCode::H: return ImGuiKey_H;
|
||||
case KeyCode::I: return ImGuiKey_I;
|
||||
case KeyCode::J: return ImGuiKey_J;
|
||||
case KeyCode::K: return ImGuiKey_K;
|
||||
case KeyCode::L: return ImGuiKey_L;
|
||||
case KeyCode::M: return ImGuiKey_M;
|
||||
case KeyCode::N: return ImGuiKey_N;
|
||||
case KeyCode::O: return ImGuiKey_O;
|
||||
case KeyCode::P: return ImGuiKey_P;
|
||||
case KeyCode::Q: return ImGuiKey_Q;
|
||||
case KeyCode::R: return ImGuiKey_R;
|
||||
case KeyCode::S: return ImGuiKey_S;
|
||||
case KeyCode::T: return ImGuiKey_T;
|
||||
case KeyCode::U: return ImGuiKey_U;
|
||||
case KeyCode::V: return ImGuiKey_V;
|
||||
case KeyCode::W: return ImGuiKey_W;
|
||||
case KeyCode::X: return ImGuiKey_X;
|
||||
case KeyCode::Y: return ImGuiKey_Y;
|
||||
case KeyCode::Z: return ImGuiKey_Z;
|
||||
case KeyCode::F1: return ImGuiKey_F1;
|
||||
case KeyCode::F2: return ImGuiKey_F2;
|
||||
case KeyCode::F3: return ImGuiKey_F3;
|
||||
case KeyCode::F4: return ImGuiKey_F4;
|
||||
case KeyCode::F5: return ImGuiKey_F5;
|
||||
case KeyCode::F6: return ImGuiKey_F6;
|
||||
case KeyCode::F7: return ImGuiKey_F7;
|
||||
case KeyCode::F8: return ImGuiKey_F8;
|
||||
case KeyCode::F9: return ImGuiKey_F9;
|
||||
case KeyCode::F10: return ImGuiKey_F10;
|
||||
case KeyCode::F11: return ImGuiKey_F11;
|
||||
case KeyCode::F12: return ImGuiKey_F12;
|
||||
case KeyCode::F13: return ImGuiKey_F13;
|
||||
case KeyCode::F14: return ImGuiKey_F14;
|
||||
case KeyCode::F15: return ImGuiKey_F15;
|
||||
case KeyCode::F16: return ImGuiKey_F16;
|
||||
case KeyCode::F17: return ImGuiKey_F17;
|
||||
case KeyCode::F18: return ImGuiKey_F18;
|
||||
case KeyCode::F19: return ImGuiKey_F19;
|
||||
case KeyCode::F20: return ImGuiKey_F20;
|
||||
case KeyCode::F21: return ImGuiKey_F21;
|
||||
case KeyCode::F22: return ImGuiKey_F22;
|
||||
case KeyCode::F23: return ImGuiKey_F23;
|
||||
case KeyCode::F24: return ImGuiKey_F24;
|
||||
case KeyCode::AC_BACK: return ImGuiKey_AppBack;
|
||||
case KeyCode::AC_FORWARD: return ImGuiKey_AppForward;
|
||||
default: return ImGuiKey_None;
|
||||
//</editor-fold>
|
||||
}
|
||||
}
|
||||
|
||||
ImGuiMouseButton mouseButtonToImGui(const MouseButton button)
|
||||
{
|
||||
switch (button)
|
||||
{
|
||||
case MouseButton::LEFT:
|
||||
return ImGuiMouseButton_Left;
|
||||
case MouseButton::RIGHT:
|
||||
return ImGuiMouseButton_Right;
|
||||
case MouseButton::MIDDLE:
|
||||
return ImGuiMouseButton_Middle;
|
||||
case MouseButton::EXTRA_1:
|
||||
return ImGuiMouseButton_Middle + 1;
|
||||
case MouseButton::EXTRA_2:
|
||||
return ImGuiMouseButton_Middle + 2;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
void ImGuiAddon::init(const AddonInitArgs& args)
|
||||
{
|
||||
(void) args;
|
||||
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
ImGui::StyleColorsDark();
|
||||
}
|
||||
|
||||
void ImGuiAddon::cleanup()
|
||||
{
|
||||
ImGui::DestroyContext();
|
||||
}
|
||||
|
||||
mijin::Task<> ImGuiAddon::c_createResources(const ImguiCreateResourcesArgs& args)
|
||||
{
|
||||
const unsigned QUEUED_FRAMES = 3; // TODO: is this okay?
|
||||
const unsigned MAX_IMAGES = 256;
|
||||
Device& device = *args.swapchain.getOwner();
|
||||
|
||||
mDescriptorPool = device.createChild<DescriptorPool>(DescriptorPoolCreationArgs{
|
||||
.flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet,
|
||||
.maxSets = MAX_IMAGES,
|
||||
.poolSizes = {
|
||||
vk::DescriptorPoolSize{.type = vk::DescriptorType::eCombinedImageSampler, .descriptorCount = MAX_IMAGES}
|
||||
}
|
||||
});
|
||||
registerObjectDestructionHandler(args.swapchain, [this, device = device.getPointer()]
|
||||
{
|
||||
// destroy the descriptor pool when the window is destroyed so we don't keep the device alive
|
||||
device->getVkHandle().waitIdle();
|
||||
ImGui_ImplVulkan_Shutdown();
|
||||
ImGui_ImplSDL2_Shutdown();
|
||||
mDescriptorPool = nullptr;
|
||||
mWidgets.clear();
|
||||
});
|
||||
|
||||
if (!ImGui_ImplSDL2_InitForVulkan(args.swapchain.getWindow()->getSDLWindow())) {
|
||||
throw std::runtime_error("Error initializing ImGui for SDL2.");
|
||||
}
|
||||
|
||||
ImGui_ImplVulkan_InitInfo initInfo{
|
||||
.Instance = device.getOwner()->getVkHandle(),
|
||||
.PhysicalDevice = device.getVkPhysicalDevice(),
|
||||
.Device = device.getVkHandle(),
|
||||
.QueueFamily = device.getDeviceInfo().graphicsQueueFamily,
|
||||
.Queue = device.getGraphicsQueue(),
|
||||
.PipelineCache = VK_NULL_HANDLE,
|
||||
.DescriptorPool = mDescriptorPool->getVkHandle(),
|
||||
.Subpass = 0,
|
||||
.MinImageCount = QUEUED_FRAMES,
|
||||
.ImageCount = QUEUED_FRAMES,
|
||||
.MSAASamples = VK_SAMPLE_COUNT_1_BIT,
|
||||
.UseDynamicRendering = true,
|
||||
.ColorAttachmentFormat = static_cast<VkFormat>(args.format),
|
||||
.Allocator = nullptr,
|
||||
.CheckVkResultFn = nullptr,
|
||||
};
|
||||
|
||||
const bool success = ImGui_ImplVulkan_LoadFunctions(imguiLoaderCallback, device.getOwner())
|
||||
&& ImGui_ImplVulkan_Init(&initInfo, VK_NULL_HANDLE);
|
||||
if (!success) {
|
||||
throw std::runtime_error("Error initializing ImGui for Vulkan.");
|
||||
}
|
||||
|
||||
// setup input
|
||||
args.swapchain.getWindow()->keyChanged.connect(*this, &ImGuiAddon::handleKeyChanged);
|
||||
args.swapchain.getWindow()->mouseButtonChanged.connect(*this, &ImGuiAddon::handleMouseButtonChanged);
|
||||
args.swapchain.getWindow()->mouseMoved.connect(*this, &ImGuiAddon::handleMouseMoved);
|
||||
args.swapchain.getWindow()->mouseScrolled.connect(*this, &ImGuiAddon::handleMouseScrolled);
|
||||
args.swapchain.getWindow()->textEntered.connect(*this, &ImGuiAddon::handleTextEntered);
|
||||
|
||||
// first frame
|
||||
beginFrame();
|
||||
co_return;
|
||||
}
|
||||
|
||||
void ImGuiAddon::renderFrame(vk::CommandBuffer cmdBuffer)
|
||||
{
|
||||
for (const std::unique_ptr<ImGuiWidget>& widget : mWidgets) {
|
||||
widget->draw();
|
||||
}
|
||||
ImGui::Render();
|
||||
ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), cmdBuffer);
|
||||
beginFrame();
|
||||
}
|
||||
|
||||
void ImGuiAddon::removeWidget(ImGuiWidget* widget)
|
||||
{
|
||||
auto it = std::ranges::find_if(mWidgets, [widget](const std::unique_ptr<ImGuiWidget>& widgetPtr)
|
||||
{
|
||||
return widgetPtr.get() == widget;
|
||||
});
|
||||
mWidgets.erase(it);
|
||||
}
|
||||
|
||||
void ImGuiAddon::beginFrame() noexcept
|
||||
{
|
||||
ImGui_ImplVulkan_NewFrame();
|
||||
ImGui_ImplSDL2_NewFrame();
|
||||
ImGui::NewFrame();
|
||||
}
|
||||
|
||||
void ImGuiAddon::handleKeyChanged(const KeyEvent& event)
|
||||
{
|
||||
ImGui::GetIO().AddKeyEvent(keyToImGui(event.keyCode), event.down);
|
||||
}
|
||||
|
||||
void ImGuiAddon::handleMouseButtonChanged(const MouseButtonEvent& event)
|
||||
{
|
||||
ImGui::GetIO().AddMouseButtonEvent(mouseButtonToImGui(event.button), event.down);
|
||||
}
|
||||
|
||||
void ImGuiAddon::handleMouseMoved(const MouseMoveEvent& event)
|
||||
{
|
||||
ImGui::GetIO().AddMousePosEvent(static_cast<float>(event.absoluteX), static_cast<float>(event.absoluteY));
|
||||
}
|
||||
|
||||
void ImGuiAddon::handleMouseScrolled(const MouseWheelEvent& event)
|
||||
{
|
||||
ImGui::GetIO().AddMouseWheelEvent(static_cast<float>(event.relativeX), static_cast<float>(event.relativeY));
|
||||
}
|
||||
|
||||
void ImGuiAddon::handleTextEntered(const TextInputEvent& event)
|
||||
{
|
||||
ImGui::GetIO().AddInputCharactersUTF8(event.text.c_str());
|
||||
}
|
||||
|
||||
ImGuiAddon& ImGuiAddon::get() noexcept
|
||||
{
|
||||
return gImguiAddon;
|
||||
}
|
||||
} // namespace iwa
|
||||
21
source/addons/imgui/fps_widget.cpp
Normal file
21
source/addons/imgui/fps_widget.cpp
Normal file
@@ -0,0 +1,21 @@
|
||||
|
||||
#include "iwa/addons/imgui/fps_widget.hpp"
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <imgui.h>
|
||||
|
||||
namespace iwa
|
||||
{
|
||||
void ImGuiFpsWidget::draw()
|
||||
{
|
||||
mFpsCalculator.tickFrame();
|
||||
|
||||
const std::string fpsText = fmt::format("{}", static_cast<unsigned>(mFpsCalculator.getFps()));
|
||||
const ImVec2 textSize = ImGui::CalcTextSize(fpsText.c_str());
|
||||
ImGui::SetNextWindowPos(ImVec2(0, 0));
|
||||
ImGui::SetNextWindowSize(ImVec2(textSize.x + 2 * ImGui::GetStyle().WindowPadding.x, 0));
|
||||
ImGui::Begin("#fps", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs);
|
||||
ImGui::TextUnformatted(fpsText.c_str());
|
||||
ImGui::End();
|
||||
}
|
||||
} // namespace iwa
|
||||
37
source/app/vulkan_application.cpp
Normal file
37
source/app/vulkan_application.cpp
Normal file
@@ -0,0 +1,37 @@
|
||||
|
||||
#include "iwa/app/vulkan_application.hpp"
|
||||
|
||||
#include <mijin/virtual_filesystem/filesystem.hpp>
|
||||
#include <mijin/virtual_filesystem/relative.hpp>
|
||||
|
||||
namespace iwa
|
||||
{
|
||||
VulkanApplication::VulkanApplication(const ApplicationCreationArgs& args, ObjectPtr<> owner) : super_t(std::move(owner))
|
||||
{
|
||||
mInstance = Instance::create(args.instanceArgs);
|
||||
mDevice = mInstance->createDevice(args.deviceArgs);
|
||||
mMainWindow = mInstance->createWindow(args.mainWindowArgs);
|
||||
|
||||
SwapchainCreationArgs swapchainCreationArgs = args.mainWindowSwapchainArgs;
|
||||
MIJIN_ASSERT(swapchainCreationArgs.window == nullptr, "Main window swapchain args shouldn't contain a window.");
|
||||
swapchainCreationArgs.window = mMainWindow;
|
||||
mMainWindowSwapchain = mDevice->createChild<Swapchain>(swapchainCreationArgs);
|
||||
|
||||
if (!args.assetPath.empty())
|
||||
{
|
||||
mInstance->getPrimaryFSAdapter().emplaceAdapter<mijin::RelativeFileSystemAdapter<mijin::OSFileSystemAdapter>>(
|
||||
/* root = */ fs::current_path() / "assets"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
int VulkanApplication::execute(int argc, char** argv) // NOLINT
|
||||
{
|
||||
(void) argc;
|
||||
(void) argv;
|
||||
mInstance->getMainTaskLoop().addTask(c_init());
|
||||
mInstance->getMainTaskLoop().runUntilDone();
|
||||
|
||||
return 0;
|
||||
}
|
||||
} // namespace iwa
|
||||
101
source/buffer.cpp
Normal file
101
source/buffer.cpp
Normal file
@@ -0,0 +1,101 @@
|
||||
|
||||
#include "iwa/buffer.hpp"
|
||||
|
||||
#include "iwa/device.hpp"
|
||||
|
||||
namespace iwa
|
||||
{
|
||||
Buffer::Buffer(ObjectPtr<Device> owner, const BufferCreationArgs& args) : super_t(std::move(owner))
|
||||
{
|
||||
mHandle = getOwner()->getVkHandle().createBuffer(vk::BufferCreateInfo
|
||||
{
|
||||
.flags = args.flags,
|
||||
.size = args.size,
|
||||
.usage = args.usage,
|
||||
.sharingMode = args.sharingMode,
|
||||
.queueFamilyIndexCount = static_cast<std::uint32_t>(args.queueFamilyIndices.size()),
|
||||
.pQueueFamilyIndices = args.queueFamilyIndices.data()
|
||||
});
|
||||
}
|
||||
|
||||
Buffer::~Buffer() noexcept
|
||||
{
|
||||
IWA_DELETE_DEVICE_OBJECT(getOwner(), mHandle, destroyBuffer);
|
||||
}
|
||||
|
||||
void Buffer::allocateMemory(HostVisible hostVisible, HostCoherent hostCoherent)
|
||||
{
|
||||
const vk::MemoryRequirements memoryRequirements = getOwner()->getVkHandle().getBufferMemoryRequirements(mHandle);
|
||||
const vk::MemoryPropertyFlags memoryFlags = hostVisible ?
|
||||
(vk::MemoryPropertyFlagBits::eHostVisible | (hostCoherent ? vk::MemoryPropertyFlagBits::eHostCoherent : vk::MemoryPropertyFlags())) :
|
||||
vk::MemoryPropertyFlagBits::eDeviceLocal;
|
||||
const std::optional<std::uint32_t> memoryTypeIdx = findMemoryType(*getOwner(), memoryRequirements, memoryFlags);
|
||||
if (!memoryTypeIdx.has_value())
|
||||
{
|
||||
throw std::runtime_error("Could not find a suitable memory type.");
|
||||
}
|
||||
|
||||
ObjectPtr<DeviceMemory> memory = getOwner()->allocateDeviceMemory(
|
||||
{
|
||||
.allocationSize = memoryRequirements.size,
|
||||
.memoryTypeIndex = memoryTypeIdx.value()
|
||||
});
|
||||
bindMemory(std::move(memory));
|
||||
}
|
||||
|
||||
void Buffer::bindMemory(ObjectPtr<DeviceMemory> memory, vk::DeviceSize offset)
|
||||
{
|
||||
mMemory = std::move(memory);
|
||||
getOwner()->getVkHandle().bindBufferMemory(mHandle, *mMemory, offset);
|
||||
}
|
||||
|
||||
mijin::Task<> Buffer::c_fill(std::uint32_t data, std::size_t bytes, std::size_t byteOffset)
|
||||
{
|
||||
ObjectPtr<CommandBuffer> cmdBufferPtr = getOwner()->beginScratchCommandBuffer();
|
||||
const vk::CommandBuffer cmdBuffer = *cmdBufferPtr;
|
||||
cmdBuffer.fillBuffer(mHandle, byteOffset, bytes, data);
|
||||
|
||||
co_await getOwner()->endScratchCommandBuffer(std::move(cmdBufferPtr));
|
||||
}
|
||||
|
||||
mijin::Task<> Buffer::c_copyFrom(vk::Buffer srcBuffer, vk::BufferCopy region)
|
||||
{
|
||||
ObjectPtr<CommandBuffer> cmdBufferPtr = getOwner()->beginScratchCommandBuffer();
|
||||
const vk::CommandBuffer cmdBuffer = *cmdBufferPtr;
|
||||
cmdBuffer.copyBuffer(srcBuffer, mHandle, region);
|
||||
|
||||
co_await getOwner()->endScratchCommandBuffer(std::move(cmdBufferPtr));
|
||||
}
|
||||
|
||||
mijin::Task<> Buffer::c_upload(const void* data, std::size_t bytes, std::size_t byteOffset)
|
||||
{
|
||||
// assert(bytes == SIZE_REST || bytes + byteOffset <= byteSize);
|
||||
// if (bytes == SIZE_REST) {
|
||||
// bytes = byteSize - byteOffset;
|
||||
// }
|
||||
// create scratch buffer
|
||||
vk::Device device = *getOwner();
|
||||
ObjectPtr<Buffer> scratchBuffer = getOwner()->createChild<Buffer>(BufferCreationArgs{
|
||||
.size = bytes,
|
||||
.usage = vk::BufferUsageFlagBits::eTransferSrc
|
||||
});
|
||||
scratchBuffer->allocateMemory(HostVisible::YES);
|
||||
|
||||
// copy to scratch buffer
|
||||
void* mapped = device.mapMemory(*scratchBuffer->getMemory(), 0, bytes);
|
||||
std::memcpy(mapped, data, bytes);
|
||||
device.unmapMemory(*scratchBuffer->getMemory());
|
||||
|
||||
// copy to actual buffer
|
||||
co_await c_copyFrom(*scratchBuffer, vk::BufferCopy{
|
||||
.srcOffset = 0,
|
||||
.dstOffset = byteOffset,
|
||||
.size = bytes
|
||||
});
|
||||
}
|
||||
|
||||
mijin::Task<> Buffer::c_upload(const mijin::TypelessBuffer& data, std::size_t byteOffset)
|
||||
{
|
||||
return c_upload(data.data(), data.byteSize(), byteOffset);
|
||||
}
|
||||
} // namespace iwa
|
||||
65
source/command.cpp
Normal file
65
source/command.cpp
Normal file
@@ -0,0 +1,65 @@
|
||||
|
||||
#include "iwa/command.hpp"
|
||||
|
||||
#include "iwa/device.hpp"
|
||||
|
||||
namespace iwa
|
||||
{
|
||||
CommandPool::CommandPool(ObjectPtr<Device> owner, CommandPoolCreationArgs args) : super_t(std::move(owner))
|
||||
{
|
||||
mHandle = getOwner()->getVkHandle().createCommandPool(vk::CommandPoolCreateInfo{
|
||||
.flags = args.flags,
|
||||
.queueFamilyIndex = args.queueFamilyIndex
|
||||
});
|
||||
}
|
||||
|
||||
CommandPool::~CommandPool() noexcept
|
||||
{
|
||||
IWA_DELETE_DEVICE_OBJECT(getOwner(), mHandle, destroyCommandPool);
|
||||
}
|
||||
|
||||
ObjectPtr<CommandBuffer> CommandPool::allocateCommandBuffer(const CommandBufferAllocateArgs& args)
|
||||
{
|
||||
vk::CommandBuffer commandBuffer;
|
||||
const vk::CommandBufferAllocateInfo allocateInfo
|
||||
{
|
||||
.commandPool = mHandle,
|
||||
.level = args.level,
|
||||
.commandBufferCount = 1
|
||||
};
|
||||
vk::resultCheck(getOwner()->getVkHandle().allocateCommandBuffers(&allocateInfo, &commandBuffer),
|
||||
"vkAllocateCommandBuffers failed");
|
||||
return createChild<CommandBuffer>(commandBuffer);
|
||||
}
|
||||
|
||||
// std::vector<vk::CommandBuffer> CommandPool::allocateCommandBuffers(
|
||||
// std::size_t count,
|
||||
// const CommandBufferAllocateArgs& args) const noexcept
|
||||
// {
|
||||
// return getOwner()->getVkHandle().allocateCommandBuffers(vk::CommandBufferAllocateInfo{
|
||||
// .commandPool = mHandle,
|
||||
// .level = args.level,
|
||||
// .commandBufferCount = static_cast<std::uint32_t>(count)
|
||||
// });
|
||||
// }
|
||||
|
||||
CommandBuffer::CommandBuffer(ObjectPtr<iwa::CommandPool> owner, vk::CommandBuffer handle)
|
||||
: super_t(std::move(owner)), MixinVulkanObject(handle)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
CommandBuffer::~CommandBuffer() noexcept
|
||||
{
|
||||
if (mHandle)
|
||||
{
|
||||
getOwner()->getOwner()->queueDelete([
|
||||
poolHandle = getOwner()->getVkHandle(),
|
||||
handle = mHandle,
|
||||
device = getOwner()->getOwner()->getVkHandle()]
|
||||
{
|
||||
device.freeCommandBuffers(poolHandle, handle);
|
||||
});
|
||||
}
|
||||
}
|
||||
} // namespace iwa
|
||||
90
source/descriptor_set.cpp
Normal file
90
source/descriptor_set.cpp
Normal file
@@ -0,0 +1,90 @@
|
||||
|
||||
#include "iwa/descriptor_set.hpp"
|
||||
|
||||
#include "iwa/device.hpp"
|
||||
|
||||
namespace iwa
|
||||
{
|
||||
|
||||
DescriptorSetLayout::DescriptorSetLayout(ObjectPtr<Device> owner, const DescriptorSetLayoutCreationArgs& args)
|
||||
: super_t(std::move(owner))
|
||||
{
|
||||
vk::DescriptorSetLayoutCreateInfo createInfo{
|
||||
.flags = args.flags,
|
||||
.bindingCount = static_cast<std::uint32_t>(args.bindings.size()),
|
||||
.pBindings = args.bindings.data()
|
||||
};
|
||||
vk::DescriptorSetLayoutBindingFlagsCreateInfo flagsInfo;
|
||||
if (!args.bindingFlags.empty())
|
||||
{
|
||||
MIJIN_ASSERT(args.bindings.size() == args.bindingFlags.size(), "Binding flags must be empty or same size as bindings.");
|
||||
flagsInfo.bindingCount = static_cast<std::uint32_t>(args.bindingFlags.size()),
|
||||
flagsInfo.pBindingFlags = args.bindingFlags.data();
|
||||
createInfo.pNext = &flagsInfo;
|
||||
}
|
||||
mHandle = getOwner()->getVkHandle().createDescriptorSetLayout(createInfo);
|
||||
}
|
||||
|
||||
DescriptorSetLayout::~DescriptorSetLayout() noexcept
|
||||
{
|
||||
IWA_DELETE_DEVICE_OBJECT(getOwner(), mHandle, destroyDescriptorSetLayout)
|
||||
}
|
||||
|
||||
DescriptorPool::DescriptorPool(ObjectPtr<Device> owner, const DescriptorPoolCreationArgs& args)
|
||||
: super_t(std::move(owner)), mCanFree(args.flags & vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet)
|
||||
{
|
||||
mHandle = getOwner()->getVkHandle().createDescriptorPool(vk::DescriptorPoolCreateInfo
|
||||
{
|
||||
.flags = args.flags,
|
||||
.maxSets = args.maxSets,
|
||||
.poolSizeCount = static_cast<std::uint32_t>(args.poolSizes.size()),
|
||||
.pPoolSizes = args.poolSizes.data()
|
||||
});
|
||||
}
|
||||
|
||||
DescriptorPool::~DescriptorPool() noexcept
|
||||
{
|
||||
IWA_DELETE_DEVICE_OBJECT(getOwner(), mHandle, destroyDescriptorPool);
|
||||
}
|
||||
|
||||
ObjectPtr<DescriptorSet> DescriptorPool::allocateDescriptorSet(const DescriptorSetAllocateArgs& args)
|
||||
{
|
||||
vk::DescriptorSet descriptorSet;
|
||||
vk::DescriptorSetAllocateInfo allocateInfo
|
||||
{
|
||||
.descriptorPool = mHandle,
|
||||
.descriptorSetCount = 1,
|
||||
.pSetLayouts = &args.layout->getVkHandle(),
|
||||
};
|
||||
vk::DescriptorSetVariableDescriptorCountAllocateInfo variableSetInfo;
|
||||
if (args.variableDescriptorCount > 0)
|
||||
{
|
||||
variableSetInfo.descriptorSetCount = 1;
|
||||
variableSetInfo.pDescriptorCounts = &args.variableDescriptorCount;
|
||||
allocateInfo.pNext = &variableSetInfo;
|
||||
}
|
||||
vk::resultCheck(getOwner()->getVkHandle().allocateDescriptorSets(&allocateInfo, &descriptorSet),
|
||||
"vkAllocateDescriptorSets failed");
|
||||
return createChild<DescriptorSet>(descriptorSet);
|
||||
}
|
||||
|
||||
DescriptorSet::DescriptorSet(ObjectPtr<DescriptorPool> owner, vk::DescriptorSet handle)
|
||||
: super_t(std::move(owner)), MixinVulkanObject(handle)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
DescriptorSet::~DescriptorSet() noexcept
|
||||
{
|
||||
if (mHandle && getOwner()->getCanFree())
|
||||
{
|
||||
getOwner()->getOwner()->queueDelete([
|
||||
poolHandle = getOwner()->getVkHandle(),
|
||||
handle = mHandle,
|
||||
device = getOwner()->getOwner()]
|
||||
{
|
||||
device->getVkHandle().freeDescriptorSets(poolHandle, handle);
|
||||
});
|
||||
}
|
||||
}
|
||||
} // namespace iwa
|
||||
381
source/device.cpp
Normal file
381
source/device.cpp
Normal file
@@ -0,0 +1,381 @@
|
||||
|
||||
#include "iwa/device.hpp"
|
||||
|
||||
#include "iwa/log.hpp"
|
||||
#include "iwa/instance.hpp"
|
||||
|
||||
namespace iwa
|
||||
{
|
||||
namespace
|
||||
{
|
||||
void buildDefaultDeviceExtensionList(std::vector<ExtensionInfo>& outExtensions) noexcept
|
||||
{
|
||||
outExtensions.push_back({.name = VK_KHR_SWAPCHAIN_EXTENSION_NAME, .required = true});
|
||||
}
|
||||
|
||||
bool checkQueueFamilies(const PhysicalDeviceInfo& deviceInfo, [[maybe_unused]] int& score)
|
||||
{
|
||||
return deviceInfo.graphicsQueueFamily != std::numeric_limits<std::uint32_t>::max()
|
||||
&& deviceInfo.computeQueueFamily != std::numeric_limits<std::uint32_t>::max();
|
||||
}
|
||||
|
||||
bool checkDeviceExtensions(const std::vector<ExtensionInfo>& extensions, const PhysicalDeviceInfo& deviceInfo, int& score)
|
||||
{
|
||||
auto isExtensionSupported = [&deviceInfo](const char* extension)
|
||||
{
|
||||
for (const vk::ExtensionProperties& props : deviceInfo.extensions)
|
||||
{
|
||||
if (std::strncmp(props.extensionName, extension, VK_MAX_EXTENSION_NAME_SIZE) == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
for (const ExtensionInfo& extInfo : extensions)
|
||||
{
|
||||
if (isExtensionSupported(extInfo.name))
|
||||
{
|
||||
score += 10;
|
||||
}
|
||||
else if (extInfo.required)
|
||||
{
|
||||
logVerbose("Vulkan device {} not supported as it is missing required extension {}.", deviceInfo.properties.deviceName, extInfo.name);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int scorePhysicalDevice(
|
||||
const PhysicalDeviceCriteria& deviceCriteria,
|
||||
const std::vector<ExtensionInfo>& extensions,
|
||||
const PhysicalDeviceInfo& deviceInfo)
|
||||
{
|
||||
int score = 0;
|
||||
|
||||
switch (deviceInfo.properties.deviceType)
|
||||
{
|
||||
case vk::PhysicalDeviceType::eDiscreteGpu:
|
||||
score = 1000;
|
||||
break;
|
||||
case vk::PhysicalDeviceType::eIntegratedGpu:
|
||||
score = 100;
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
#define CHECK_FEATURE_STRUCT(infoName, requiredName) \
|
||||
for (auto feature : deviceCriteria.requiredName) \
|
||||
{ \
|
||||
if (!(deviceInfo.infoName.*feature)) \
|
||||
{ \
|
||||
return -1; \
|
||||
} \
|
||||
}
|
||||
CHECK_FEATURE_STRUCT(features, requiredFeatures)
|
||||
CHECK_FEATURE_STRUCT(vulkan11Features, requiredVulkan11Features)
|
||||
CHECK_FEATURE_STRUCT(vulkan12Features, requiredVulkan12Features)
|
||||
CHECK_FEATURE_STRUCT(vulkan13Features, requiredVulkan13Features)
|
||||
CHECK_FEATURE_STRUCT(accelerationStructureFeatures, requiredAccelerationStructureFeatures)
|
||||
CHECK_FEATURE_STRUCT(rayTracingPipelineFeatures, requiredRayTracingPipelineFeatures)
|
||||
CHECK_FEATURE_STRUCT(meshShaderFeatures, requredMeshShaderFeatures)
|
||||
|
||||
#undef CHECK_FEATURE_STRUCT
|
||||
|
||||
if (!checkQueueFamilies(deviceInfo, score))
|
||||
{
|
||||
logVerbose("Vulkan device {} not supported as no suitable queue family configuration could be found.", deviceInfo.properties.deviceName);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!checkDeviceExtensions(extensions, deviceInfo, score))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
std::size_t findBestPhysicalDevice(
|
||||
PhysicalDeviceCriteria& deviceCriteria,
|
||||
const std::vector<ExtensionInfo>& extensions,
|
||||
const std::vector<PhysicalDeviceInfo>& deviceInfos)
|
||||
{
|
||||
int bestScore = -1;
|
||||
std::size_t bestIdx = 0;
|
||||
|
||||
for (std::size_t idx = 0; idx < deviceInfos.size(); ++idx)
|
||||
{
|
||||
const int score = scorePhysicalDevice(deviceCriteria, extensions, deviceInfos[idx]);
|
||||
if (score > bestScore)
|
||||
{
|
||||
bestScore = score;
|
||||
bestIdx = idx;
|
||||
}
|
||||
}
|
||||
|
||||
if (bestScore < 0)
|
||||
{
|
||||
logAndDie("Could not find a suitable Vulkan device!");
|
||||
}
|
||||
|
||||
return bestIdx;
|
||||
}
|
||||
|
||||
std::vector<const char*> buildDeviceExtensionNameList(const PhysicalDeviceInfo& deviceInfo, std::vector<ExtensionInfo>& extensions)
|
||||
{
|
||||
auto isExtensionSupported = [&](const char* extension)
|
||||
{
|
||||
for (const vk::ExtensionProperties& props : deviceInfo.extensions)
|
||||
{
|
||||
if (std::strncmp(props.extensionName, extension, VK_MAX_EXTENSION_NAME_SIZE) == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
std::vector<const char*> enabledExtensions;
|
||||
for (ExtensionInfo& extInfo : extensions)
|
||||
{
|
||||
const bool supported = isExtensionSupported(extInfo.name);
|
||||
if (!supported && extInfo.required)
|
||||
{
|
||||
// this shouldn't be possible, we already checked when scoring the device
|
||||
logAndDie("Required Vulkan device extension not supported: {}.", extInfo.name);
|
||||
}
|
||||
if (supported) {
|
||||
enabledExtensions.push_back(extInfo.name);
|
||||
}
|
||||
extInfo.enabled = supported;
|
||||
}
|
||||
return enabledExtensions;
|
||||
}
|
||||
|
||||
std::vector<vk::DeviceQueueCreateInfo> buildQueueCreateInfoList(const PhysicalDeviceInfo& deviceInfo)
|
||||
{
|
||||
static const float QUEUE_PRIORITY_ONE = 1.0f;
|
||||
std::vector<vk::DeviceQueueCreateInfo> createInfos;
|
||||
|
||||
createInfos.emplace_back()
|
||||
.setQueueFamilyIndex(deviceInfo.graphicsQueueFamily)
|
||||
.setQueueCount(1)
|
||||
.setPQueuePriorities(&QUEUE_PRIORITY_ONE);
|
||||
|
||||
return createInfos;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Device::Device(ObjectPtr<Instance> owner, DeviceCreationArgs args)
|
||||
: super_t(std::move(owner)),
|
||||
mExtensions(std::move(args.extensions))
|
||||
{
|
||||
if (!args.flags.noDefaultExtensions)
|
||||
{
|
||||
buildDefaultDeviceExtensionList(mExtensions);
|
||||
}
|
||||
|
||||
const std::vector<PhysicalDeviceInfo>& physicalDevices = getOwner()->getPhysicalDevices();
|
||||
const std::size_t physicalDeviceIdx = findBestPhysicalDevice(args.physicalDeviceCriteria, mExtensions, physicalDevices);
|
||||
mDeviceInfo = &physicalDevices[physicalDeviceIdx];
|
||||
const std::vector<const char*> enabledExtensions = buildDeviceExtensionNameList(*mDeviceInfo, mExtensions);
|
||||
const std::vector<vk::DeviceQueueCreateInfo> queueCreateInfos = buildQueueCreateInfoList(*mDeviceInfo);
|
||||
|
||||
void* pNext = nullptr;
|
||||
#define APPEND_FEATURE_STRUCT(type, name, requiredArray) \
|
||||
type name{.pNext = pNext}; \
|
||||
if (!args.physicalDeviceCriteria.requiredArray.empty()) \
|
||||
{ \
|
||||
for (auto feature : args.physicalDeviceCriteria.requiredArray) \
|
||||
{ \
|
||||
(name).*(feature) = VK_TRUE; \
|
||||
} \
|
||||
pNext = &(name); \
|
||||
}
|
||||
APPEND_FEATURE_STRUCT(vk::PhysicalDeviceVulkan11Features, vulkan11Features, requiredVulkan11Features)
|
||||
APPEND_FEATURE_STRUCT(vk::PhysicalDeviceVulkan12Features, vulkan12Features, requiredVulkan12Features)
|
||||
APPEND_FEATURE_STRUCT(vk::PhysicalDeviceVulkan13Features, vulkan13Features, requiredVulkan13Features)
|
||||
APPEND_FEATURE_STRUCT(vk::PhysicalDeviceAccelerationStructureFeaturesKHR, accelerationStructureFeatures, requiredAccelerationStructureFeatures)
|
||||
APPEND_FEATURE_STRUCT(vk::PhysicalDeviceRayTracingPipelineFeaturesKHR, rayTracingPipelineFeature, requiredRayTracingPipelineFeatures)
|
||||
APPEND_FEATURE_STRUCT(vk::PhysicalDeviceMeshShaderFeaturesEXT, meshShaderFeatures, requredMeshShaderFeatures)
|
||||
#undef APPEND_FEATURE_STRUCT
|
||||
|
||||
vk::PhysicalDeviceFeatures enabledFeatures; // NOLINT(*-const-correctness) false positive
|
||||
for (auto feature : args.physicalDeviceCriteria.requiredFeatures)
|
||||
{
|
||||
enabledFeatures.*feature = VK_TRUE;
|
||||
}
|
||||
|
||||
const vk::DeviceCreateInfo deviceCreateInfo =
|
||||
{
|
||||
.pNext = pNext,
|
||||
.queueCreateInfoCount = static_cast<std::uint32_t>(queueCreateInfos.size()),
|
||||
.pQueueCreateInfos = queueCreateInfos.data(),
|
||||
.enabledExtensionCount = static_cast<std::uint32_t>(enabledExtensions.size()),
|
||||
.ppEnabledExtensionNames = enabledExtensions.data(),
|
||||
.pEnabledFeatures = &enabledFeatures
|
||||
};
|
||||
mHandle = mDeviceInfo->device.createDevice(deviceCreateInfo);
|
||||
if (args.flags.singleDevice)
|
||||
{
|
||||
VULKAN_HPP_DEFAULT_DISPATCHER.init(mHandle);
|
||||
}
|
||||
|
||||
mGraphicsQueue = mHandle.getQueue(mDeviceInfo->graphicsQueueFamily, 0);
|
||||
if (mDeviceInfo->graphicsQueueFamily == mDeviceInfo->computeQueueFamily)
|
||||
{
|
||||
mComputeQueue = mGraphicsQueue;
|
||||
}
|
||||
else
|
||||
{
|
||||
mComputeQueue = mHandle.getQueue(mDeviceInfo->computeQueueFamily, 0);
|
||||
}
|
||||
|
||||
getOwner()->getMainTaskLoop().addTask(c_updateLoop(), &mUpdateLoopHandle);
|
||||
getOwner()->deviceCreated.emit(*this);
|
||||
}
|
||||
|
||||
Device::~Device() noexcept
|
||||
{
|
||||
mUpdateLoopHandle.cancel();
|
||||
mScratchCommandPools.clear(); // hack2: due to the below hack the pools would get destroyed after the device, reset them manually
|
||||
if (mHandle)
|
||||
{
|
||||
mHandle.waitIdle();
|
||||
mPendingScratchCmdBuffers.clear();
|
||||
getOwner()->queueDelete([handle=mHandle]()
|
||||
{
|
||||
handle.destroy();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ScratchCommandPool::ScratchCommandPool(Device& device)
|
||||
{
|
||||
mCommandPool = device.createChild<CommandPool>(CommandPoolCreationArgs{
|
||||
.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer,
|
||||
.queueFamilyIndex = device.getDeviceInfo().graphicsQueueFamily
|
||||
});
|
||||
}
|
||||
|
||||
ObjectPtr<CommandBuffer> ScratchCommandPool::allocateCommandBuffer()
|
||||
{
|
||||
for (Buffer& buffer : mBuffers)
|
||||
{
|
||||
if (buffer.doneFuture->ready())
|
||||
{
|
||||
buffer.doneFuture = std::make_shared<mijin::Future<void>>();
|
||||
buffer.cmdBuffer->getVkHandle().reset();
|
||||
return buffer.cmdBuffer;
|
||||
}
|
||||
}
|
||||
|
||||
// nothing found, allocate a new one
|
||||
return mBuffers.emplace_back(mCommandPool->allocateCommandBuffer(), std::make_shared<mijin::Future<void>>()).cmdBuffer;
|
||||
}
|
||||
|
||||
mijin::FuturePtr<void> ScratchCommandPool::getFuture(const ObjectPtr<CommandBuffer>& cmdBuffer) noexcept
|
||||
{
|
||||
for (const Buffer& buffer : mBuffers)
|
||||
{
|
||||
if (buffer.cmdBuffer == cmdBuffer)
|
||||
{
|
||||
return buffer.doneFuture;
|
||||
}
|
||||
}
|
||||
logAndDie("Someone passed an invalid cmdBuffer to getFuture()!");
|
||||
}
|
||||
|
||||
ObjectPtr<DeviceMemory> Device::allocateDeviceMemory(const DeviceMemoryAllocationArgs& args)
|
||||
{
|
||||
return createChild<DeviceMemory>(args);
|
||||
}
|
||||
|
||||
ObjectPtr<CommandBuffer> Device::beginScratchCommandBuffer()
|
||||
{
|
||||
ObjectPtr<CommandBuffer> cmdBuffer;
|
||||
{
|
||||
const std::shared_lock readLock(mScratchCommandPoolsMutex);
|
||||
auto it = mScratchCommandPools.find(std::this_thread::get_id());
|
||||
if (it != mScratchCommandPools.end())
|
||||
{
|
||||
cmdBuffer = it->second.allocateCommandBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
if (cmdBuffer == nullptr)
|
||||
{
|
||||
// create the scratch command pool
|
||||
const std::unique_lock writeLock(mScratchCommandPoolsMutex);
|
||||
ScratchCommandPool& pool = mScratchCommandPools.emplace(std::this_thread::get_id(), ScratchCommandPool(*this)).first->second;
|
||||
cmdBuffer = pool.allocateCommandBuffer();
|
||||
|
||||
// hack: an object should normally not contain pointers to its own child as that leads to cyclic dependencies
|
||||
// decrease reference count by 1 to migitate that
|
||||
decreaseReferenceCount();
|
||||
}
|
||||
|
||||
const vk::CommandBufferBeginInfo beginInfo =
|
||||
{
|
||||
.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit
|
||||
};
|
||||
cmdBuffer->getVkHandle().begin(beginInfo);
|
||||
return cmdBuffer;
|
||||
}
|
||||
|
||||
mijin::FuturePtr<void> Device::endScratchCommandBuffer(ObjectPtr<CommandBuffer> cmdBuffer)
|
||||
{
|
||||
mijin::FuturePtr<void> future;
|
||||
{
|
||||
const std::shared_lock readLock(mScratchCommandPoolsMutex);
|
||||
future = mScratchCommandPools.at(std::this_thread::get_id()).getFuture(cmdBuffer);
|
||||
}
|
||||
cmdBuffer->getVkHandle().end();
|
||||
|
||||
getOwner()->runOnMainThread([cmdBuffer = std::move(cmdBuffer), self = getPointer(), future]() mutable
|
||||
{
|
||||
ObjectPtr<Fence> doneFence = Fence::create(self);
|
||||
|
||||
const vk::SubmitInfo submitInfo =
|
||||
{
|
||||
.commandBufferCount = 1,
|
||||
.pCommandBuffers = &cmdBuffer->getVkHandle()
|
||||
};
|
||||
self->getGraphicsQueue().submit(submitInfo, *doneFence);
|
||||
self->mPendingScratchCmdBuffers.push_back({
|
||||
.cmdBuffer = std::move(cmdBuffer),
|
||||
.doneFence = std::move(doneFence),
|
||||
.future = std::move(future)
|
||||
});
|
||||
});
|
||||
return future;
|
||||
}
|
||||
|
||||
void Device::queueDelete(std::function<void()> deleter) noexcept
|
||||
{
|
||||
getOwner()->queueDelete(std::move(deleter));
|
||||
}
|
||||
|
||||
mijin::Task<> Device::c_updateLoop() noexcept
|
||||
{
|
||||
while(!getOwner()->isQuitRequested())
|
||||
{
|
||||
for (auto it = mPendingScratchCmdBuffers.begin(); it != mPendingScratchCmdBuffers.end();)
|
||||
{
|
||||
if (it->doneFence->isDone())
|
||||
{
|
||||
it->future->set();
|
||||
it = mPendingScratchCmdBuffers.erase(it);
|
||||
} else
|
||||
{
|
||||
++it;
|
||||
}
|
||||
}
|
||||
co_await mijin::c_suspend();
|
||||
}
|
||||
}
|
||||
}
|
||||
39
source/device_memory.cpp
Normal file
39
source/device_memory.cpp
Normal file
@@ -0,0 +1,39 @@
|
||||
|
||||
#include "iwa/device_memory.hpp"
|
||||
|
||||
#include "iwa/device.hpp"
|
||||
|
||||
namespace iwa
|
||||
{
|
||||
DeviceMemory::DeviceMemory(ObjectPtr<Device> owner, const DeviceMemoryAllocationArgs& args) : super_t(std::move(owner))
|
||||
{
|
||||
mHandle = getOwner()->getVkHandle().allocateMemory(vk::MemoryAllocateInfo
|
||||
{
|
||||
.allocationSize = args.allocationSize,
|
||||
.memoryTypeIndex = args.memoryTypeIndex
|
||||
});
|
||||
}
|
||||
|
||||
DeviceMemory::~DeviceMemory() noexcept
|
||||
{
|
||||
IWA_DELETE_DEVICE_OBJECT(getOwner(), mHandle, freeMemory);
|
||||
}
|
||||
|
||||
std::optional<std::uint32_t> findMemoryType(Device& device, const vk::MemoryRequirements& requirements, vk::MemoryPropertyFlags properties)
|
||||
{
|
||||
const vk::PhysicalDeviceMemoryProperties& memoryProperties = device.getDeviceInfo().memoryProperties;
|
||||
|
||||
for (std::uint32_t idx = 0; idx < memoryProperties.memoryTypeCount; ++idx)
|
||||
{
|
||||
if ((requirements.memoryTypeBits & (1 << idx)) == 0) {
|
||||
continue; // not suitable for this buffer
|
||||
}
|
||||
if ((memoryProperties.memoryTypes[idx].propertyFlags & properties) != properties) {
|
||||
continue; // does not fulfill required properties
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
} // namespace iwa
|
||||
28
source/event.cpp
Normal file
28
source/event.cpp
Normal file
@@ -0,0 +1,28 @@
|
||||
|
||||
#include "iwa/event.hpp"
|
||||
|
||||
#include "iwa/device.hpp"
|
||||
|
||||
namespace iwa
|
||||
{
|
||||
Event::Event(ObjectPtr<Device> owner, const EventCreationArgs& args) : super_t(std::move(owner))
|
||||
{
|
||||
mHandle = getOwner()->getVkHandle().createEvent(vk::EventCreateInfo
|
||||
{
|
||||
.flags = args.flags
|
||||
});
|
||||
}
|
||||
|
||||
Event::~Event() noexcept
|
||||
{
|
||||
IWA_DELETE_DEVICE_OBJECT(getOwner(), mHandle, destroyEvent);
|
||||
}
|
||||
|
||||
mijin::Task<> Event::c_wait()
|
||||
{
|
||||
while (getOwner()->getVkHandle().getEventStatus(mHandle) != vk::Result::eEventSet)
|
||||
{
|
||||
co_await mijin::c_suspend();
|
||||
}
|
||||
}
|
||||
}
|
||||
38
source/fence.cpp
Normal file
38
source/fence.cpp
Normal file
@@ -0,0 +1,38 @@
|
||||
|
||||
#include "iwa/fence.hpp"
|
||||
|
||||
#include "iwa/device.hpp"
|
||||
|
||||
namespace iwa
|
||||
{
|
||||
Fence::Fence(ObjectPtr<Device> owner, const FenceCreationArgs& args) : super_t(std::move(owner))
|
||||
{
|
||||
mHandle = getOwner()->getVkHandle().createFence(vk::FenceCreateInfo{
|
||||
.flags = args.flags
|
||||
});
|
||||
}
|
||||
|
||||
Fence::~Fence() noexcept
|
||||
{
|
||||
IWA_DELETE_DEVICE_OBJECT(getOwner(), mHandle, destroyFence)
|
||||
}
|
||||
|
||||
bool Fence::isDone() const
|
||||
{
|
||||
return getOwner()->getVkHandle().waitForFences(mHandle, VK_TRUE, 0) != vk::Result::eTimeout;
|
||||
}
|
||||
|
||||
mijin::Task<> Fence::c_wait() const
|
||||
{
|
||||
while (getOwner()->getVkHandle().waitForFences(mHandle, VK_TRUE, 0) == vk::Result::eTimeout)
|
||||
{
|
||||
co_await mijin::c_suspend();
|
||||
}
|
||||
co_return;
|
||||
}
|
||||
|
||||
void Fence::reset() const
|
||||
{
|
||||
getOwner()->getVkHandle().resetFences(mHandle);
|
||||
}
|
||||
} // namespace iwa
|
||||
384
source/image.cpp
Normal file
384
source/image.cpp
Normal file
@@ -0,0 +1,384 @@
|
||||
|
||||
#include "iwa/image.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include "iwa/resource/bitmap.hpp"
|
||||
#include "iwa/buffer.hpp"
|
||||
#include "iwa/device.hpp"
|
||||
#include "iwa/util/vkutil.hpp"
|
||||
|
||||
namespace iwa
|
||||
{
|
||||
Image::Image(ObjectPtr<Device> owner, ImageCreationArgs args) : super_t(std::move(owner)), mFlags(args.flags), mType(args.imageType),
|
||||
mFormat(args.format), mTiling(args.tiling), mUsage(args.usage), mSize(args.extent), mArrayLayers(args.arrayLayers),
|
||||
mMipLevels(clampMipLevels(args.mipLevels))
|
||||
{
|
||||
if (mMipLevels > 1) {
|
||||
mUsage |= vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eTransferSrc;
|
||||
}
|
||||
mHandle = getOwner()->getVkHandle().createImage(vk::ImageCreateInfo{
|
||||
.flags = args.flags,
|
||||
.imageType = args.imageType,
|
||||
.format = args.format,
|
||||
.extent = args.extent,
|
||||
.mipLevels = mMipLevels,
|
||||
.arrayLayers = args.arrayLayers,
|
||||
.samples = args.samples,
|
||||
.tiling = args.tiling,
|
||||
.usage = mUsage,
|
||||
.sharingMode = args.sharingMode,
|
||||
.queueFamilyIndexCount = static_cast<std::uint32_t>(args.queueFamilyIndices.size()),
|
||||
.pQueueFamilyIndices = args.queueFamilyIndices.data(),
|
||||
.initialLayout = args.initialLayout
|
||||
});
|
||||
}
|
||||
|
||||
Image::Image(ObjectPtr<Device> owner, ImageWrapArgs args)
|
||||
: super_t(std::move(owner)), MixinVulkanObject(args.handle), mType(args.type), mFormat(args.format), mUsage(args.usage),
|
||||
mSize(args.size), mMipLevels(args.mipLevels), mWrapped(true)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
Image::~Image() noexcept
|
||||
{
|
||||
if (!mWrapped)
|
||||
{
|
||||
IWA_DELETE_DEVICE_OBJECT(getOwner(), mHandle, destroyImage)
|
||||
}
|
||||
}
|
||||
|
||||
void Image::resetUsage(ResetLayout resetLayout) noexcept
|
||||
{
|
||||
lastUsageStages = vk::PipelineStageFlagBits::eTopOfPipe;
|
||||
lastAccess = vk::AccessFlags();
|
||||
if (resetLayout)
|
||||
{
|
||||
currentLayout = vk::ImageLayout::eUndefined;
|
||||
}
|
||||
}
|
||||
|
||||
void Image::allocateMemory()
|
||||
{
|
||||
const vk::MemoryRequirements memoryRequirements = getOwner()->getVkHandle().getImageMemoryRequirements(mHandle);
|
||||
const vk::MemoryPropertyFlags memoryFlags = vk::MemoryPropertyFlagBits::eDeviceLocal;
|
||||
const std::optional<std::uint32_t> memoryTypeIdx = findMemoryType(*getOwner(), memoryRequirements, memoryFlags);
|
||||
if (!memoryTypeIdx.has_value())
|
||||
{
|
||||
throw std::runtime_error("Could not find a suitable memory type.");
|
||||
}
|
||||
|
||||
ObjectPtr<DeviceMemory> memory = getOwner()->allocateDeviceMemory(
|
||||
{
|
||||
.allocationSize = memoryRequirements.size,
|
||||
.memoryTypeIndex = memoryTypeIdx.value()
|
||||
});
|
||||
bindMemory(std::move(memory));
|
||||
}
|
||||
|
||||
void Image::bindMemory(ObjectPtr<DeviceMemory> memory, vk::DeviceSize offset)
|
||||
{
|
||||
mMemory = std::move(memory);
|
||||
getOwner()->getVkHandle().bindImageMemory(mHandle, *mMemory, offset);
|
||||
}
|
||||
|
||||
void Image::applyTransition(vk::CommandBuffer cmdBuffer, const ImageTransition& transition)
|
||||
{
|
||||
assert(transition.layout != vk::ImageLayout::eUndefined);
|
||||
|
||||
if (transition.layout != currentLayout)
|
||||
{
|
||||
cmdBuffer.pipelineBarrier(
|
||||
/* srcStageMask = */ lastUsageStages,
|
||||
/* dstStageMask = */ transition.stages,
|
||||
/* dependencyFlags = */ vk::DependencyFlags(),
|
||||
/* memoryBarriers = */ {},
|
||||
/* bufferMemoryBarriers = */ {},
|
||||
/* imageMemoryBarriers = */ {
|
||||
vk::ImageMemoryBarrier{
|
||||
.srcAccessMask = lastAccess,
|
||||
.dstAccessMask = transition.access,
|
||||
.oldLayout = currentLayout,
|
||||
.newLayout = transition.layout,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = mHandle,
|
||||
.subresourceRange = transition.subResourceRange
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
lastUsageStages = transition.stages;
|
||||
lastAccess = transition.access;
|
||||
currentLayout = transition.layout;
|
||||
}
|
||||
|
||||
ObjectPtr<ImageView> Image::createImageView(const ImageViewCreationArgs& args)
|
||||
{
|
||||
return createChild<ImageView>(args);
|
||||
}
|
||||
|
||||
|
||||
mijin::Task<> Image::c_upload(const void* data, std::size_t bytes, vk::Extent3D bufferImageSize, vk::Offset3D imageOffset,
|
||||
unsigned baseLayer, unsigned layerCount)
|
||||
{
|
||||
MIJIN_ASSERT(bytes >= static_cast<std::size_t>(bufferImageSize.width) * bufferImageSize.height * bufferImageSize.depth
|
||||
* vkFormatSize(mFormat), "Buffer for image upload is too small!");
|
||||
|
||||
// TODO: optimize this whole process
|
||||
// create scratch buffer
|
||||
vk::Device device = *getOwner();
|
||||
ObjectPtr<Buffer> scratchBuffer = getOwner()->createChild<Buffer>(BufferCreationArgs{
|
||||
.size = bytes,
|
||||
.usage = vk::BufferUsageFlagBits::eTransferSrc
|
||||
});
|
||||
scratchBuffer->allocateMemory(HostVisible::YES);
|
||||
|
||||
// copy to scratch buffer
|
||||
void* mapped = device.mapMemory(*scratchBuffer->getMemory(), 0, bytes);
|
||||
std::memcpy(mapped, data, bytes);
|
||||
device.unmapMemory(*scratchBuffer->getMemory());
|
||||
|
||||
ObjectPtr<CommandBuffer> cmdBufferPtr = getOwner()->beginScratchCommandBuffer();
|
||||
const vk::CommandBuffer cmdBuffer = *cmdBufferPtr;
|
||||
applyTransition(cmdBuffer, {
|
||||
.stages = vk::PipelineStageFlagBits::eTransfer,
|
||||
.layout = vk::ImageLayout::eTransferDstOptimal,
|
||||
.access = vk::AccessFlagBits::eTransferWrite
|
||||
});
|
||||
|
||||
// copy to actual buffer
|
||||
cmdBuffer.copyBufferToImage(*scratchBuffer, mHandle, vk::ImageLayout::eTransferDstOptimal, vk::BufferImageCopy{
|
||||
.bufferOffset = 0,
|
||||
.bufferRowLength = 0,
|
||||
.bufferImageHeight = 0,
|
||||
.imageSubresource = {
|
||||
.aspectMask = vk::ImageAspectFlagBits::eColor,
|
||||
.mipLevel = 0,
|
||||
.baseArrayLayer = baseLayer,
|
||||
.layerCount = layerCount
|
||||
},
|
||||
.imageOffset = {
|
||||
.x = imageOffset.x,
|
||||
.y = imageOffset.y,
|
||||
.z = imageOffset.z
|
||||
},
|
||||
.imageExtent = {
|
||||
.width = bufferImageSize.width,
|
||||
.height = bufferImageSize.height,
|
||||
.depth = bufferImageSize.depth
|
||||
}
|
||||
});
|
||||
if (mMipLevels > 1)
|
||||
{
|
||||
generateMipMaps(cmdBuffer);
|
||||
}
|
||||
co_await getOwner()->endScratchCommandBuffer(std::move(cmdBufferPtr));
|
||||
}
|
||||
|
||||
mijin::Task<> Image::c_doTransition(const ImageTransition& transition)
|
||||
{
|
||||
ObjectPtr<CommandBuffer> cmdBufferPtr = getOwner()->beginScratchCommandBuffer();
|
||||
const vk::CommandBuffer cmdBuffer = *cmdBufferPtr;
|
||||
applyTransition(cmdBuffer, transition);
|
||||
|
||||
co_await getOwner()->endScratchCommandBuffer(std::move(cmdBufferPtr));
|
||||
}
|
||||
|
||||
mijin::Task<> Image::c_upload(const Bitmap& bitmap, vk::Offset3D imageOffset, unsigned baseLayer, unsigned layerCount)
|
||||
{
|
||||
MIJIN_ASSERT(vkFormatSize(mFormat) == vkFormatSize(bitmap.getFormat()), "Bitmap format size doesn't match image format size.");
|
||||
|
||||
const vk::Extent3D bufferImageSize = {
|
||||
.width = bitmap.getSize().width,
|
||||
.height = bitmap.getSize().height,
|
||||
.depth = 1
|
||||
};
|
||||
return c_upload(bitmap.getData().data(), bitmap.getData().size_bytes(), bufferImageSize, imageOffset, baseLayer, layerCount);
|
||||
}
|
||||
|
||||
mijin::Task<> Image::c_blitFrom(Image& srcImage, std::vector<vk::ImageBlit> regions, vk::Filter filter)
|
||||
{
|
||||
ObjectPtr<CommandBuffer> cmdBufferPtr = getOwner()->beginScratchCommandBuffer();
|
||||
const vk::CommandBuffer cmdBuffer = *cmdBufferPtr;
|
||||
|
||||
applyTransition(cmdBuffer, IMAGE_TRANSITION_TRANSFER_WRITE);
|
||||
srcImage.applyTransition(cmdBuffer, IMAGE_TRANSITION_TRANSFER_READ);
|
||||
|
||||
cmdBuffer.blitImage(
|
||||
/* srcImage = */ srcImage,
|
||||
/* srcImageLayout = */ vk::ImageLayout::eTransferSrcOptimal,
|
||||
/* dstImage = */ *this,
|
||||
/* dstImageLayout = */ vk::ImageLayout::eTransferDstOptimal,
|
||||
/* regions = */ regions,
|
||||
/* filter = */ filter
|
||||
);
|
||||
co_await getOwner()->endScratchCommandBuffer(std::move(cmdBufferPtr));
|
||||
}
|
||||
|
||||
mijin::Task<> Image::c_blitFrom(const Bitmap& bitmap, std::vector<vk::ImageBlit> regions, vk::Filter filter)
|
||||
{
|
||||
ObjectPtr<Image> scratchImage = co_await c_create(getOwner()->getPointer(), ImageFromBitmapArgs{
|
||||
.bitmap = &bitmap,
|
||||
.usage = vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eTransferSrc
|
||||
});
|
||||
co_await c_blitFrom(*scratchImage, std::move(regions), filter);
|
||||
}
|
||||
|
||||
mijin::Task<> Image::c_copyFrom(Image& srcImage, std::vector<vk::ImageCopy> regions)
|
||||
{
|
||||
ObjectPtr<CommandBuffer> cmdBufferPtr = getOwner()->beginScratchCommandBuffer();
|
||||
const vk::CommandBuffer cmdBuffer = *cmdBufferPtr;
|
||||
|
||||
applyTransition(cmdBuffer, IMAGE_TRANSITION_TRANSFER_WRITE);
|
||||
srcImage.applyTransition(cmdBuffer, IMAGE_TRANSITION_TRANSFER_READ);
|
||||
|
||||
cmdBuffer.copyImage(
|
||||
/* srcImage = */ srcImage,
|
||||
/* srcImageLayout = */ vk::ImageLayout::eTransferSrcOptimal,
|
||||
/* dstImage = */ *this,
|
||||
/* dstImageLayout = */ vk::ImageLayout::eTransferDstOptimal,
|
||||
/* regions = */ regions
|
||||
);
|
||||
co_await getOwner()->endScratchCommandBuffer(std::move(cmdBufferPtr));
|
||||
}
|
||||
|
||||
std::uint32_t Image::clampMipLevels(std::uint32_t levels) const
|
||||
{
|
||||
if (levels <= 1) {
|
||||
return 1;
|
||||
}
|
||||
const vk::ImageFormatProperties props = getOwner()->getVkPhysicalDevice().getImageFormatProperties(mFormat, mType, mTiling, mUsage, mFlags);
|
||||
return std::min(levels, std::min(props.maxMipLevels, static_cast<std::uint32_t>(std::log2(std::max(mSize.width, mSize.height)))+1));
|
||||
}
|
||||
|
||||
void Image::generateMipMaps(vk::CommandBuffer cmdBuffer)
|
||||
{
|
||||
std::vector<vk::ImageBlit> regions;
|
||||
regions.resize(mMipLevels - 1);
|
||||
|
||||
applyTransition(cmdBuffer, {
|
||||
.stages = vk::PipelineStageFlagBits::eTransfer,
|
||||
.layout = vk::ImageLayout::eGeneral, // TODO: using transfer dst/src optimal would be better, but I don't want to deal with different layout in the mips
|
||||
.access = vk::AccessFlagBits::eTransferWrite | vk::AccessFlagBits::eTransferRead
|
||||
});
|
||||
|
||||
unsigned mipWidth = mSize.width / 2;
|
||||
unsigned mipHeight = mSize.height / 2;
|
||||
for (unsigned level = 1; level < mMipLevels; ++level)
|
||||
{
|
||||
vk::ImageBlit& region = regions[level - 1];
|
||||
region.srcSubresource = {
|
||||
.aspectMask = vk::ImageAspectFlagBits::eColor,
|
||||
.mipLevel = 0,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1
|
||||
};
|
||||
region.srcOffsets[0].x = 0;
|
||||
region.srcOffsets[0].y = 0;
|
||||
region.srcOffsets[0].z = 0;
|
||||
region.srcOffsets[1].x = static_cast<std::int32_t>(mSize.width);
|
||||
region.srcOffsets[1].y = static_cast<std::int32_t>(mSize.height);
|
||||
region.srcOffsets[1].z = 1;
|
||||
region.dstSubresource = {
|
||||
.aspectMask = vk::ImageAspectFlagBits::eColor,
|
||||
.mipLevel = level,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1
|
||||
};
|
||||
region.dstOffsets[0].x = 0;
|
||||
region.dstOffsets[0].y = 0;
|
||||
region.dstOffsets[0].z = 0;
|
||||
region.dstOffsets[1].x = static_cast<std::int32_t>(mipWidth);
|
||||
region.dstOffsets[1].y = static_cast<std::int32_t>(mipHeight);
|
||||
region.dstOffsets[1].z = 1;
|
||||
|
||||
if (mipWidth > 1) {
|
||||
mipWidth /= 2;
|
||||
}
|
||||
if (mipHeight > 1) {
|
||||
mipHeight /= 2;
|
||||
}
|
||||
}
|
||||
|
||||
cmdBuffer.blitImage(
|
||||
/* srcImage = */ mHandle,
|
||||
/* srcImageLayout = */ vk::ImageLayout::eGeneral,
|
||||
/* dstImage = */ mHandle,
|
||||
/* dstImageLayout = */ vk::ImageLayout::eGeneral,
|
||||
/* regions = */ regions,
|
||||
/* filter = */ vk::Filter::eLinear
|
||||
);
|
||||
}
|
||||
|
||||
mijin::Task<ObjectPtr<Image>> Image::c_create(ObjectPtr<Device> owner, ImageFromBitmapArgs args)
|
||||
{
|
||||
ObjectPtr<Image> image = owner->createChild<Image>(ImageCreationArgs{
|
||||
.flags = args.flags,
|
||||
.imageType = vk::ImageType::e2D,
|
||||
.format = args.bitmap->getFormat(),
|
||||
.extent = {
|
||||
.width = args.bitmap->getSize().width,
|
||||
.height = args.bitmap->getSize().height,
|
||||
.depth = 1
|
||||
},
|
||||
.mipLevels = 1,
|
||||
.arrayLayers = 1,
|
||||
.samples = vk::SampleCountFlagBits::e1,
|
||||
.tiling = args.tiling,
|
||||
.usage = args.usage | vk::ImageUsageFlagBits::eTransferDst,
|
||||
.sharingMode = args.sharingMode,
|
||||
.queueFamilyIndices = std::move(args.queueFamilyIndices),
|
||||
.initialLayout = args.initialLayout
|
||||
});
|
||||
image->allocateMemory();
|
||||
co_await image->c_upload(*args.bitmap);
|
||||
co_return image;
|
||||
}
|
||||
|
||||
ImageView::ImageView(ObjectPtr<Image> owner, const ImageViewCreationArgs& args) : super_t(std::move(owner))
|
||||
{
|
||||
mHandle = getOwner()->getOwner()->getVkHandle().createImageView(vk::ImageViewCreateInfo
|
||||
{
|
||||
.flags = args.flags,
|
||||
.image = *getOwner(),
|
||||
.viewType = args.viewType,
|
||||
.format = args.format != vk::Format::eUndefined ? args.format : getOwner()->getFormat(),
|
||||
.components = args.components,
|
||||
.subresourceRange = args.subresourceRange
|
||||
});
|
||||
}
|
||||
|
||||
ImageView::~ImageView() noexcept
|
||||
{
|
||||
IWA_DELETE_DEVICE_OBJECT(getOwner()->getOwner(), mHandle, destroyImageView);
|
||||
}
|
||||
|
||||
Sampler::Sampler(ObjectPtr<Device> owner, const SamplerCreationArgs& args) : super_t(std::move(owner))
|
||||
{
|
||||
mHandle = getOwner()->getVkHandle().createSampler(vk::SamplerCreateInfo
|
||||
{
|
||||
.flags = args.flags,
|
||||
.magFilter = args.magFilter,
|
||||
.minFilter = args.minFilter,
|
||||
.mipmapMode = args.mipmapMode,
|
||||
.addressModeU = args.addressModeU,
|
||||
.addressModeV = args.addressModeV,
|
||||
.addressModeW = args.addressModeW,
|
||||
.mipLodBias = args.mipLodBias,
|
||||
.anisotropyEnable = args.options.anisotropyEnable,
|
||||
.maxAnisotropy = args.maxAnisotropy,
|
||||
.compareEnable = args.options.compareEnable,
|
||||
.compareOp = args.compareOp,
|
||||
.minLod = args.minLod,
|
||||
.maxLod = args.maxLod,
|
||||
.borderColor = args.borderColor,
|
||||
.unnormalizedCoordinates = args.options.unnormalizedCoordinates
|
||||
});
|
||||
}
|
||||
|
||||
Sampler::~Sampler() noexcept
|
||||
{
|
||||
IWA_DELETE_DEVICE_OBJECT(getOwner(), mHandle, destroySampler);
|
||||
}
|
||||
} // namespace iwa
|
||||
37
source/input.cpp
Normal file
37
source/input.cpp
Normal file
@@ -0,0 +1,37 @@
|
||||
|
||||
#include "iwa/input.hpp"
|
||||
|
||||
#include "iwa/log.hpp"
|
||||
|
||||
namespace iwa
|
||||
{
|
||||
KeyState getKeyState(ScanCode scanCode) noexcept
|
||||
{
|
||||
const int index = static_cast<int>(scanCode);
|
||||
int numKeys = 0;
|
||||
const Uint8* keys = SDL_GetKeyboardState(&numKeys);
|
||||
if (index >= numKeys) {
|
||||
return {};
|
||||
}
|
||||
return {
|
||||
.pressed = keys[index] > 0
|
||||
};
|
||||
}
|
||||
|
||||
void captureMouse() noexcept
|
||||
{
|
||||
SDL_CaptureMouse(SDL_TRUE);
|
||||
}
|
||||
|
||||
void uncaptureMouse() noexcept
|
||||
{
|
||||
SDL_CaptureMouse(SDL_FALSE);
|
||||
}
|
||||
|
||||
std::pair<int, int> getMouseScreenPosition() noexcept
|
||||
{
|
||||
std::pair<int, int> position;
|
||||
SDL_GetGlobalMouseState(&position.first, &position.second);
|
||||
return position;
|
||||
}
|
||||
} // namespace iwa
|
||||
462
source/instance.cpp
Normal file
462
source/instance.cpp
Normal file
@@ -0,0 +1,462 @@
|
||||
|
||||
#include "iwa/instance.hpp"
|
||||
|
||||
#include <mijin/detect.hpp>
|
||||
#include <mijin/debug/assert.hpp>
|
||||
#include "iwa/addon.hpp"
|
||||
#include "iwa/log.hpp"
|
||||
#include "iwa/window.hpp"
|
||||
|
||||
#include <vulkan/vulkan_from_string.inl>
|
||||
|
||||
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE
|
||||
|
||||
namespace iwa
|
||||
{
|
||||
namespace
|
||||
{
|
||||
void buildDefaultInstanceExtensionList(std::vector<ExtensionInfo>& outExtensions) noexcept
|
||||
{
|
||||
#if !defined(KAZAN_RELEASE)
|
||||
outExtensions.push_back({.name = VK_EXT_DEBUG_UTILS_EXTENSION_NAME, .required = false});
|
||||
#endif
|
||||
// if (!getEngineOptions().headless)
|
||||
{
|
||||
outExtensions.push_back({.name = VK_KHR_SURFACE_EXTENSION_NAME, .required = true});
|
||||
#if MIJIN_TARGET_OS == MIJIN_OS_LINUX
|
||||
// There are two possible APIs for X11 surfaces (SDL supports both)
|
||||
// just try to enable both and hope SDL will be happy.
|
||||
// Both are optional, in case one is not supported, but in the end
|
||||
// one of them will be needed.
|
||||
outExtensions.push_back({.name = "VK_KHR_xcb_surface", .required = false});
|
||||
outExtensions.push_back({.name = "VK_KHR_xlib_surface", .required = false});
|
||||
#elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
|
||||
g_instanceExtensions.push_back({.name = "VK_KHR_win32_surface", .required = true});
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void buildDefaultInstanceLayerList(std::vector<LayerInfo>& outLayers) noexcept
|
||||
{
|
||||
#if !defined(KAZAN_RELEASE)
|
||||
outLayers.push_back({.name = "VK_LAYER_KHRONOS_validation", .required = false});
|
||||
#else
|
||||
(void) outLayers;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::vector<const char*> checkInstanceExtensions(std::vector<ExtensionInfo>& extensions)
|
||||
{
|
||||
std::vector<vk::ExtensionProperties> properties = vk::enumerateInstanceExtensionProperties(nullptr);
|
||||
|
||||
auto isExtensionSupported = [&properties](const char* extension)
|
||||
{
|
||||
for (const vk::ExtensionProperties& props : properties)
|
||||
{
|
||||
if (std::strncmp(props.extensionName, extension, VK_MAX_EXTENSION_NAME_SIZE) == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
std::vector<const char*> enabledExtensions;
|
||||
for (ExtensionInfo& extInfo : extensions)
|
||||
{
|
||||
const bool supported = isExtensionSupported(extInfo.name);
|
||||
if (!supported && extInfo.required)
|
||||
{
|
||||
logAndDie("Required Vulkan instance extension not supported: {}.", extInfo.name);
|
||||
}
|
||||
if (supported) {
|
||||
enabledExtensions.push_back(extInfo.name);
|
||||
}
|
||||
extInfo.enabled = supported;
|
||||
}
|
||||
return enabledExtensions;
|
||||
}
|
||||
|
||||
std::vector<const char*> checkInstanceLayers(std::vector<LayerInfo>& layers)
|
||||
{
|
||||
std::vector<const char*> enabledLayers;
|
||||
#if !defined(KAZAN_RELEASE)
|
||||
std::vector<vk::LayerProperties> properties = vk::enumerateInstanceLayerProperties();
|
||||
|
||||
auto isLayerSupported = [&properties](const char* extension)
|
||||
{
|
||||
for (const vk::LayerProperties& props : properties)
|
||||
{
|
||||
if (std::strncmp(props.layerName, extension, VK_MAX_EXTENSION_NAME_SIZE) == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
for (LayerInfo& layerInfo : layers)
|
||||
{
|
||||
const bool supported = isLayerSupported(layerInfo.name);
|
||||
if (!supported && layerInfo.required)
|
||||
{
|
||||
logAndDie("Required Vulkan instance layer not supported: {}.", layerInfo.name);
|
||||
}
|
||||
if (supported) {
|
||||
enabledLayers.push_back(layerInfo.name);
|
||||
}
|
||||
layerInfo.enabled = supported;
|
||||
}
|
||||
#endif
|
||||
return enabledLayers;
|
||||
}
|
||||
vk::Bool32 VKAPI_PTR vulkanMessengerCallback(
|
||||
VkDebugUtilsMessageSeverityFlagBitsEXT severity,
|
||||
VkDebugUtilsMessageTypeFlagsEXT /* types */,
|
||||
const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
|
||||
void* /* pUserData */
|
||||
)
|
||||
{
|
||||
switch (pCallbackData->messageIdNumber)
|
||||
{
|
||||
case 1303270965: // We use general layout to copy texture to mips. I know it's not optimal, but it's allowed and
|
||||
// works for me ¯\_(ツ)_/¯
|
||||
case 148949623: // The spec says that a) you can't create descriptor sets from layouts with the push-descriptor bit
|
||||
// set and b) you have to bind a descriptor set when using said set layout in vkCmdPushDescriptorSetKHR.
|
||||
// So you have to use a set, but can't create it... Something is off.
|
||||
// Apart from that c) it also says that dstSet is ignored when using vkCmdPushDescriptorSetKHR, so
|
||||
// maybe the validation layers are just wrong.
|
||||
// a) https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkDescriptorSetAllocateInfo.html#VUID-VkDescriptorSetAllocateInfo-pSetLayouts-00308
|
||||
// b) https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkWriteDescriptorSet.html#VUID-VkWriteDescriptorSet-dstSet-00320
|
||||
// c) https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkCmdPushDescriptorSetKHR.html
|
||||
return VK_FALSE;
|
||||
}
|
||||
logMsg("VK> {}", pCallbackData->pMessage);
|
||||
if (/* g_breakOnVulkanError && */ severity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT)
|
||||
{
|
||||
if (const mijin::Result<mijin::Stacktrace> trace = mijin::captureStacktrace(0); trace.isSuccess())
|
||||
{
|
||||
logMsg("{}", *trace);
|
||||
}
|
||||
MIJIN_TRAP();
|
||||
}
|
||||
|
||||
return VK_FALSE;
|
||||
}
|
||||
|
||||
std::pair<std::uint32_t, std::uint32_t> detectQueueFamilies(vk::PhysicalDevice physicalDevice, vk::SurfaceKHR dummySurface)
|
||||
{
|
||||
std::vector<vk::QueueFamilyProperties> queueFamilyProperties = physicalDevice.getQueueFamilyProperties();
|
||||
|
||||
for (std::uint32_t idx = 0; idx < queueFamilyProperties.size(); ++idx)
|
||||
{
|
||||
const vk::QueueFamilyProperties& properties = queueFamilyProperties[idx];
|
||||
const vk::Bool32 surfaceSupport = dummySurface ? physicalDevice.getSurfaceSupportKHR(idx, dummySurface) : VK_TRUE; // no dummy surface -> no surface support needed
|
||||
if (surfaceSupport
|
||||
&& (properties.queueFlags & vk::QueueFlagBits::eGraphics)
|
||||
&& (properties.queueFlags & vk::QueueFlagBits::eCompute))
|
||||
{
|
||||
return std::make_pair(idx, idx);
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_pair(std::numeric_limits<std::uint32_t>::max(), std::numeric_limits<std::uint32_t>::max());
|
||||
}
|
||||
|
||||
PhysicalDeviceInfo getPhysicalDeviceInfo(vk::PhysicalDevice physicalDevice, vk::SurfaceKHR dummySurface)
|
||||
{
|
||||
PhysicalDeviceInfo deviceInfo;
|
||||
|
||||
deviceInfo.device = physicalDevice;
|
||||
deviceInfo.memoryProperties = physicalDevice.getMemoryProperties();
|
||||
deviceInfo.extensions = physicalDevice.enumerateDeviceExtensionProperties();
|
||||
|
||||
auto isExtensionSupported = [&](const char* extension)
|
||||
{
|
||||
for (const vk::ExtensionProperties& props : deviceInfo.extensions)
|
||||
{
|
||||
if (std::strncmp(props.extensionName, extension, VK_MAX_EXTENSION_NAME_SIZE) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
const bool rayTracingSupported = isExtensionSupported(VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME)
|
||||
&& isExtensionSupported(VK_KHR_RAY_TRACING_PIPELINE_EXTENSION_NAME)
|
||||
&& isExtensionSupported(VK_KHR_DEFERRED_HOST_OPERATIONS_EXTENSION_NAME);
|
||||
const bool meshShadersSupported = isExtensionSupported(VK_EXT_MESH_SHADER_EXTENSION_NAME);
|
||||
|
||||
vk::PhysicalDeviceProperties2 deviceProperties = {
|
||||
.pNext = &deviceInfo.rayTracingProperties
|
||||
};
|
||||
physicalDevice.getProperties2(&deviceProperties);
|
||||
deviceInfo.properties = deviceProperties.properties;
|
||||
|
||||
#define ADD_TO_NEXT_CHAIN(struct_name) \
|
||||
*ppNext = &(struct_name); ppNext = &(struct_name).pNext
|
||||
vk::PhysicalDeviceFeatures2 deviceFeatures;
|
||||
void** ppNext = &deviceFeatures.pNext;
|
||||
ADD_TO_NEXT_CHAIN(deviceInfo.vulkan11Features);
|
||||
ADD_TO_NEXT_CHAIN(deviceInfo.vulkan12Features);
|
||||
ADD_TO_NEXT_CHAIN(deviceInfo.vulkan13Features);
|
||||
if (rayTracingSupported)
|
||||
{
|
||||
ADD_TO_NEXT_CHAIN(deviceInfo.accelerationStructureFeatures);
|
||||
ADD_TO_NEXT_CHAIN(deviceInfo.rayTracingPipelineFeatures);
|
||||
}
|
||||
if (meshShadersSupported) {
|
||||
ADD_TO_NEXT_CHAIN(deviceInfo.meshShaderFeatures);
|
||||
}
|
||||
|
||||
#undef ADD_TO_NEXT_CHAIN
|
||||
physicalDevice.getFeatures2(&deviceFeatures);
|
||||
deviceInfo.features = deviceFeatures.features;
|
||||
|
||||
// fill the renderer features
|
||||
deviceInfo.availableFeatures.rayTracing = rayTracingSupported;
|
||||
deviceInfo.availableFeatures.meshShaders = meshShadersSupported && deviceInfo.meshShaderFeatures.meshShader;
|
||||
|
||||
deviceInfo.surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(dummySurface);
|
||||
std::tie(deviceInfo.graphicsQueueFamily, deviceInfo.computeQueueFamily) = detectQueueFamilies(physicalDevice, dummySurface);
|
||||
|
||||
return deviceInfo;
|
||||
}
|
||||
|
||||
std::vector<PhysicalDeviceInfo> getPhysicalDeviceInfos(Instance& instance)
|
||||
{
|
||||
const std::vector<vk::PhysicalDevice> devices = instance.getVkHandle().enumeratePhysicalDevices();
|
||||
std::vector<PhysicalDeviceInfo> deviceInfos;
|
||||
deviceInfos.reserve(devices.size());
|
||||
|
||||
const ObjectPtr<Window> dummyWindow = instance.createWindow({.flags={.hidden=true}});
|
||||
const vk::SurfaceKHR dummySurface = dummyWindow->getVkSurface();
|
||||
|
||||
for (const vk::PhysicalDevice device : devices)
|
||||
{
|
||||
deviceInfos.push_back(getPhysicalDeviceInfo(device, dummySurface));
|
||||
}
|
||||
|
||||
return deviceInfos;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Instance::Instance(InstanceCreationArgs args)
|
||||
: super_t(nullptr),
|
||||
mExtensions(std::move(args.extensions)),
|
||||
mLayers(std::move(args.layers))
|
||||
{
|
||||
setMainThread();
|
||||
|
||||
const AddonInitArgs addonInitArgs = {
|
||||
.instance = getPointer(),
|
||||
.instanceCreationArgs = args
|
||||
};
|
||||
for (Addon* addon : getAddons())
|
||||
{
|
||||
addon->init(addonInitArgs);
|
||||
}
|
||||
|
||||
VULKAN_HPP_DEFAULT_DISPATCHER.init();
|
||||
|
||||
if (!args.flags.noDefaultExtensions)
|
||||
{
|
||||
buildDefaultInstanceExtensionList(mExtensions);
|
||||
}
|
||||
if (!args.flags.noDefaultLayers)
|
||||
{
|
||||
buildDefaultInstanceLayerList(mLayers);
|
||||
}
|
||||
|
||||
const std::vector<const char*> enabledLayerNames = checkInstanceLayers(mLayers);
|
||||
const std::vector<const char*> enabledExtensionNames = checkInstanceExtensions(mExtensions);
|
||||
const vk::InstanceCreateInfo createInfo =
|
||||
{
|
||||
.pApplicationInfo = &args.applicationInfo,
|
||||
.enabledLayerCount = static_cast<std::uint32_t>(enabledLayerNames.size()),
|
||||
.ppEnabledLayerNames = enabledLayerNames.data(),
|
||||
.enabledExtensionCount = static_cast<std::uint32_t>(enabledExtensionNames.size()),
|
||||
.ppEnabledExtensionNames = enabledExtensionNames.data()
|
||||
};
|
||||
mHandle = vk::createInstance(createInfo);
|
||||
VULKAN_HPP_DEFAULT_DISPATCHER.init(mHandle);
|
||||
|
||||
// dump info about enabled extensions and layers
|
||||
logMsg("Enabled Vulkan instance extensions: {}", enabledExtensionNames);
|
||||
logMsg("Enabled Vulkan instance layers: {}", enabledLayerNames);
|
||||
|
||||
// also dump the actual instance version
|
||||
const std::uint32_t apiVersion = vk::enumerateInstanceVersion();
|
||||
logMsg("Vulkan instance version: {}.{}.{}.\n",
|
||||
VK_API_VERSION_MAJOR(apiVersion),
|
||||
VK_API_VERSION_MINOR(apiVersion),
|
||||
VK_API_VERSION_PATCH(apiVersion)
|
||||
);
|
||||
|
||||
if (isExtensionEnabled(VK_EXT_DEBUG_UTILS_EXTENSION_NAME))
|
||||
{
|
||||
// create debug messenger
|
||||
const vk::DebugUtilsMessengerCreateInfoEXT dumCreateInfo =
|
||||
{
|
||||
.messageSeverity = vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError,
|
||||
.messageType = vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation,
|
||||
.pfnUserCallback = &vulkanMessengerCallback
|
||||
};
|
||||
mDebugMessenger = mHandle.createDebugUtilsMessengerEXT(dumCreateInfo);
|
||||
}
|
||||
|
||||
mPhysicalDevices = getPhysicalDeviceInfos(*this);
|
||||
mWorkerTaskLoop.start(10);
|
||||
}
|
||||
|
||||
Instance::~Instance() noexcept
|
||||
{
|
||||
for (Addon* addon : getAddons())
|
||||
{
|
||||
addon->cleanup();
|
||||
}
|
||||
while (!mDeleteQueue.empty())
|
||||
{
|
||||
runDeleters(true);
|
||||
}
|
||||
|
||||
if (mDebugMessenger)
|
||||
{
|
||||
mHandle.destroyDebugUtilsMessengerEXT(mDebugMessenger);
|
||||
}
|
||||
mHandle.destroy();
|
||||
}
|
||||
|
||||
bool Instance::isExtensionEnabled(const char* name) const noexcept
|
||||
{
|
||||
for (const ExtensionInfo& extInfo : mExtensions)
|
||||
{
|
||||
if (std::strcmp(extInfo.name, name) == 0)
|
||||
{
|
||||
return extInfo.enabled;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Instance::isLayerEnabled(const char* name) const noexcept
|
||||
{
|
||||
for (const LayerInfo& layerInfo : mLayers)
|
||||
{
|
||||
if (std::strcmp(layerInfo.name, name) == 0)
|
||||
{
|
||||
return layerInfo.enabled;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
ObjectPtr<Window> Instance::createWindow(const WindowCreationArgs& args)
|
||||
{
|
||||
return createChild<Window>(args);
|
||||
}
|
||||
|
||||
ObjectPtr<Device> Instance::createDevice(DeviceCreationArgs args)
|
||||
{
|
||||
return createChild<Device>(std::move(args));
|
||||
}
|
||||
|
||||
void Instance::queueDelete(deleter_t deleter) noexcept
|
||||
{
|
||||
MIJIN_ASSERT(deleter, "Don't pass an empty function into queueDelete()!");
|
||||
if (!deleter)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
runOnMainThread([deleter = std::move(deleter), this]() mutable
|
||||
{
|
||||
static const int DELETE_DELAY_FRAMES = 5; // TODO: ?
|
||||
mDeleteQueue.push_back({
|
||||
.deleter = std::move(deleter),
|
||||
.remainingFrames = DELETE_DELAY_FRAMES
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void Instance::tickDeleteQueue()
|
||||
{
|
||||
for (DeleteQueueEntry& entry : mDeleteQueue)
|
||||
{
|
||||
--entry.remainingFrames;
|
||||
}
|
||||
runDeleters();
|
||||
}
|
||||
|
||||
void Instance::setMainThread()
|
||||
{
|
||||
mMainThread = std::this_thread::get_id();
|
||||
}
|
||||
|
||||
void Instance::requestQuit() noexcept
|
||||
{
|
||||
if (mQuitRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
mQuitRequested = true;
|
||||
quitRequested.emit();
|
||||
|
||||
mMainTaskLoop.addTask([](Instance* instance) -> mijin::Task<>
|
||||
{
|
||||
for (int tick = 0; tick < 100; ++tick)
|
||||
{
|
||||
if (instance->getMainTaskLoop().getNumTasks() == 1)
|
||||
{
|
||||
// just us left, bye bye!
|
||||
co_return;
|
||||
}
|
||||
co_await mijin::c_suspend();
|
||||
}
|
||||
const std::size_t activeTasks = instance->getMainTaskLoop().getActiveTasks() - 1; // substract yourself
|
||||
if (activeTasks > 0)
|
||||
{
|
||||
logMsg("{} tasks did not finish when shutting down!", activeTasks - 1);
|
||||
#if MIJIN_COROUTINE_ENABLE_DEBUG_INFO
|
||||
const std::vector<mijin::TaskHandle> tasks = instance->getMainTaskLoop().getAllTasks();
|
||||
for (auto [index, handle] : mijin::enumerate(tasks))
|
||||
{
|
||||
if (handle == mijin::getCurrentTask())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const mijin::Optional<mijin::Stacktrace> stack = handle.getCreationStack();
|
||||
if (!stack.empty())
|
||||
{
|
||||
logMsg("Task {} creation stack:\n{}", index, *stack);
|
||||
}
|
||||
}
|
||||
#endif // MIJIN_COROUTINE_ENABLE_DEBUG_INFO
|
||||
MIJIN_TRAP();
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::size_t totalTasks = instance->getMainTaskLoop().getNumTasks() - 1;
|
||||
logMsg("{} tasks still waiting on program exit.", totalTasks);
|
||||
}
|
||||
instance->getMainTaskLoop().cancelAllTasks();
|
||||
co_return;
|
||||
}(this));
|
||||
}
|
||||
|
||||
void Instance::runDeleters(bool runAll)
|
||||
{
|
||||
for (auto it = mDeleteQueue.begin(); it != mDeleteQueue.end();)
|
||||
{
|
||||
if (runAll || it->remainingFrames <= 0)
|
||||
{
|
||||
it->deleter();
|
||||
it = mDeleteQueue.erase(it);
|
||||
}
|
||||
else
|
||||
{
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
207
source/io/bitmap.cpp
Normal file
207
source/io/bitmap.cpp
Normal file
@@ -0,0 +1,207 @@
|
||||
|
||||
#include "iwa/io/bitmap.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <mijin/detect.hpp>
|
||||
|
||||
#if MIJIN_COMPILER == MIJIN_COMPILER_GCC || MIJIN_COMPILER == MIJIN_COMPILER_CLANG
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
|
||||
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
|
||||
#endif // MIJIN_COMPILER == MIJIN_COMPILER_GCC || MIJIN_COMPILER == MIJIN_COMPILER_CLANG
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||
#define STBI_FAILURE_USERMSG
|
||||
#include <stb_image.h>
|
||||
#include <stb_image_write.h>
|
||||
#undef STB_IMAGE_IMPLEMENTATION
|
||||
#undef STB_IMAGE_WRITE_IMPLEMENTATION
|
||||
#if MIJIN_COMPILER == MIJIN_COMPILER_GCC || MIJIN_COMPILER == MIJIN_COMPILER_CLANG
|
||||
#pragma GCC diagnostic pop
|
||||
#endif // MIJIN_COMPILER == MIJIN_COMPILER_GCC || MIJIN_COMPILER == MIJIN_COMPILER_CLANG
|
||||
|
||||
#include "iwa/log.hpp"
|
||||
|
||||
namespace iwa
|
||||
{
|
||||
namespace
|
||||
{
|
||||
vk::Format formatByComponents(int components, ImageFormatType formatType)
|
||||
{
|
||||
switch (formatType)
|
||||
{
|
||||
case ImageFormatType::UNORM:
|
||||
switch (components)
|
||||
{
|
||||
case 1:
|
||||
return vk::Format::eR8Unorm;
|
||||
case 2:
|
||||
return vk::Format::eR8G8Unorm;
|
||||
case 3:
|
||||
return vk::Format::eR8G8B8Unorm;
|
||||
case 4:
|
||||
return vk::Format::eR8G8B8A8Unorm;
|
||||
}
|
||||
break;
|
||||
case ImageFormatType::UINT:
|
||||
switch (components)
|
||||
{
|
||||
case 1:
|
||||
return vk::Format::eR8Uint;
|
||||
case 2:
|
||||
return vk::Format::eR8G8Uint;
|
||||
case 3:
|
||||
return vk::Format::eR8G8B8Uint;
|
||||
case 4:
|
||||
return vk::Format::eR8G8B8A8Uint;
|
||||
}
|
||||
break;
|
||||
case ImageFormatType::SRGB:
|
||||
switch (components)
|
||||
{
|
||||
case 1:
|
||||
return vk::Format::eR8Srgb;
|
||||
case 2:
|
||||
return vk::Format::eR8G8Srgb;
|
||||
case 3:
|
||||
return vk::Format::eR8G8B8Srgb;
|
||||
case 4:
|
||||
return vk::Format::eR8G8B8A8Srgb;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
logAndDie("Could not detect image format.");
|
||||
}
|
||||
|
||||
ObjectPtr<Bitmap> loadBitmapFromSTBI(stbi_uc* data, std::size_t width, std::size_t height, std::size_t /* components */,
|
||||
const BitmapLoadOptions& options)
|
||||
{
|
||||
if (data == nullptr)
|
||||
{
|
||||
logAndDie("Could not load texture: {}", stbi_failure_reason());
|
||||
}
|
||||
|
||||
const BitmapCreationArgs args =
|
||||
{
|
||||
.format = formatByComponents(/*components */ 4, options.formatType),
|
||||
.size = {static_cast<unsigned>(width), static_cast<unsigned>(height)}
|
||||
};
|
||||
|
||||
ObjectPtr <Bitmap> bitmap = Bitmap::create(args);
|
||||
std::memcpy(bitmap->getData().data(), data, bitmap->getData().size());
|
||||
stbi_image_free(data);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
int stbiReadCallback(void* user, char* data, int size)
|
||||
{
|
||||
mijin::Stream& stream = *static_cast<mijin::Stream*>(user);
|
||||
std::size_t bytesRead = 0;
|
||||
const mijin::StreamError error = stream.readRaw(data, size, /* partial = */ true, &bytesRead);
|
||||
if (error != mijin::StreamError::SUCCESS)
|
||||
{
|
||||
// TODO: return what?
|
||||
return 0;
|
||||
}
|
||||
return static_cast<int>(bytesRead);
|
||||
}
|
||||
|
||||
void stbiSkipCallback(void* user, int bytes)
|
||||
{
|
||||
mijin::Stream& stream = *static_cast<mijin::Stream*>(user);
|
||||
(void) stream.seek(bytes, mijin::SeekMode::RELATIVE);
|
||||
}
|
||||
|
||||
int stbiEofCallback(void* user)
|
||||
{
|
||||
mijin::Stream& stream = *static_cast<mijin::Stream*>(user);
|
||||
return stream.isAtEnd();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
ObjectPtr<Bitmap> loadBitmap(const BitmapLoadOptions& options)
|
||||
{
|
||||
int width, height, components; // NOLINT(cppcoreguidelines-init-variables)
|
||||
const stbi_io_callbacks callbacks{
|
||||
.read = &stbiReadCallback,
|
||||
.skip = &stbiSkipCallback,
|
||||
.eof = &stbiEofCallback
|
||||
};
|
||||
stbi_uc* data = stbi_load_from_callbacks(
|
||||
/* clbk = */ &callbacks,
|
||||
/* user = */ options.stream,
|
||||
/* x = */ &width,
|
||||
/* y = */ &height,
|
||||
/* channels_in_file = */ &components,
|
||||
/* desired_channels = */ 4);
|
||||
// NOLINTNEXTLINE(clang-analyzer-core.CallAndMessage) Yes, width isn't initialized if STB fails. No, that is not a problem
|
||||
return loadBitmapFromSTBI(data, width, height, components, options);
|
||||
}
|
||||
|
||||
void saveBitmap(const Bitmap& bitmap, const BitmapSaveOptions& options)
|
||||
{
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
BitmapCodec codec = options.codec;
|
||||
if (codec == BitmapCodec::NONE)
|
||||
{
|
||||
std::string extension = fs::path(options.fileName).extension().string();
|
||||
std::ranges::transform(extension, extension.begin(), [](char chr)
|
||||
{ return static_cast<char>(std::tolower(chr)); });
|
||||
if (extension == ".png")
|
||||
{
|
||||
codec = BitmapCodec::PNG;
|
||||
} else if (extension == ".jpg" || extension == ".jpeg")
|
||||
{
|
||||
codec = BitmapCodec::JPEG;
|
||||
} else
|
||||
{
|
||||
logAndDie("Couldn't guess image file codec from file extension: {}.", extension);
|
||||
}
|
||||
}
|
||||
|
||||
int comp = 0;
|
||||
switch (bitmap.getFormat())
|
||||
{
|
||||
case vk::Format::eR8G8B8A8Unorm:
|
||||
case vk::Format::eR8G8B8A8Srgb:
|
||||
comp = 4;
|
||||
break;
|
||||
case vk::Format::eR8G8B8Unorm:
|
||||
case vk::Format::eR8G8B8Srgb:
|
||||
comp = 3;
|
||||
break;
|
||||
default:
|
||||
logAndDie("Cannot write this image format (yet).");
|
||||
break;
|
||||
}
|
||||
|
||||
switch (codec)
|
||||
{
|
||||
case BitmapCodec::NONE:
|
||||
assert(0);
|
||||
break;
|
||||
case BitmapCodec::PNG:
|
||||
stbi_write_png(
|
||||
/* filename = */ options.fileName.c_str(),
|
||||
/* w = */ static_cast<int>(bitmap.getSize().width),
|
||||
/* h = */ static_cast<int>(bitmap.getSize().height),
|
||||
/* comp = */ comp,
|
||||
/* data = */ bitmap.getData().data(),
|
||||
/* stride_in_bytes = */ comp * static_cast<int>(bitmap.getSize().width));
|
||||
break;
|
||||
case BitmapCodec::JPEG:
|
||||
stbi_write_jpg(
|
||||
/* filename = */ options.fileName.c_str(),
|
||||
/* x = */ static_cast<int>(bitmap.getSize().width),
|
||||
/* y = */ static_cast<int>(bitmap.getSize().height),
|
||||
/* comp = */ comp,
|
||||
/* data = */ bitmap.getData().data(),
|
||||
/* quality = */ options.jpegQuality
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} // namespace iwa
|
||||
165
source/io/font.cpp
Normal file
165
source/io/font.cpp
Normal file
@@ -0,0 +1,165 @@
|
||||
|
||||
#include "iwa/io/font.hpp"
|
||||
|
||||
#include <mijin/util/iterators.hpp>
|
||||
|
||||
#define STB_RECT_PACK_IMPLEMENTATION
|
||||
#define STB_TRUETYPE_IMPLEMENTATION
|
||||
#include <stb_rect_pack.h>
|
||||
#include <stb_truetype.h>
|
||||
#undef STB_RECT_PACK_IMPLEMENTATION
|
||||
#undef STB_TRUETYPE_IMPLEMENTATION
|
||||
|
||||
namespace iwa
|
||||
{
|
||||
namespace
|
||||
{
|
||||
std::vector<int> getRenderCodePoints()
|
||||
{
|
||||
std::vector<int> result;
|
||||
for (int chr = 32; chr <= 255; ++chr) {
|
||||
result.push_back(chr);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
ObjectPtr<Font> loadFont(const FontLoadOptions& options)
|
||||
{
|
||||
mijin::TypelessBuffer fontData;
|
||||
if (const mijin::StreamError error = options.stream->readRest(fontData); error != mijin::StreamError::SUCCESS) {
|
||||
throw std::runtime_error("IO error reading font from stream.");
|
||||
}
|
||||
|
||||
// init font data
|
||||
stbtt_fontinfo fontInfo;
|
||||
const int fontOffset = stbtt_GetFontOffsetForIndex(static_cast<const unsigned char*>(fontData.data()), 0);
|
||||
stbtt_InitFont(&fontInfo, static_cast<const unsigned char*>(fontData.data()), fontOffset);
|
||||
|
||||
// prepare data for packing
|
||||
std::vector<int> codepoints = getRenderCodePoints();
|
||||
std::vector<stbtt_packedchar> chardata;
|
||||
std::vector<stbrp_rect> rects;
|
||||
|
||||
chardata.resize(codepoints.size());
|
||||
rects.resize(codepoints.size());
|
||||
stbtt_pack_range range = {
|
||||
.font_size = options.size,
|
||||
.first_unicode_codepoint_in_range = 0,
|
||||
.array_of_unicode_codepoints = codepoints.data(),
|
||||
.num_chars = static_cast<int>(codepoints.size()),
|
||||
.chardata_for_range = chardata.data()
|
||||
};
|
||||
|
||||
// TODO: this is just a guess, maybe there is a better way to detect this
|
||||
int oversample = 1;
|
||||
if (options.size < 30) {
|
||||
oversample = 4;
|
||||
}
|
||||
else if (options.size < 35) {
|
||||
oversample = 3;
|
||||
}
|
||||
else if (options.size < 40) {
|
||||
oversample = 4;
|
||||
}
|
||||
|
||||
int textureSize = 64;
|
||||
mijin::TypelessBuffer pixels;
|
||||
while (textureSize <= 4096)
|
||||
{
|
||||
pixels.resize(textureSize * textureSize * sizeof(unsigned char)); // NOLINT
|
||||
|
||||
// reset char data
|
||||
for (stbtt_packedchar& packedChar : chardata) {
|
||||
packedChar.x0 = packedChar.y0 = packedChar.x1 = packedChar.y1 = 0;
|
||||
}
|
||||
|
||||
// try packing with this size
|
||||
stbtt_pack_context packContext;
|
||||
stbtt_PackBegin(
|
||||
/* spc = */ &packContext,
|
||||
/* pixels = */ static_cast<unsigned char*>(pixels.data()),
|
||||
/* width = */ textureSize,
|
||||
/* height = */ textureSize,
|
||||
/* stride_in_bytes = */ 0,
|
||||
/* padding = */ 1,
|
||||
/* alloc_context = */ nullptr
|
||||
);
|
||||
stbtt_PackSetOversampling(&packContext, oversample, oversample); // TODO: make adjustable for better font quality
|
||||
|
||||
const int nRects = stbtt_PackFontRangesGatherRects(&packContext, &fontInfo, &range, 1, rects.data());
|
||||
stbtt_PackFontRangesPackRects(&packContext, rects.data(), nRects);
|
||||
|
||||
bool allPacked = true;
|
||||
for (int rectIdx = 0; rectIdx < nRects; ++rectIdx)
|
||||
{
|
||||
if (!rects[rectIdx].was_packed) {
|
||||
allPacked = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!allPacked)
|
||||
{
|
||||
textureSize *= 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
stbtt_PackFontRangesRenderIntoRects(
|
||||
/* spc = */ &packContext,
|
||||
/* info = */ &fontInfo,
|
||||
/* ranges = */ &range,
|
||||
/* num_ranges = */ 1,
|
||||
/* rects = */ rects.data()
|
||||
);
|
||||
|
||||
stbtt_PackEnd(&packContext);
|
||||
break;
|
||||
}
|
||||
|
||||
// now create a bitmap from pixels
|
||||
ObjectPtr<Bitmap> bitmap = Bitmap::create(BitmapCreationArgs{
|
||||
.format = vk::Format::eR8Unorm,
|
||||
.size = {
|
||||
.width = static_cast<std::uint32_t>(textureSize),
|
||||
.height = static_cast<std::uint32_t>(textureSize)
|
||||
},
|
||||
.initialData = std::move(pixels)
|
||||
});
|
||||
std::unordered_map<char32_t, GlyphInfo> glyphMap;
|
||||
glyphMap.reserve(chardata.size());
|
||||
|
||||
for (auto [codepoint, chrdata] : mijin::zip(codepoints, chardata))
|
||||
{
|
||||
glyphMap.emplace(static_cast<char32_t>(codepoint), GlyphInfo{
|
||||
.uvPos0 = {
|
||||
static_cast<float>(chrdata.x0) / static_cast<float>(textureSize),
|
||||
static_cast<float>(chrdata.y0) / static_cast<float>(textureSize)
|
||||
},
|
||||
.uvPos1 = {
|
||||
static_cast<float>(chrdata.x1) / static_cast<float>(textureSize),
|
||||
static_cast<float>(chrdata.y1) / static_cast<float>(textureSize)
|
||||
},
|
||||
.xOffsetBefore = chrdata.xoff,
|
||||
.xOffsetAfter = chrdata.xoff2,
|
||||
.yOffsetBefore = chrdata.yoff,
|
||||
.yOffsetAfter = chrdata.yoff2,
|
||||
.xAdvance = chrdata.xadvance
|
||||
});
|
||||
}
|
||||
|
||||
const float scale = options.size > 0 ? stbtt_ScaleForPixelHeight(&fontInfo, options.size) : stbtt_ScaleForMappingEmToPixels(&fontInfo, -options.size);
|
||||
int ascent = 0, descent = 0, lineGap = 0;
|
||||
stbtt_GetFontVMetrics(&fontInfo, &ascent, &descent, &lineGap);
|
||||
|
||||
return Font::create(FontCreationArgs{
|
||||
.bitmap = std::move(bitmap),
|
||||
.glyphMap = std::move(glyphMap),
|
||||
.metrics = {
|
||||
.ascent = scale * static_cast<float>(ascent),
|
||||
.descent = scale * static_cast<float>(descent),
|
||||
.lineGap = scale * static_cast<float>(lineGap),
|
||||
.sizeFactor = 1.f / static_cast<float>(oversample)
|
||||
}
|
||||
});
|
||||
}
|
||||
} // namespace iwa
|
||||
6
source/io/mesh.cpp
Normal file
6
source/io/mesh.cpp
Normal file
@@ -0,0 +1,6 @@
|
||||
|
||||
#include "iwa/io/mesh.hpp"
|
||||
|
||||
namespace iwa
|
||||
{
|
||||
} // namespace iwa
|
||||
130
source/object.cpp
Normal file
130
source/object.cpp
Normal file
@@ -0,0 +1,130 @@
|
||||
|
||||
#include "iwa/object.hpp"
|
||||
|
||||
#if IWA_OBJECTPTR_TRACKING
|
||||
#include <iostream>
|
||||
#endif
|
||||
#include <mutex>
|
||||
#include <shared_mutex>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace iwa::impl
|
||||
{
|
||||
namespace
|
||||
{
|
||||
std::atomic<object_id_t> gNextObjectId;
|
||||
std::unordered_map<object_id_t, BaseObject*> gAllObjects;
|
||||
std::shared_mutex gAllObjectMutex;
|
||||
std::unordered_map<object_id_t, object_destruction_handler_t> gDestructionHandlers;
|
||||
std::shared_mutex gDestructionHandlersMutex;
|
||||
|
||||
#if IWA_OBJECTPTR_TRACKING
|
||||
std::list<ObjectPtrAllocation> gObjectPtrAllocations;
|
||||
std::mutex gObjectPtrAllocationsMutex;
|
||||
|
||||
struct ObjectPtrAllocationsChecker
|
||||
{
|
||||
~ObjectPtrAllocationsChecker()
|
||||
{
|
||||
const std::unique_lock lock(gObjectPtrAllocationsMutex);
|
||||
if (!gObjectPtrAllocations.empty())
|
||||
{
|
||||
std::cerr << "ObjectPtrs pending deletion:\n";
|
||||
for (const ObjectPtrAllocation& allocation : gObjectPtrAllocations)
|
||||
{
|
||||
std::cerr << typeid(*allocation.object).name() /* << "@" << allocation.stacktrace */ <<"\n";
|
||||
#if IWA_OBJECTPTR_TRACKING > 1
|
||||
if (allocation.stacktrace.isSuccess())
|
||||
{
|
||||
std::cerr << *allocation.stacktrace << "\n";
|
||||
}
|
||||
#endif
|
||||
}
|
||||
std::cerr.flush();
|
||||
MIJIN_TRAP();
|
||||
}
|
||||
}
|
||||
} gObjectPtrAllocationsChecker;
|
||||
#endif // IWA_OBJECTPTR_TRACKING
|
||||
}
|
||||
|
||||
#if IWA_OBJECTPTR_TRACKING
|
||||
#if IWA_OBJECTPTR_TRACKING > 1
|
||||
objectptr_allocation_handle_t trackObjectPtr(BaseObject* object, mijin::Result<mijin::Stacktrace>&& stacktrace) noexcept
|
||||
{
|
||||
const std::unique_lock lock(gObjectPtrAllocationsMutex);
|
||||
gObjectPtrAllocations.emplace_back(object, std::move(stacktrace));
|
||||
return std::prev(gObjectPtrAllocations.end());
|
||||
}
|
||||
#else
|
||||
objectptr_allocation_handle_t trackObjectPtr(BaseObject* object) noexcept
|
||||
{
|
||||
const std::unique_lock lock(gObjectPtrAllocationsMutex);
|
||||
gObjectPtrAllocations.emplace_back(object);
|
||||
return std::prev(gObjectPtrAllocations.end());
|
||||
}
|
||||
#endif
|
||||
|
||||
void untrackObjectPtr(objectptr_allocation_handle_t handle) noexcept
|
||||
{
|
||||
const std::unique_lock lock(gObjectPtrAllocationsMutex);
|
||||
gObjectPtrAllocations.erase(*handle); // NOLINT(bugprone-unchecked-optional-access)
|
||||
}
|
||||
#endif // IWA_OBJECTPTR_TRACKING
|
||||
|
||||
object_id_t nextObjectId() noexcept
|
||||
{
|
||||
return ++gNextObjectId;
|
||||
}
|
||||
|
||||
void registerObject(BaseObject* object) noexcept
|
||||
{
|
||||
const std::unique_lock lock(gAllObjectMutex);
|
||||
MIJIN_ASSERT(gAllObjects.find(object->getId()) == gAllObjects.end(), "Duplicate object id!");
|
||||
gAllObjects.emplace(object->getId(), object);
|
||||
}
|
||||
|
||||
void unregisterObject(BaseObject* object) noexcept
|
||||
{
|
||||
{
|
||||
const std::unique_lock lock(gAllObjectMutex);
|
||||
gAllObjects.erase(object->getId());
|
||||
}
|
||||
|
||||
std::unordered_map<object_id_t, object_destruction_handler_t>::iterator itHandler;
|
||||
{
|
||||
const std::shared_lock readLock(gDestructionHandlersMutex);
|
||||
itHandler = gDestructionHandlers.find(object->getId());
|
||||
}
|
||||
|
||||
if (itHandler != gDestructionHandlers.end())
|
||||
{
|
||||
object_destruction_handler_t handler;
|
||||
{
|
||||
const std::unique_lock writeLock(gDestructionHandlersMutex);
|
||||
handler = std::move(itHandler->second);
|
||||
gDestructionHandlers.erase(itHandler);
|
||||
}
|
||||
handler();
|
||||
}
|
||||
}
|
||||
|
||||
ObjectPtr<BaseObject> getRegisteredObject(object_id_t objectId) noexcept
|
||||
{
|
||||
const std::shared_lock lock(gAllObjectMutex);
|
||||
auto it = gAllObjects.find(objectId);
|
||||
if (it != gAllObjects.end()) {
|
||||
return it->second->getPointer();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
} // namespace iwa::impl
|
||||
|
||||
namespace iwa
|
||||
{
|
||||
void registerObjectDestructionHandler(const BaseObject& object, object_destruction_handler_t handler) noexcept
|
||||
{
|
||||
const std::unique_lock writeLock(impl::gDestructionHandlersMutex);
|
||||
impl::gDestructionHandlers.emplace(object.getId(), std::move(handler));
|
||||
} // namespace iwa
|
||||
}
|
||||
136
source/pipeline.cpp
Normal file
136
source/pipeline.cpp
Normal file
@@ -0,0 +1,136 @@
|
||||
|
||||
#include "iwa/pipeline.hpp"
|
||||
|
||||
#include <mijin/io/stream.hpp>
|
||||
#include <yaml-cpp/yaml.h>
|
||||
#include "iwa/device.hpp"
|
||||
#include "iwa/util/glsl_compiler.hpp"
|
||||
#include "iwa/util/next_chain.hpp"
|
||||
|
||||
namespace iwa
|
||||
{
|
||||
PipelineLayout::PipelineLayout(ObjectPtr<Device> owner, const PipelineLayoutCreationArgs& args) : super_t(std::move(owner))
|
||||
{
|
||||
std::vector<vk::DescriptorSetLayout> setLayoutHandles;
|
||||
setLayoutHandles.reserve(args.setLayouts.size());
|
||||
for (const ObjectPtr<DescriptorSetLayout>& setLayout : args.setLayouts)
|
||||
{
|
||||
setLayoutHandles.push_back(setLayout->getVkHandle());
|
||||
}
|
||||
|
||||
mHandle = getOwner()->getVkHandle().createPipelineLayout(vk::PipelineLayoutCreateInfo{
|
||||
.flags = args.flags,
|
||||
.setLayoutCount = static_cast<std::uint32_t>(setLayoutHandles.size()),
|
||||
.pSetLayouts = setLayoutHandles.data(),
|
||||
.pushConstantRangeCount = static_cast<std::uint32_t>(args.pushConstantRanges.size()),
|
||||
.pPushConstantRanges = args.pushConstantRanges.data()
|
||||
});
|
||||
}
|
||||
|
||||
PipelineLayout::~PipelineLayout() noexcept
|
||||
{
|
||||
IWA_DELETE_DEVICE_OBJECT(getOwner(), mHandle, destroyPipelineLayout)
|
||||
}
|
||||
|
||||
Pipeline::Pipeline(ObjectPtr<Device> owner) noexcept : super_t(std::move(owner))
|
||||
{
|
||||
}
|
||||
|
||||
Pipeline::~Pipeline() noexcept
|
||||
{
|
||||
IWA_DELETE_DEVICE_OBJECT(getOwner(), mHandle, destroyPipeline)
|
||||
}
|
||||
|
||||
GraphicsPipeline::GraphicsPipeline(ObjectPtr<Device> owner, const GraphicsPipelineCreationArgs& args) noexcept
|
||||
: super_t(std::move(owner))
|
||||
{
|
||||
std::vector<vk::PipelineShaderStageCreateInfo> vkStages;
|
||||
|
||||
for (const PipelineStage& stage : args.stages)
|
||||
{
|
||||
vk::PipelineShaderStageCreateInfo& vkStage = vkStages.emplace_back();
|
||||
vkStage.stage = stage.stage;
|
||||
vkStage.module = *stage.shader;
|
||||
vkStage.pName = stage.name.c_str();
|
||||
}
|
||||
|
||||
const vk::PipelineVertexInputStateCreateInfo vkVertexInput =
|
||||
{
|
||||
.vertexBindingDescriptionCount = static_cast<std::uint32_t>(args.vertexInput.bindings.size()),
|
||||
.pVertexBindingDescriptions = args.vertexInput.bindings.data(),
|
||||
.vertexAttributeDescriptionCount = static_cast<std::uint32_t>(args.vertexInput.attributes.size()),
|
||||
.pVertexAttributeDescriptions = args.vertexInput.attributes.data()
|
||||
};
|
||||
|
||||
const vk::PipelineColorBlendStateCreateInfo vkColorBlend =
|
||||
{
|
||||
.logicOpEnable = args.colorBlend.logicOp.has_value(),
|
||||
.logicOp = args.colorBlend.logicOp.has_value() ? *args.colorBlend.logicOp : vk::LogicOp(),
|
||||
.attachmentCount = static_cast<std::uint32_t>(args.colorBlend.attachements.size()),
|
||||
.pAttachments = args.colorBlend.attachements.data()
|
||||
};
|
||||
|
||||
const vk::PipelineDynamicStateCreateInfo vkDynamic =
|
||||
{
|
||||
.dynamicStateCount = static_cast<std::uint32_t>(args.dynamicState.size()),
|
||||
.pDynamicStates = args.dynamicState.data()
|
||||
};
|
||||
|
||||
NextChain nextChain;
|
||||
if (args.renderingInfo.has_value())
|
||||
{
|
||||
const GraphicsPipelineRenderingInfo& rinfo = *args.renderingInfo;
|
||||
nextChain.append(vk::PipelineRenderingCreateInfo{
|
||||
.viewMask = rinfo.viewMask,
|
||||
.colorAttachmentCount = static_cast<std::uint32_t>(rinfo.colorAttachmentFormats.size()),
|
||||
.pColorAttachmentFormats = rinfo.colorAttachmentFormats.data(),
|
||||
.depthAttachmentFormat = rinfo.depthFormat,
|
||||
.stencilAttachmentFormat = rinfo.stencilFormat
|
||||
});
|
||||
}
|
||||
|
||||
const vk::GraphicsPipelineCreateInfo vkCreateInfo =
|
||||
{
|
||||
.pNext = nextChain.finalize(),
|
||||
.stageCount = static_cast<std::uint32_t>(vkStages.size()),
|
||||
.pStages = vkStages.data(),
|
||||
.pVertexInputState = &vkVertexInput,
|
||||
.pInputAssemblyState = &args.inputAssembly,
|
||||
.pViewportState = &args.viewport,
|
||||
.pRasterizationState = &args.rasterization,
|
||||
.pMultisampleState = &args.multisample,
|
||||
.pDepthStencilState = &args.depthStencil,
|
||||
.pColorBlendState = &vkColorBlend,
|
||||
.pDynamicState = &vkDynamic,
|
||||
.layout = *args.layout,
|
||||
.renderPass = args.renderPass ? *args.renderPass : vk::RenderPass(),
|
||||
.subpass = args.subpass
|
||||
};
|
||||
|
||||
const auto result = getOwner()->getVkHandle().createGraphicsPipeline(VK_NULL_HANDLE, vkCreateInfo);
|
||||
MIJIN_ASSERT(result.result == vk::Result::eSuccess, "Graphics pipeline creation failed.");
|
||||
mHandle = result.value;
|
||||
}
|
||||
|
||||
ComputePipeline::ComputePipeline(ObjectPtr<Device> owner, const ComputePipelineCreationArgs& args) noexcept
|
||||
: super_t(std::move(owner))
|
||||
{
|
||||
const vk::ComputePipelineCreateInfo vkCreateInfo{
|
||||
.stage = {
|
||||
.stage = vk::ShaderStageFlagBits::eCompute,
|
||||
.module = *args.stage.shader,
|
||||
.pName = args.stage.name.c_str()
|
||||
},
|
||||
.layout = *args.layout
|
||||
};
|
||||
const vk::ResultValue<vk::Pipeline> result = getOwner()->getVkHandle().createComputePipeline(VK_NULL_HANDLE, vkCreateInfo);
|
||||
MIJIN_ASSERT(result.result == vk::Result::eSuccess, "Compute pipeline creation failed.");
|
||||
mHandle = result.value;
|
||||
}
|
||||
|
||||
RayTracingPipeline::RayTracingPipeline(ObjectPtr<Device> owner, const RayTracingPipelineCreationArgs& args) noexcept
|
||||
: super_t(std::move(owner))
|
||||
{
|
||||
(void) args;
|
||||
}
|
||||
} // namespace iwa
|
||||
70
source/render_pass.cpp
Normal file
70
source/render_pass.cpp
Normal file
@@ -0,0 +1,70 @@
|
||||
|
||||
#include "iwa/render_pass.hpp"
|
||||
|
||||
#include "iwa/device.hpp"
|
||||
#include "iwa/image.hpp"
|
||||
|
||||
namespace iwa
|
||||
{
|
||||
RenderPass::RenderPass(ObjectPtr<Device> owner, const RenderPassCreationArgs& args) : super_t(std::move(owner))
|
||||
{
|
||||
std::vector<vk::SubpassDescription> vkSubpasses;
|
||||
vkSubpasses.reserve(args.subpasses.size());
|
||||
for (const SubpassDescription& subpass : args.subpasses)
|
||||
{
|
||||
MIJIN_ASSERT(subpass.resolveAttachments.empty() || subpass.resolveAttachments.size() == subpass.colorAttachments.size(),
|
||||
"Number of resolve attachments must be either 0 or the same as color attachments.");
|
||||
vkSubpasses.push_back({
|
||||
.flags = subpass.flags,
|
||||
.pipelineBindPoint = subpass.pipelineBindPoint,
|
||||
.inputAttachmentCount = static_cast<std::uint32_t>(subpass.inputAttachments.size()),
|
||||
.pInputAttachments = subpass.inputAttachments.data(),
|
||||
.colorAttachmentCount = static_cast<std::uint32_t>(subpass.colorAttachments.size()),
|
||||
.pColorAttachments = subpass.colorAttachments.data(),
|
||||
.pResolveAttachments = subpass.resolveAttachments.data(),
|
||||
.pDepthStencilAttachment = subpass.depthStencilAttachment.has_value() ? &subpass.depthStencilAttachment.value() : nullptr,
|
||||
.preserveAttachmentCount = static_cast<std::uint32_t>(subpass.preserveAttachments.size()),
|
||||
.pPreserveAttachments = subpass.preserveAttachments.data()
|
||||
});
|
||||
}
|
||||
mHandle = getOwner()->getVkHandle().createRenderPass(vk::RenderPassCreateInfo{
|
||||
.flags = args.flags,
|
||||
.attachmentCount = static_cast<std::uint32_t>(args.attachments.size()),
|
||||
.pAttachments = args.attachments.data(),
|
||||
.subpassCount = static_cast<std::uint32_t>(vkSubpasses.size()),
|
||||
.pSubpasses = vkSubpasses.data(),
|
||||
.dependencyCount = static_cast<std::uint32_t>(args.dependencies.size()),
|
||||
.pDependencies = args.dependencies.data()
|
||||
});
|
||||
}
|
||||
|
||||
RenderPass::~RenderPass() noexcept
|
||||
{
|
||||
IWA_DELETE_DEVICE_OBJECT(getOwner(), mHandle, destroyRenderPass);
|
||||
}
|
||||
|
||||
Framebuffer::Framebuffer(ObjectPtr<Device> owner, const FramebufferCreationArgs& args)
|
||||
: super_t(std::move(owner)), mImageViews(args.attachments)
|
||||
{
|
||||
std::vector<vk::ImageView> vkImageViews;
|
||||
vkImageViews.reserve(mImageViews.size());
|
||||
for (const ObjectPtr<ImageView>& imageView : mImageViews)
|
||||
{
|
||||
vkImageViews.push_back(*imageView);
|
||||
}
|
||||
mHandle = getOwner()->getVkHandle().createFramebuffer(vk::FramebufferCreateInfo{
|
||||
.flags = args.flags,
|
||||
.renderPass = *args.renderPass,
|
||||
.attachmentCount = static_cast<std::uint32_t>(vkImageViews.size()),
|
||||
.pAttachments = vkImageViews.data(),
|
||||
.width = args.width,
|
||||
.height = args.height,
|
||||
.layers = args.layers
|
||||
});
|
||||
}
|
||||
|
||||
Framebuffer::~Framebuffer() noexcept
|
||||
{
|
||||
IWA_DELETE_DEVICE_OBJECT(getOwner(), mHandle, destroyFramebuffer);
|
||||
}
|
||||
} // namespace iwa
|
||||
305
source/resource/bitmap.cpp
Normal file
305
source/resource/bitmap.cpp
Normal file
@@ -0,0 +1,305 @@
|
||||
|
||||
#include "iwa/resource/bitmap.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include "iwa/log.hpp"
|
||||
#include "iwa/util/vkutil.hpp"
|
||||
|
||||
namespace iwa
|
||||
{
|
||||
enum class InterpolationMethod
|
||||
{
|
||||
NEAREST
|
||||
};
|
||||
|
||||
class BitmapViewBase
|
||||
{
|
||||
protected:
|
||||
Bitmap* mBitmap;
|
||||
protected:
|
||||
explicit inline BitmapViewBase(Bitmap* bitmap_) : mBitmap(bitmap_) {}
|
||||
public:
|
||||
BitmapViewBase(const BitmapViewBase&) = delete;
|
||||
BitmapViewBase(BitmapViewBase&&) = default;
|
||||
|
||||
BitmapViewBase& operator=(const BitmapViewBase&) = delete;
|
||||
BitmapViewBase& operator=(BitmapViewBase&&) = default;
|
||||
|
||||
virtual ~BitmapViewBase() = default; // just to make clang happy, should be optimised out again
|
||||
[[nodiscard]] virtual glm::vec4 getPixel(unsigned x, unsigned y) const = 0;
|
||||
[[nodiscard]] virtual glm::vec4 getPixelRelative(float x, float y, InterpolationMethod interpolation = InterpolationMethod::NEAREST) const = 0;
|
||||
[[nodiscard]] virtual std::vector<glm::vec4> getPixels(unsigned x, unsigned y, unsigned width, unsigned height) const = 0;
|
||||
virtual void fill(const glm::vec4& color) = 0;
|
||||
virtual void copyChannels(const Bitmap& other, const std::vector<ChannelMapping>& mappings) = 0;
|
||||
virtual void multiply(const glm::vec4& color) = 0;
|
||||
};
|
||||
|
||||
template<typename TData, int numComponents, bool normalized = std::is_floating_point_v<TData>>
|
||||
class BitmapView : public BitmapViewBase
|
||||
{
|
||||
private:
|
||||
static constexpr std::size_t pixelSize = sizeof(TData) * numComponents;
|
||||
public:
|
||||
explicit inline BitmapView(Bitmap* bitmap_) : BitmapViewBase(bitmap_) {}
|
||||
TData* getPixelRaw(unsigned x, unsigned y)
|
||||
{
|
||||
const std::size_t offset = x + y * mBitmap->getSize().width;
|
||||
return reinterpret_cast<TData*>(&mBitmap->getData()[offset * pixelSize]);
|
||||
}
|
||||
|
||||
[[nodiscard]] const TData* getPixelRaw(unsigned x, unsigned y) const
|
||||
{
|
||||
const std::size_t offset = x + y * mBitmap->getSize().width;
|
||||
return reinterpret_cast<const TData*>(&mBitmap->getData()[offset * pixelSize]);
|
||||
}
|
||||
|
||||
[[nodiscard]] TData convertChannel(float channel) const
|
||||
{
|
||||
if constexpr (normalized) {
|
||||
return static_cast<TData>(channel);
|
||||
} else {
|
||||
return static_cast<TData>(255 * channel);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] float convertChannelBack(TData channel) const
|
||||
{
|
||||
if constexpr (normalized) {
|
||||
return static_cast<float>(channel);
|
||||
}
|
||||
else {
|
||||
return channel / 255.f;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] TData clampChannel(TData value)
|
||||
{
|
||||
if constexpr (normalized) {
|
||||
return std::clamp(value, TData(0), TData(1));
|
||||
}
|
||||
else {
|
||||
return std::clamp(value, TData(0), TData(255));
|
||||
}
|
||||
}
|
||||
|
||||
void convertColor(const glm::vec4& color, std::array<TData, numComponents>& outConverted) const
|
||||
{
|
||||
for (int comp = 0; comp < numComponents; ++comp)
|
||||
{
|
||||
outConverted[comp] = convertChannel(color[comp]);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] glm::vec4 convertColorBack(const TData* raw) const
|
||||
{
|
||||
glm::vec4 result;
|
||||
for (int comp = 0; comp < numComponents; ++comp)
|
||||
{
|
||||
result[comp] = convertChannelBack(raw[comp]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// overrides
|
||||
[[nodiscard]] glm::vec4 getPixel(unsigned x, unsigned y) const override
|
||||
{
|
||||
const TData* pixelRaw = getPixelRaw(x, y);
|
||||
return convertColorBack(pixelRaw);
|
||||
}
|
||||
|
||||
[[nodiscard]] glm::vec4 getPixelRelative(float x, float y, InterpolationMethod interpolation = InterpolationMethod::NEAREST) const override
|
||||
{
|
||||
const vk::Extent2D size = mBitmap->getSize();
|
||||
(void) interpolation; // TODO
|
||||
const unsigned myX = std::clamp(static_cast<unsigned>(std::round(x * static_cast<float>(size.width))), 0u, size.width - 1);
|
||||
const unsigned myY = std::clamp(static_cast<unsigned>(std::round(y * static_cast<float>(size.height))), 0u, size.height - 1);
|
||||
|
||||
return getPixel(myX, myY);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::vector<glm::vec4> getPixels(unsigned x, unsigned y, unsigned width, unsigned height) const override
|
||||
{
|
||||
std::vector<glm::vec4> pixels;
|
||||
pixels.resize(static_cast<std::size_t>(width) * height);
|
||||
|
||||
for (unsigned ypos = y; ypos < y + height; ++ypos)
|
||||
{
|
||||
for (unsigned xpos = x; xpos < x + width; ++xpos)
|
||||
{
|
||||
pixels[xpos + height * ypos] = getPixel(xpos, ypos);
|
||||
}
|
||||
}
|
||||
return pixels;
|
||||
}
|
||||
|
||||
void fill(const glm::vec4& color) override
|
||||
{
|
||||
std::array<TData, numComponents> convertedColor; // NOLINT(cppcoreguidelines-pro-type-member-init)
|
||||
convertColor(color, convertedColor);
|
||||
|
||||
for (unsigned y = 0; y < mBitmap->getSize().height; ++y)
|
||||
{
|
||||
for (unsigned x = 0; x < mBitmap->getSize().width; ++x)
|
||||
{
|
||||
TData* pixel = getPixelRaw(x, y);
|
||||
for (int comp = 0; comp < numComponents; ++comp)
|
||||
{
|
||||
pixel[comp] = convertedColor[comp];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: maybe introduce a "dual-mView" class that includes both types at once?
|
||||
void copyChannels(const Bitmap& other, const std::vector<ChannelMapping>& mappings) override
|
||||
{
|
||||
const vk::Extent2D size = mBitmap->getSize();
|
||||
const std::vector<glm::vec4> otherPixels = other.getAllPixels();
|
||||
|
||||
if(otherPixels.size() == static_cast<std::size_t>(size.width) * size.height)
|
||||
{
|
||||
for (unsigned y = 0; y < size.height; ++y)
|
||||
{
|
||||
for (unsigned x = 0; x < size.width; ++x)
|
||||
{
|
||||
const glm::vec4 color = otherPixels[x + y * size.height];
|
||||
for (const ChannelMapping& mapping : mappings)
|
||||
{
|
||||
assert(static_cast<int>(mapping.to) < numComponents);
|
||||
TData* myPixel = getPixelRaw(x, y);
|
||||
myPixel[static_cast<int>(mapping.to)] = convertChannel(color[static_cast<int>(mapping.from)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const vk::Extent2D otherSize = other.getSize();
|
||||
const float factorX = static_cast<float>(otherSize.width) / static_cast<float>(size.width);
|
||||
const float factorY = static_cast<float>(otherSize.height) / static_cast<float>(size.height);
|
||||
|
||||
for (unsigned y = 0; y < size.height; ++y)
|
||||
{
|
||||
for (unsigned x = 0; x < size.width; ++x)
|
||||
{
|
||||
const unsigned otherX = std::clamp(static_cast<unsigned>(std::round(factorX * static_cast<float>(x))), 0u, otherSize.width - 1);
|
||||
const unsigned otherY = std::clamp(static_cast<unsigned>(std::round(factorY * static_cast<float>(x))), 0u, otherSize.height - 1);
|
||||
const glm::vec4 color = otherPixels[otherX + otherY * size.height];
|
||||
for (const ChannelMapping& mapping : mappings)
|
||||
{
|
||||
assert(static_cast<int>(mapping.to) < numComponents);
|
||||
TData* myPixel = getPixelRaw(x, y);
|
||||
myPixel[static_cast<int>(mapping.to)] = convertChannel(color[static_cast<int>(mapping.from)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void multiply(const glm::vec4& color) override
|
||||
{
|
||||
for (unsigned y = 0; y < mBitmap->getSize().height; ++y)
|
||||
{
|
||||
for (unsigned x = 0; x < mBitmap->getSize().width; ++x)
|
||||
{
|
||||
TData* pixel = getPixelRaw(x, y);
|
||||
for (int comp = 0; comp < numComponents; ++comp)
|
||||
{
|
||||
pixel[comp] = clampChannel(static_cast<TData>(pixel[comp] * color[comp]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void Bitmap::createView()
|
||||
{
|
||||
switch(mFormat)
|
||||
{
|
||||
case vk::Format::eR8Uint:
|
||||
case vk::Format::eR8Unorm:
|
||||
case vk::Format::eR8Srgb:
|
||||
mView = std::make_unique<BitmapView<std::uint8_t, 1>>(this);
|
||||
break;
|
||||
case vk::Format::eR8G8Uint:
|
||||
case vk::Format::eR8G8Unorm:
|
||||
case vk::Format::eR8G8Srgb:
|
||||
mView = std::make_unique<BitmapView<std::uint8_t, 2>>(this);
|
||||
break;
|
||||
case vk::Format::eR8G8B8Uint:
|
||||
case vk::Format::eR8G8B8Unorm:
|
||||
case vk::Format::eR8G8B8Srgb:
|
||||
mView = std::make_unique<BitmapView<std::uint8_t, 3>>(this);
|
||||
break;
|
||||
case vk::Format::eR8G8B8A8Uint:
|
||||
case vk::Format::eR8G8B8A8Unorm:
|
||||
case vk::Format::eR8G8B8A8Srgb:
|
||||
mView = std::make_unique<BitmapView<std::uint8_t, 4>>(this);
|
||||
break;
|
||||
case vk::Format::eR32Sfloat:
|
||||
mView = std::make_unique<BitmapView<float, 1>>(this);
|
||||
break;
|
||||
case vk::Format::eR32G32Sfloat:
|
||||
mView = std::make_unique<BitmapView<float, 2>>(this);
|
||||
break;
|
||||
case vk::Format::eR32G32B32Sfloat:
|
||||
mView = std::make_unique<BitmapView<float, 3>>(this);
|
||||
break;
|
||||
case vk::Format::eR32G32B32A32Sfloat:
|
||||
mView = std::make_unique<BitmapView<float, 4>>(this);
|
||||
break;
|
||||
default:
|
||||
logAndDie("Missing format for bitmap mView!");
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// public functions
|
||||
//
|
||||
Bitmap::Bitmap(BitmapCreationArgs args, ObjectPtr<BaseObject> owner)
|
||||
: super_t(std::move(owner)), mFormat(args.format), mSize(args.size)
|
||||
{
|
||||
const std::size_t dataSize = static_cast<std::size_t>(args.size.width) * args.size.height * vkFormatSize(args.format);
|
||||
if (args.initialData.empty())
|
||||
{
|
||||
mData.resize(dataSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
MIJIN_ASSERT(args.initialData->byteSize() == dataSize, "Bitmap initial data size is invalid.");
|
||||
mData = std::move(*args.initialData);
|
||||
}
|
||||
|
||||
createView();
|
||||
}
|
||||
|
||||
Bitmap::~Bitmap() // NOLINT(modernize-use-equals-default)
|
||||
{
|
||||
// needs to be implemented for the mView to be deleted correctly
|
||||
}
|
||||
|
||||
glm::vec4 Bitmap::getPixel(unsigned int x, unsigned int y) const
|
||||
{
|
||||
return mView->getPixel(x, y);
|
||||
}
|
||||
|
||||
std::vector<glm::vec4> Bitmap::getPixels(unsigned x, unsigned y, unsigned width, unsigned height) const
|
||||
{
|
||||
return mView->getPixels(x, y, width, height);
|
||||
}
|
||||
|
||||
void Bitmap::fill(const glm::vec4& color)
|
||||
{
|
||||
mView->fill(color);
|
||||
}
|
||||
|
||||
void Bitmap::copyChannels(const Bitmap& other, const std::vector<ChannelMapping>& mappings)
|
||||
{
|
||||
mView->copyChannels(other, mappings);
|
||||
}
|
||||
|
||||
void Bitmap::multiply(const glm::vec4& color)
|
||||
{
|
||||
mView->multiply(color);
|
||||
}
|
||||
} // namespace iwa
|
||||
11
source/resource/font.cpp
Normal file
11
source/resource/font.cpp
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
#include "iwa/resource/font.hpp"
|
||||
|
||||
namespace iwa
|
||||
{
|
||||
Font::Font(FontCreationArgs args)
|
||||
: mBitmap(std::move(args.bitmap)), mGlyphMap(std::move(args.glyphMap)), mMetrics(args.metrics)
|
||||
{
|
||||
|
||||
}
|
||||
} // namespace iwa
|
||||
19
source/semaphore.cpp
Normal file
19
source/semaphore.cpp
Normal file
@@ -0,0 +1,19 @@
|
||||
|
||||
#include "iwa/semaphore.hpp"
|
||||
|
||||
#include "iwa/device.hpp"
|
||||
|
||||
namespace iwa
|
||||
{
|
||||
Semaphore::Semaphore(ObjectPtr<Device> owner, const SemaphoreCreationArgs& args) : super_t(std::move(owner))
|
||||
{
|
||||
mHandle = getOwner()->getVkHandle().createSemaphore(vk::SemaphoreCreateInfo{
|
||||
.flags = args.flags
|
||||
});
|
||||
}
|
||||
|
||||
Semaphore::~Semaphore() noexcept
|
||||
{
|
||||
IWA_DELETE_DEVICE_OBJECT(getOwner(), mHandle, destroySemaphore)
|
||||
}
|
||||
} // namespace iwa
|
||||
20
source/shader_module.cpp
Normal file
20
source/shader_module.cpp
Normal file
@@ -0,0 +1,20 @@
|
||||
|
||||
#include "iwa/shader_module.hpp"
|
||||
|
||||
#include "iwa/device.hpp"
|
||||
|
||||
namespace iwa
|
||||
{
|
||||
ShaderModule::ShaderModule(ObjectPtr<Device> owner, const ShaderModuleCreationArgs& args) : super_t(std::move(owner))
|
||||
{
|
||||
mHandle = getOwner()->getVkHandle().createShaderModule(vk::ShaderModuleCreateInfo{
|
||||
.codeSize = static_cast<std::uint32_t>(args.code.size_bytes()),
|
||||
.pCode = args.code.data()
|
||||
});
|
||||
}
|
||||
|
||||
ShaderModule::~ShaderModule() noexcept
|
||||
{
|
||||
IWA_DELETE_DEVICE_OBJECT(getOwner(), mHandle, destroyShaderModule)
|
||||
}
|
||||
} // namespace iwa
|
||||
316
source/swapchain.cpp
Normal file
316
source/swapchain.cpp
Normal file
@@ -0,0 +1,316 @@
|
||||
|
||||
#include "iwa/swapchain.hpp"
|
||||
|
||||
#include <mijin/debug/assert.hpp>
|
||||
#include <SDL_vulkan.h>
|
||||
#include "iwa/device.hpp"
|
||||
#include "iwa/log.hpp"
|
||||
|
||||
namespace iwa
|
||||
{
|
||||
namespace
|
||||
{
|
||||
std::pair<vk::PresentModeKHR, std::uint32_t> detectPresentMode(const Device& device, const Window& window, const vk::SurfaceCapabilitiesKHR& surfaceCapabilities)
|
||||
{
|
||||
std::vector<vk::PresentModeKHR> presentModes = device.getVkPhysicalDevice().getSurfacePresentModesKHR(window.getVkSurface());
|
||||
|
||||
vk::PresentModeKHR presentMode = vk::PresentModeKHR::eImmediate;
|
||||
std::uint32_t imageCount = 2;
|
||||
if (std::ranges::find(presentModes, vk::PresentModeKHR::eMailbox) != presentModes.end())
|
||||
{
|
||||
presentMode = vk::PresentModeKHR::eMailbox;
|
||||
imageCount = 3;
|
||||
}
|
||||
imageCount = std::max(imageCount, surfaceCapabilities.minImageCount);
|
||||
if (surfaceCapabilities.maxImageCount > 0)
|
||||
{
|
||||
imageCount = std::min(imageCount, surfaceCapabilities.maxImageCount);
|
||||
}
|
||||
return std::make_pair(presentMode, imageCount);
|
||||
}
|
||||
|
||||
vk::SurfaceFormatKHR detectImageFormat(const Device& device, const Window& window, const vk::ImageUsageFlags imageUsage)
|
||||
{
|
||||
std::vector<vk::SurfaceFormatKHR> surfaceFormats = device.getVkPhysicalDevice().getSurfaceFormatsKHR(window.getVkSurface());
|
||||
|
||||
int resultIdx = -1;
|
||||
for (std::size_t idx = 0; idx < surfaceFormats.size(); ++idx)
|
||||
{
|
||||
const vk::SurfaceFormatKHR surfaceFormat = surfaceFormats[idx];
|
||||
vk::ImageFormatProperties formatProperties;
|
||||
|
||||
try
|
||||
{
|
||||
formatProperties = device.getVkPhysicalDevice().getImageFormatProperties(
|
||||
/* format = */ surfaceFormat.format,
|
||||
/* type = */ vk::ImageType::e2D,
|
||||
/* tiling = */ vk::ImageTiling::eOptimal,
|
||||
/* usage = */ imageUsage
|
||||
);
|
||||
}
|
||||
catch(vk::FormatNotSupportedError&)
|
||||
{
|
||||
continue; // not supported
|
||||
}
|
||||
|
||||
if (resultIdx < 0)
|
||||
{
|
||||
resultIdx = static_cast<int>(idx);
|
||||
}
|
||||
else if (surfaceFormat.format == vk::Format::eB8G8R8A8Srgb
|
||||
&& surfaceFormat.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear)
|
||||
{
|
||||
resultIdx = static_cast<int>(idx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (resultIdx < 0)
|
||||
{
|
||||
logAndDie("Error creating Vulkan swapchain, no compatible image format found.");
|
||||
}
|
||||
|
||||
return surfaceFormats[resultIdx];
|
||||
}
|
||||
|
||||
vk::Extent2D detectImageExtent(const Window& window, const vk::SurfaceCapabilitiesKHR& surfaceCapabilities)
|
||||
{
|
||||
// default to current extent (chosen by the driver)
|
||||
vk::Extent2D extent = surfaceCapabilities.currentExtent;
|
||||
|
||||
// alternatively ask SDL about it
|
||||
if (extent.width == 0xFFFFFFFF && extent.height == 0xFFFFFFFF)
|
||||
{
|
||||
int width, height; // NOLINT(readability-isolate-declaration, cppcoreguidelines-init-variables)
|
||||
SDL_Vulkan_GetDrawableSize(window.getSDLWindow(), &width, &height);
|
||||
|
||||
extent.width = std::clamp(static_cast<std::uint32_t>(width), surfaceCapabilities.minImageExtent.width, surfaceCapabilities.maxImageExtent.width);
|
||||
extent.height = std::clamp(static_cast<std::uint32_t>(height), surfaceCapabilities.minImageExtent.height, surfaceCapabilities.maxImageExtent.height);
|
||||
}
|
||||
|
||||
return extent;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Swapchain::Swapchain(ObjectPtr<Device> owner, SwapchainCreationArgs args)
|
||||
: super_t(std::move(owner)), mWindow(std::move(args.window)), mImageUsage(args.imageUsage)
|
||||
{
|
||||
MIJIN_ASSERT(mWindow, "Invalid SwapchainCreationArgs: window cannot be NULL.");
|
||||
|
||||
mImageAvailableSemaphores.resize(args.parallelFrames);
|
||||
recreate();
|
||||
}
|
||||
Swapchain::~Swapchain() noexcept
|
||||
{
|
||||
IWA_DELETE_DEVICE_OBJECT(getOwner(), mHandle, destroySwapchainKHR)
|
||||
}
|
||||
|
||||
mijin::Task<> Swapchain::c_present(const PresentArgs& args)
|
||||
{
|
||||
while (!mWindow->isVisible()) {
|
||||
co_await mijin::c_suspend();
|
||||
}
|
||||
|
||||
const vk::PresentInfoKHR presentInfo =
|
||||
{
|
||||
.waitSemaphoreCount = static_cast<std::uint32_t>(args.waitSemaphores.size()),
|
||||
.pWaitSemaphores = args.waitSemaphores.data(),
|
||||
.swapchainCount = 1,
|
||||
.pSwapchains = &mHandle,
|
||||
.pImageIndices = &mCurrentImageIdx
|
||||
};
|
||||
|
||||
vk::Result result; // NOLINT(cppcoreguidelines-init-variables)
|
||||
try {
|
||||
result = args.queue.presentKHR(presentInfo);
|
||||
} catch(vk::OutOfDateKHRError&) {
|
||||
result = vk::Result::eErrorOutOfDateKHR;
|
||||
}
|
||||
mCurrentImageIdx = INVALID_IMAGE_INDEX;
|
||||
|
||||
// next image, please
|
||||
mCurrentFrameIdx = (mCurrentFrameIdx + 1) % static_cast<int>(getNumParallelFrames());
|
||||
if (result == vk::Result::eSuccess) {
|
||||
co_await c_acquireImage();
|
||||
}
|
||||
else {
|
||||
recreate();
|
||||
}
|
||||
}
|
||||
|
||||
void Swapchain::recreate()
|
||||
{
|
||||
const vk::SurfaceCapabilitiesKHR surfaceCapabilities = getOwner()->getVkPhysicalDevice().getSurfaceCapabilitiesKHR(mWindow->getVkSurface());
|
||||
const bool firstCreate = !mHandle;
|
||||
|
||||
// doing this here seems to fix the device loss during recreation
|
||||
getOwner()->getVkHandle().waitIdle();
|
||||
|
||||
vk::SwapchainCreateInfoKHR createInfo;
|
||||
|
||||
// surface
|
||||
createInfo.surface = mWindow->getVkSurface();
|
||||
|
||||
// old swapchain
|
||||
createInfo.oldSwapchain = mHandle;
|
||||
|
||||
// some defaults
|
||||
createInfo.imageSharingMode = vk::SharingMode::eExclusive;
|
||||
createInfo.clipped = VK_TRUE;
|
||||
createInfo.preTransform = surfaceCapabilities.currentTransform;
|
||||
|
||||
// queue families
|
||||
createInfo.queueFamilyIndexCount = 1;
|
||||
createInfo.pQueueFamilyIndices = &getOwner()->getDeviceInfo().graphicsQueueFamily;
|
||||
|
||||
// present mode and image count
|
||||
std::tie(createInfo.presentMode, createInfo.minImageCount) = detectPresentMode(*getOwner(), *mWindow, surfaceCapabilities);
|
||||
|
||||
// image usage
|
||||
createInfo.imageUsage = mImageUsage;
|
||||
MIJIN_ASSERT((surfaceCapabilities.supportedUsageFlags & createInfo.imageUsage) == createInfo.imageUsage, "Invalid image usage flags when creating surface.");
|
||||
|
||||
// image format and color space
|
||||
const vk::SurfaceFormatKHR surfaceFormat = detectImageFormat(*getOwner(), *mWindow, createInfo.imageUsage);
|
||||
createInfo.imageFormat = surfaceFormat.format;
|
||||
createInfo.imageColorSpace = surfaceFormat.colorSpace;
|
||||
|
||||
// image extent
|
||||
createInfo.imageExtent = detectImageExtent(*mWindow, surfaceCapabilities);
|
||||
createInfo.imageArrayLayers = 1;
|
||||
|
||||
// composite alpha
|
||||
createInfo.compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque;
|
||||
MIJIN_ASSERT((surfaceCapabilities.supportedCompositeAlpha & createInfo.compositeAlpha) == createInfo.compositeAlpha, "Invalid composite alpha when creating surface.");
|
||||
|
||||
mHandle = getOwner()->getVkHandle().createSwapchainKHR(createInfo);
|
||||
mFormat = createInfo.imageFormat;
|
||||
mExtent = createInfo.imageExtent;
|
||||
|
||||
//for (ObjectPtr<Image>& image : mImages)
|
||||
//{
|
||||
// image->unwrap();
|
||||
// unregisterImage(&image);
|
||||
//}
|
||||
mImages.clear();
|
||||
|
||||
// retrieve swapchain images
|
||||
const std::vector<vk::Image> imageHandles = getOwner()->getVkHandle().getSwapchainImagesKHR(mHandle);
|
||||
mImages.reserve(imageHandles.size());
|
||||
|
||||
// and create image views
|
||||
// destroyImageViews();
|
||||
// imageViews.reserve(imageHandles.size());
|
||||
|
||||
for (const vk::Image image : imageHandles)
|
||||
{
|
||||
mImages.emplace_back(getOwner()->createChild<Image>(ImageWrapArgs{
|
||||
.handle = image,
|
||||
.type = vk::ImageType::e2D,
|
||||
.format = createInfo.imageFormat,
|
||||
.usage = createInfo.imageUsage,
|
||||
.size = {
|
||||
createInfo.imageExtent.width,
|
||||
createInfo.imageExtent.height,
|
||||
1
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
// const ImageViewCreateInfo ivCreateInfo =
|
||||
// {
|
||||
// .nativeImage = image,
|
||||
// .viewType = vk::ImageViewType::e2D,
|
||||
// .format = createInfo.imageFormat,
|
||||
// .components =
|
||||
// {
|
||||
// .r = vk::ComponentSwizzle::eIdentity,
|
||||
// .g = vk::ComponentSwizzle::eIdentity,
|
||||
// .b = vk::ComponentSwizzle::eIdentity,
|
||||
// .a = vk::ComponentSwizzle::eIdentity
|
||||
// },
|
||||
// .subresourceRange =
|
||||
// {
|
||||
// .aspectMask = vk::ImageAspectFlagBits::eColor,
|
||||
// .baseMipLevel = 0,
|
||||
// .levelCount = 1,
|
||||
// .baseArrayLayer = 0,
|
||||
// .layerCount = 1
|
||||
// }
|
||||
// };
|
||||
// imageViews.emplace_back().create(ivCreateInfo);
|
||||
|
||||
// images.back().setDebugName(fmt::format("Swapchain Image #{}", images.size() - 1));
|
||||
// registerImage(&images.back());
|
||||
}
|
||||
|
||||
// create "image available" semaphores
|
||||
for (ObjectPtr<Semaphore>& semaphore : mImageAvailableSemaphores)
|
||||
{
|
||||
semaphore = getOwner()->createChild<Semaphore>();
|
||||
}
|
||||
|
||||
if (!firstCreate)
|
||||
{
|
||||
getOwner()->getVkHandle().destroySwapchainKHR(createInfo.oldSwapchain);
|
||||
}
|
||||
|
||||
// update size
|
||||
// imageSize.width = createInfo.imageExtent.width;
|
||||
// imageSize.height = createInfo.imageExtent.height;
|
||||
// imageFormat = createInfo.imageFormat;
|
||||
|
||||
// already request the first frame
|
||||
acquireImage();
|
||||
|
||||
recreated.emit();
|
||||
}
|
||||
|
||||
void Swapchain::acquireImage()
|
||||
{
|
||||
MIJIN_ASSERT(mCurrentImageIdx == INVALID_IMAGE_INDEX, "Attempting to acquire a new image before presenting.");
|
||||
|
||||
try
|
||||
{
|
||||
const vk::ResultValue<std::uint32_t> result = getOwner()->getVkHandle().acquireNextImageKHR(mHandle, std::numeric_limits<std::uint64_t>::max(), *mImageAvailableSemaphores[mCurrentFrameIdx]);
|
||||
if (result.result != vk::Result::eSuccess)
|
||||
{
|
||||
assert(result.result == vk::Result::eSuboptimalKHR);
|
||||
recreate();
|
||||
return;
|
||||
}
|
||||
mCurrentImageIdx = result.value;
|
||||
mImages[mCurrentImageIdx]->resetUsage(ResetLayout::YES);
|
||||
}
|
||||
catch(vk::OutOfDateKHRError&) {
|
||||
recreate();
|
||||
}
|
||||
}
|
||||
|
||||
mijin::Task<> Swapchain::c_acquireImage()
|
||||
{
|
||||
try
|
||||
{
|
||||
vk::ResultValue<std::uint32_t> result = vk::ResultValue<std::uint32_t>(vk::Result::eTimeout, 0);
|
||||
while(true)
|
||||
{
|
||||
result = getOwner()->getVkHandle().acquireNextImageKHR(mHandle, 0, *mImageAvailableSemaphores[mCurrentFrameIdx]);
|
||||
if (result.result != vk::Result::eTimeout && result.result != vk::Result::eNotReady) {
|
||||
break;
|
||||
}
|
||||
co_await mijin::c_suspend();
|
||||
}
|
||||
if (result.result != vk::Result::eSuccess)
|
||||
{
|
||||
MIJIN_ASSERT(result.result == vk::Result::eSuboptimalKHR, "Error acquiring swapchain image.");
|
||||
recreate();
|
||||
co_return;
|
||||
}
|
||||
mCurrentImageIdx = result.value;
|
||||
mImages[mCurrentImageIdx]->resetUsage(ResetLayout::YES);
|
||||
}
|
||||
catch(vk::OutOfDateKHRError&) {
|
||||
recreate();
|
||||
}
|
||||
}
|
||||
}
|
||||
47
source/texture.cpp
Normal file
47
source/texture.cpp
Normal file
@@ -0,0 +1,47 @@
|
||||
|
||||
#include "iwa/texture.hpp"
|
||||
|
||||
#include "iwa/device.hpp"
|
||||
#include "iwa/resource/bitmap.hpp"
|
||||
|
||||
namespace iwa
|
||||
{
|
||||
Texture::Texture(TextureCreationArgs args) : super_t(nullptr), mImage(std::move(args.image)),
|
||||
mImageView(mImage->createImageView(args.imageViewArgs)), mSampler(mImage->getOwner()->createChild<Sampler>(args.samplerArgs))
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
mijin::Task<ObjectPtr<Texture>> Texture::c_createSingleColor(const ObjectPtr<Device>& device, const SingleColorTextureArgs& args)
|
||||
{
|
||||
ObjectPtr<Bitmap> bitmap = Bitmap::create(BitmapCreationArgs{
|
||||
.format = args.format,
|
||||
.size = {.width = 1, .height = 1}
|
||||
});
|
||||
bitmap->fill(args.color);
|
||||
co_return co_await c_createFromBitmap(device, {.bitmap = *bitmap});
|
||||
}
|
||||
|
||||
mijin::Task<ObjectPtr<Texture>> Texture::c_createFromBitmap(const ObjectPtr<Device>& device, const TextureFromBitmapArgs& args)
|
||||
{
|
||||
ImageCreationArgs imageCreationArgs = args.imageArgs;
|
||||
imageCreationArgs.format = args.bitmap.getFormat();
|
||||
imageCreationArgs.extent.width = args.bitmap.getSize().width;
|
||||
imageCreationArgs.extent.height = args.bitmap.getSize().height;
|
||||
imageCreationArgs.extent.depth = 1;
|
||||
imageCreationArgs.usage |= vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferDst;
|
||||
ObjectPtr<Image> image = device->createChild<Image>(imageCreationArgs);
|
||||
image->allocateMemory();
|
||||
co_await image->c_upload(args.bitmap);
|
||||
co_await image->c_doTransition({
|
||||
.stages = vk::PipelineStageFlagBits::eFragmentShader,
|
||||
.layout = vk::ImageLayout::eShaderReadOnlyOptimal,
|
||||
.access = vk::AccessFlagBits::eShaderRead
|
||||
});
|
||||
co_return create(TextureCreationArgs{
|
||||
.image = std::move(image),
|
||||
.imageViewArgs = args.imageViewArgs,
|
||||
.samplerArgs = args.samplerArgs
|
||||
});
|
||||
}
|
||||
} // namespace iwa
|
||||
548
source/util/glsl_compiler.cpp
Normal file
548
source/util/glsl_compiler.cpp
Normal file
@@ -0,0 +1,548 @@
|
||||
|
||||
#include "iwa/util/glsl_compiler.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <utility>
|
||||
#include <glslang/Include/InfoSink.h>
|
||||
#include <glslang/Public/ShaderLang.h>
|
||||
#include <glslang/MachineIndependent/iomapper.h>
|
||||
#include <glslang/MachineIndependent/localintermediate.h>
|
||||
#include <glslang/Public/ResourceLimits.h>
|
||||
#include <glslang/SPIRV/GlslangToSpv.h>
|
||||
#include <yaml-cpp/yaml.h>
|
||||
#include "iwa/device.hpp"
|
||||
#include "iwa/instance.hpp"
|
||||
#include "iwa/log.hpp"
|
||||
#include "iwa/util/dir_stack_file_includer.hpp"
|
||||
#include "iwa/util/reflect_glsl.hpp"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace iwa
|
||||
{
|
||||
namespace
|
||||
{
|
||||
class CustomFileIncluder : public impl::DirStackFileIncluder
|
||||
{
|
||||
private:
|
||||
fs::path workingDir;
|
||||
mijin::FileSystemAdapter& mFsAdapter;
|
||||
public:
|
||||
explicit CustomFileIncluder(mijin::FileSystemAdapter& fsAdapter) noexcept: mFsAdapter(fsAdapter)
|
||||
{}
|
||||
|
||||
public:
|
||||
void setWorkingDir(const fs::path& workingDir_)
|
||||
{ workingDir = workingDir_; }
|
||||
|
||||
protected: // overrides
|
||||
IncludeResult* readLocalPath(const char* headerName, const char* includerName, int depth) override
|
||||
{
|
||||
// Discard popped include directories, and
|
||||
// initialize when at parse-time first level.
|
||||
directoryStack.resize(depth + externalLocalDirectoryCount);
|
||||
if (depth == 1)
|
||||
{
|
||||
directoryStack.back() = getDirectory(includerName);
|
||||
}
|
||||
|
||||
// Find a directory that works, using a reverse search of the include stack.
|
||||
for (auto it = directoryStack.rbegin(); it != directoryStack.rend(); ++it)
|
||||
{
|
||||
std::string path = *it + '/' + headerName;
|
||||
std::replace(path.begin(), path.end(), '\\', '/');
|
||||
|
||||
std::unique_ptr<mijin::Stream> stream;
|
||||
mijin::StreamError error = mijin::StreamError::UNKNOWN_ERROR;
|
||||
if (workingDir != fs::path())
|
||||
{
|
||||
// try relative include first
|
||||
error = mFsAdapter.open(workingDir / path, mijin::FileOpenMode::READ, stream);
|
||||
}
|
||||
if (error != mijin::StreamError::SUCCESS)
|
||||
{
|
||||
error = mFsAdapter.open(path, mijin::FileOpenMode::READ, stream);
|
||||
}
|
||||
if (error == mijin::StreamError::SUCCESS)
|
||||
{
|
||||
directoryStack.push_back(getDirectory(path));
|
||||
includedFiles.insert(path);
|
||||
return newCustomIncludeResult(path, *stream);
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Do actual reading of the file, filling in a new include result.
|
||||
IncludeResult* newCustomIncludeResult(const std::string& path, mijin::Stream& stream) const
|
||||
{
|
||||
(void) stream.seek(0, mijin::SeekMode::RELATIVE_TO_END);
|
||||
const std::size_t length = stream.tell();
|
||||
(void) stream.seek(0);
|
||||
|
||||
char* content = new tUserDataElement[length]; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
const mijin::StreamError error = stream.readRaw(content, length);
|
||||
if (error != mijin::StreamError::SUCCESS)
|
||||
{
|
||||
logAndDie("Error reading include file.");
|
||||
}
|
||||
return new IncludeResult(path, content, length, content); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
}
|
||||
};
|
||||
|
||||
class SemanticIoResolver : public glslang::TDefaultIoResolverBase
|
||||
{
|
||||
private:
|
||||
const std::vector<GLSLSemanticMapping>& mMappings;
|
||||
public:
|
||||
SemanticIoResolver(const glslang::TIntermediate& intermediate, const std::vector<GLSLSemanticMapping>& mappings)
|
||||
: glslang::TDefaultIoResolverBase(intermediate), mMappings(mappings) {}
|
||||
|
||||
bool validateBinding(EShLanguage /* stage */, glslang::TVarEntryInfo& /* ent */) override { return true; }
|
||||
|
||||
glslang::TResourceType getResourceType(const glslang::TType& type) override {
|
||||
if (isImageType(type)) {
|
||||
return glslang::EResImage;
|
||||
}
|
||||
if (isTextureType(type)) {
|
||||
return glslang::EResTexture;
|
||||
}
|
||||
if (isSsboType(type)) {
|
||||
return glslang::EResSsbo;
|
||||
}
|
||||
if (isSamplerType(type)) {
|
||||
return glslang::EResSampler;
|
||||
}
|
||||
if (isUboType(type)) {
|
||||
return glslang::EResUbo;
|
||||
}
|
||||
return glslang::EResCount;
|
||||
}
|
||||
|
||||
int resolveBinding(EShLanguage stage, glslang::TVarEntryInfo& ent) override
|
||||
{
|
||||
const glslang::TType& type = ent.symbol->getType();
|
||||
if (type.getQualifier().hasSemantic())
|
||||
{
|
||||
const unsigned semantic = type.getQualifier().layoutSemantic;
|
||||
const unsigned semanticIdx = type.getQualifier().hasSemanticIndex() ? type.getQualifier().layoutSemanticIndex : 0;
|
||||
auto it = std::ranges::find_if(mMappings, [&](const GLSLSemanticMapping& mapping)
|
||||
{
|
||||
return mapping.semantic == semantic && mapping.semanticIdx == semanticIdx;
|
||||
});
|
||||
if (it != mMappings.end()) {
|
||||
return ent.newBinding = it->newBinding;
|
||||
}
|
||||
}
|
||||
|
||||
// default implementation
|
||||
const int set = getLayoutSet(type);
|
||||
// On OpenGL arrays of opaque types take a seperate binding for each element
|
||||
const int numBindings = referenceIntermediate.getSpv().openGl != 0 && type.isSizedArray() ? type.getCumulativeArraySize() : 1;
|
||||
const glslang::TResourceType resource = getResourceType(type);
|
||||
if (resource < glslang::EResCount) {
|
||||
if (type.getQualifier().hasBinding()) {
|
||||
return ent.newBinding = reserveSlot(
|
||||
set, getBaseBinding(stage, resource, set) + type.getQualifier().layoutBinding, numBindings);
|
||||
}
|
||||
if (ent.live && doAutoBindingMapping()) {
|
||||
// find free slot, the caller did make sure it passes all vars with binding
|
||||
// first and now all are passed that do not have a binding and needs one
|
||||
return ent.newBinding = getFreeSlot(set, getBaseBinding(stage, resource, set), numBindings);
|
||||
}
|
||||
}
|
||||
return ent.newBinding = -1;
|
||||
}
|
||||
int resolveSet(EShLanguage stage, glslang::TVarEntryInfo& ent) override
|
||||
{
|
||||
const glslang::TType& type = ent.symbol->getType();
|
||||
if (type.getQualifier().hasSemantic())
|
||||
{
|
||||
const unsigned semantic = type.getQualifier().layoutSemantic;
|
||||
const unsigned semanticIdx = type.getQualifier().hasSemanticIndex() ? type.getQualifier().layoutSemanticIndex : 0;
|
||||
auto it = std::ranges::find_if(mMappings, [&](const GLSLSemanticMapping& mapping)
|
||||
{
|
||||
return mapping.semantic == semantic && mapping.semanticIdx == semanticIdx;
|
||||
});
|
||||
if (it == mMappings.end()) {
|
||||
return glslang::TDefaultIoResolverBase::resolveSet(stage, ent);
|
||||
}
|
||||
return ent.newSet = it->newSet;
|
||||
}
|
||||
return glslang::TDefaultIoResolverBase::resolveSet(stage, ent);
|
||||
}
|
||||
void addStage(EShLanguage stage, glslang::TIntermediate& stageIntermediate) override
|
||||
{
|
||||
nextInputLocation = nextOutputLocation = 0;
|
||||
glslang::TDefaultIoResolverBase::addStage(stage, stageIntermediate);
|
||||
}
|
||||
};
|
||||
|
||||
void initGlslang() noexcept
|
||||
{
|
||||
static bool inited = false;
|
||||
if (inited)
|
||||
{
|
||||
return;
|
||||
}
|
||||
inited = true;
|
||||
if (!glslang::InitializeProcess())
|
||||
{
|
||||
logAndDie("Error initializing Glslang.");
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
EShLanguage typeToGlslang(vk::ShaderStageFlagBits type) noexcept
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case vk::ShaderStageFlagBits::eCompute:
|
||||
return EShLangCompute;
|
||||
case vk::ShaderStageFlagBits::eVertex:
|
||||
return EShLangVertex;
|
||||
case vk::ShaderStageFlagBits::eFragment:
|
||||
return EShLangFragment;
|
||||
case vk::ShaderStageFlagBits::eRaygenKHR:
|
||||
return EShLangRayGen;
|
||||
case vk::ShaderStageFlagBits::eClosestHitKHR:
|
||||
return EShLangClosestHit;
|
||||
case vk::ShaderStageFlagBits::eAnyHitKHR:
|
||||
return EShLangAnyHit;
|
||||
case vk::ShaderStageFlagBits::eMissKHR:
|
||||
return EShLangMiss;
|
||||
case vk::ShaderStageFlagBits::eIntersectionKHR:
|
||||
return EShLangIntersect;
|
||||
case vk::ShaderStageFlagBits::eCallableKHR:
|
||||
return EShLangCallable;
|
||||
case vk::ShaderStageFlagBits::eTaskEXT:
|
||||
return EShLangTask;
|
||||
case vk::ShaderStageFlagBits::eMeshEXT:
|
||||
return EShLangMesh;
|
||||
case vk::ShaderStageFlagBits::eTessellationControl:
|
||||
return EShLangTessControl;
|
||||
case vk::ShaderStageFlagBits::eTessellationEvaluation:
|
||||
return EShLangTessEvaluation;
|
||||
case vk::ShaderStageFlagBits::eGeometry:
|
||||
return EShLangGeometry;
|
||||
case vk::ShaderStageFlagBits::eAllGraphics:
|
||||
case vk::ShaderStageFlagBits::eAll:
|
||||
case vk::ShaderStageFlagBits::eSubpassShadingHUAWEI:
|
||||
case vk::ShaderStageFlagBits::eClusterCullingHUAWEI:
|
||||
break; // let it fail
|
||||
}
|
||||
|
||||
logAndDie("Invalid value passed to typeToGlslang!");
|
||||
}
|
||||
|
||||
ShaderSource ShaderSource::fromStream(mijin::Stream& stream, std::string fileName)
|
||||
{
|
||||
ShaderSource result = {
|
||||
.fileName = std::move(fileName)
|
||||
};
|
||||
if (const mijin::StreamError error = stream.readAsString(result.code); error != mijin::StreamError::SUCCESS)
|
||||
{
|
||||
// TODO: custom exception type, for stacktrace and stuff
|
||||
throw std::runtime_error("Error reading shader source.");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
ShaderSource ShaderSource::fromFile(const mijin::PathReference& file)
|
||||
{
|
||||
std::unique_ptr<mijin::Stream> stream;
|
||||
if (const mijin::StreamError error = file.open(mijin::FileOpenMode::READ, stream); error != mijin::StreamError::SUCCESS)
|
||||
{
|
||||
throw std::runtime_error("Error opening file for reading shader source.");
|
||||
}
|
||||
return fromStream(*stream, file.getPath().string());
|
||||
}
|
||||
|
||||
ShaderSource ShaderSource::fromYaml(const YAML::Node& node, const mijin::PathReference& yamlFile)
|
||||
{
|
||||
if (node.Tag() == "!load")
|
||||
{
|
||||
return fromFile(yamlFile.getAdapter()->getPath(node.as<std::string>()));
|
||||
}
|
||||
const std::string& source = node["source"].as<std::string>();
|
||||
std::string fileName;
|
||||
if (const YAML::Node fileNameNode = node["fileName"]; !fileNameNode.IsNull())
|
||||
{
|
||||
fileName = fileNameNode.as<std::string>();
|
||||
}
|
||||
return {
|
||||
.code = source,
|
||||
.fileName = std::move(fileName)
|
||||
};
|
||||
}
|
||||
|
||||
GLSLShader::GLSLShader(ObjectPtr<Instance> owner, GLSLShaderCreationArgs args) noexcept
|
||||
: super_t(std::move(owner)), mType(args.type), mSources(std::move(args.sources)), mDefines(std::move(args.defines))
|
||||
{
|
||||
MIJIN_ASSERT(!mSources.empty(), "Cannot compile without sources.");
|
||||
compile();
|
||||
}
|
||||
|
||||
GLSLShader::~GLSLShader() noexcept = default;
|
||||
|
||||
std::unique_ptr<glslang::TShader> GLSLShader::releaseHandle()
|
||||
{
|
||||
if (mHandle == nullptr) {
|
||||
compile();
|
||||
}
|
||||
return std::exchange(mHandle, nullptr);
|
||||
}
|
||||
|
||||
ShaderMeta GLSLShader::getPartialMeta()
|
||||
{
|
||||
if (mHandle == nullptr) {
|
||||
compile();
|
||||
}
|
||||
return reflectShader(*mHandle);
|
||||
}
|
||||
|
||||
void GLSLShader::compile()
|
||||
{
|
||||
initGlslang();
|
||||
|
||||
const EShLanguage stage = typeToGlslang(mType);
|
||||
|
||||
std::unique_ptr<glslang::TShader> shader = std::make_unique<glslang::TShader>(stage); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
|
||||
std::vector<const char*> sources;
|
||||
std::vector<int> lengths;
|
||||
std::vector<const char*> names;
|
||||
sources.reserve(mSources.size() + 1);
|
||||
lengths.reserve(mSources.size() + 1);
|
||||
names.reserve(mSources.size() + 1);
|
||||
|
||||
std::string preamble = getOwner()->getInstanceExtension<GLSLCompilerSettings>().getCommonPreamble();
|
||||
for (const std::string& define : mDefines) {
|
||||
preamble.append(fmt::format("\n#define {}\n", define));
|
||||
}
|
||||
sources.push_back(preamble.c_str());
|
||||
lengths.push_back(static_cast<int>(preamble.size()));
|
||||
names.push_back("<preamble>");
|
||||
|
||||
for (const ShaderSource& source : mSources)
|
||||
{
|
||||
sources.push_back(source.code.c_str());
|
||||
lengths.push_back(static_cast<int>(source.code.size()));
|
||||
names.push_back(source.fileName.c_str());
|
||||
}
|
||||
shader->setStringsWithLengthsAndNames(sources.data(), lengths.data(), names.data(), static_cast<int>(sources.size()));
|
||||
shader->setDebugInfo(true);
|
||||
shader->setEnvInput(glslang::EShSourceGlsl, stage, glslang::EShClientVulkan, glslang::EShTargetVulkan_1_3);
|
||||
shader->setEnvClient(glslang::EShClientVulkan, glslang::EShTargetVulkan_1_3);
|
||||
shader->setEnvTarget(glslang::EShTargetLanguage::EShTargetSpv, glslang::EShTargetSpv_1_6);
|
||||
shader->setAutoMapLocations(true);
|
||||
shader->setAutoMapBindings(true);
|
||||
|
||||
const EShMessages PREPROCESS_MESSAGES = static_cast<EShMessages>(EShMsgDefault
|
||||
#if !defined(KAZAN_RELEASE)
|
||||
| EShMsgDebugInfo
|
||||
#endif
|
||||
);
|
||||
std::string completeCode;
|
||||
CustomFileIncluder includer(getOwner()->getPrimaryFSAdapter()); // TODO: this type seems to be doing stupid things, investigate
|
||||
const std::string sourceFileAbsStr = mSources[0].fileName; // just for you MSVC <3
|
||||
if (!sourceFileAbsStr.empty()) {
|
||||
includer.setWorkingDir(fs::path(sourceFileAbsStr).parent_path());
|
||||
}
|
||||
const bool couldPreprocess = shader->preprocess(
|
||||
/* builtInResources = */ GetDefaultResources(),
|
||||
/* defaultVersion = */ 450,
|
||||
/* defaultProfile = */ ECoreProfile,
|
||||
/* forceDefaultVersionAndProfile = */ false,
|
||||
/* forwardCompatible = */ false,
|
||||
/* message = */ PREPROCESS_MESSAGES,
|
||||
/* outputString = */ &completeCode,
|
||||
/* includer = */ includer
|
||||
);
|
||||
if (!couldPreprocess)
|
||||
{
|
||||
logMsg("GLSL preprocessing failed:\ninfo log:\n{}\ndebug log:\n{}",
|
||||
shader->getInfoLog(), shader->getInfoDebugLog()
|
||||
);
|
||||
logAndDie("Error preprocessing shader.");
|
||||
}
|
||||
|
||||
#if 0
|
||||
ShaderPreprocessResult preprocessResult = preprocessShader(completeCode);
|
||||
|
||||
for (std::string& module : preprocessResult.importedModules) {
|
||||
importedModules.insert(std::move(module));
|
||||
}
|
||||
|
||||
for (std::string& option : preprocessResult.supportedOptions) {
|
||||
supportedOptions.insert(std::move(option));
|
||||
}
|
||||
#endif
|
||||
|
||||
const char* newSource = completeCode.c_str();
|
||||
#if defined(KAZAN_RELEASE)
|
||||
shader->setStrings(&newSource, 1); // replace source with the preprocessed one
|
||||
#else
|
||||
const int newSourceLen = static_cast<int>(std::strlen(newSource));
|
||||
const char* newSourceName = sourceFileAbsStr.c_str();
|
||||
shader->setStringsWithLengthsAndNames(&newSource, &newSourceLen, &newSourceName, 1);
|
||||
#endif
|
||||
const EShMessages PARSE_MESSAGES = static_cast<EShMessages>(EShMsgDefault
|
||||
#if !defined(KAZAN_RELEASE)
|
||||
| EShMsgDebugInfo
|
||||
#endif
|
||||
);
|
||||
const bool couldParse = shader->parse(
|
||||
/* builtinResources = */ GetDefaultResources(),
|
||||
/* defaultVersion = */ 450,
|
||||
/* forwardCompatible = */ false,
|
||||
/* messages = */ PARSE_MESSAGES
|
||||
);
|
||||
if (!couldParse)
|
||||
{
|
||||
logMsg("GLSL parsing failed:\ninfo log:\n{}\ndebug log:\n{}",
|
||||
shader->getInfoLog(), shader->getInfoDebugLog()
|
||||
);
|
||||
logAndDie("Error parsing shader.");
|
||||
}
|
||||
|
||||
mHandle = std::move(shader);
|
||||
}
|
||||
|
||||
GLSLShaderProgram::GLSLShaderProgram(ObjectPtr<Device> owner, GLSLShaderProgramCreationArgs args)
|
||||
: super_t(std::move(owner)), mLinkFlags(args.linkFlags)
|
||||
{
|
||||
MIJIN_ASSERT_FATAL(!args.shaders.empty(), "At least one shader per program is required!");
|
||||
|
||||
mHandle = std::make_unique<glslang::TProgram>();
|
||||
for (const ObjectPtr<GLSLShader>& shader : args.shaders)
|
||||
{
|
||||
mShaderHandles.push_back(shader->releaseHandle());
|
||||
mHandle->addShader(mShaderHandles.back().get());
|
||||
}
|
||||
|
||||
const EShMessages linkMessages = static_cast<EShMessages>(EShMsgSpvRules | EShMsgVulkanRules
|
||||
| (args.linkFlags.withDebugInfo ? EShMsgDebugInfo : EShMessages(0)));
|
||||
|
||||
if (!mHandle->link(linkMessages))
|
||||
{
|
||||
logAndDie("GLSL linking failed!\ninfo log:\n{}\ndebug log:\n{}",
|
||||
mHandle->getInfoLog(), mHandle->getInfoDebugLog()
|
||||
);
|
||||
}
|
||||
|
||||
glslang::TIntermediate* referenceIntermediate = mHandle->getIntermediate(typeToGlslang(args.shaders[0]->getType()));
|
||||
SemanticIoResolver ioResolver(*referenceIntermediate, args.semanticMappings);
|
||||
if (!mHandle->mapIO(&ioResolver))
|
||||
{
|
||||
logAndDie("GLSL io mapping failed!\ninfo log:\n{}\ndebug log:\n{}",
|
||||
mHandle->getInfoLog(), mHandle->getInfoDebugLog()
|
||||
);
|
||||
}
|
||||
|
||||
mMeta = reflectProgram(*mHandle);
|
||||
}
|
||||
|
||||
std::vector<std::uint32_t> GLSLShaderProgram::generateSpirv(vk::ShaderStageFlagBits stage) const
|
||||
{
|
||||
const EShLanguage glslLang = typeToGlslang(stage);
|
||||
|
||||
glslang::SpvOptions options = {};
|
||||
if (mLinkFlags.withDebugInfo)
|
||||
{
|
||||
options.generateDebugInfo = true;
|
||||
options.stripDebugInfo = false;
|
||||
options.disableOptimizer = true;
|
||||
options.emitNonSemanticShaderDebugInfo = true;
|
||||
options.emitNonSemanticShaderDebugSource = false; // TODO: this should be true, but makes GLSLang crash
|
||||
}
|
||||
else
|
||||
{
|
||||
options.generateDebugInfo = false;
|
||||
options.stripDebugInfo = true;
|
||||
options.disableOptimizer = false;
|
||||
options.emitNonSemanticShaderDebugInfo = true; // TODO: this should be false, but that also crashes GLSLang ...
|
||||
options.emitNonSemanticShaderDebugSource = false;
|
||||
}
|
||||
options.optimizeSize = false;
|
||||
options.disassemble = false;
|
||||
options.validate = true;
|
||||
|
||||
spv::SpvBuildLogger logger;
|
||||
const glslang::TIntermediate* intermediate = mHandle->getIntermediate(glslLang);
|
||||
if (intermediate == nullptr)
|
||||
{
|
||||
throw std::runtime_error("Attempting to generate SpirV from invalid shader stage.");
|
||||
}
|
||||
std::vector<std::uint32_t> spirv;
|
||||
glslang::GlslangToSpv(*intermediate, spirv, &logger, &options);
|
||||
|
||||
const std::string messages = logger.getAllMessages();
|
||||
if (!messages.empty())
|
||||
{
|
||||
logMsg("SpirV messages: {}", messages);
|
||||
}
|
||||
|
||||
return spirv;
|
||||
}
|
||||
|
||||
std::vector<PipelineStage> GLSLShaderProgram::generatePipelineStages() const
|
||||
{
|
||||
std::vector<PipelineStage> stages;
|
||||
for (const vk::ShaderStageFlagBits stage : mMeta.stages)
|
||||
{
|
||||
const std::vector<std::uint32_t> spirv = generateSpirv(stage);
|
||||
stages.push_back({
|
||||
.shader = getOwner()->createChild<ShaderModule>(ShaderModuleCreationArgs{.code = spirv}),
|
||||
.stage = stage
|
||||
});
|
||||
}
|
||||
return stages;
|
||||
}
|
||||
|
||||
GraphicsPipelineCreationArgs GLSLShaderProgram::prepareGraphicsPipeline(PrepareGraphicsPipelineArgs& args) const
|
||||
{
|
||||
args.pipelineLayoutMeta = mMeta.generatePipelineLayout(args.layoutArgs);
|
||||
args.layouts = args.pipelineLayoutMeta.createPipelineLayout(*getOwner());
|
||||
return {
|
||||
.stages = generatePipelineStages(),
|
||||
.vertexInput = mMeta.generateVertexInputFromLayout(args.vertexLayout),
|
||||
.layout = args.layouts.pipelineLayout
|
||||
};
|
||||
}
|
||||
|
||||
ComputePipelineCreationArgs GLSLShaderProgram::prepareComputePipeline(PrepareComputePipelineArgs& args) const
|
||||
{
|
||||
args.pipelineLayoutMeta = mMeta.generatePipelineLayout(args.layoutArgs);
|
||||
args.layouts = args.pipelineLayoutMeta.createPipelineLayout(*getOwner());
|
||||
return {
|
||||
.stage = generatePipelineStages()[0],
|
||||
.layout = args.layouts.pipelineLayout
|
||||
};
|
||||
}
|
||||
|
||||
// GraphicsPipelineCreationArgs prepareGLSLGraphicsPipeline(const PrepareGLSLGraphicsPipelineArgs& args)
|
||||
// {
|
||||
// return {
|
||||
// .stages =
|
||||
// {
|
||||
// PipelineStage{.shader = vertexShaderModule, .stage = vk::ShaderStageFlagBits::eVertex},
|
||||
// PipelineStage{.shader = fragmentShaderModule, .stage = vk::ShaderStageFlagBits::eFragment}
|
||||
// },
|
||||
// .vertexInput = std::move(vertexInput),
|
||||
// .inputAssembly =
|
||||
// {
|
||||
// .topology = vk::PrimitiveTopology::eTriangleStrip
|
||||
// },
|
||||
// .colorBlend =
|
||||
// {
|
||||
// .attachements = {DEFAULT_BLEND_ATTACHMENT}
|
||||
// },
|
||||
// .renderingInfo = GraphicsPipelineRenderingInfo{
|
||||
// .colorAttachmentFormats = {mRenderTarget->getFormat()}
|
||||
// },
|
||||
// .layout = mPipelineLayout
|
||||
// };
|
||||
// }
|
||||
} // namespace iwa
|
||||
33
source/util/growing_descriptor_pool.cpp
Normal file
33
source/util/growing_descriptor_pool.cpp
Normal file
@@ -0,0 +1,33 @@
|
||||
|
||||
#include "iwa/util/growing_descriptor_pool.hpp"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "iwa/device.hpp"
|
||||
|
||||
namespace iwa
|
||||
{
|
||||
GrowingDescriptorPool::GrowingDescriptorPool(ObjectPtr<Device> owner, GrowingDescriptorPoolCreationArgs args)
|
||||
: super_t(std::move(owner)), mCreationArgs(std::move(args))
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
ObjectPtr<DescriptorSet> GrowingDescriptorPool::allocateDescriptorSet(const DescriptorSetAllocateArgs& args)
|
||||
{
|
||||
for (const ObjectPtr<DescriptorPool>& pool : mPools)
|
||||
{
|
||||
try
|
||||
{
|
||||
return pool->allocateDescriptorSet(args);
|
||||
}
|
||||
catch (vk::FragmentedPoolError&) {}
|
||||
catch (vk::OutOfPoolMemoryError&) {}
|
||||
// any other error shall be forwarded
|
||||
}
|
||||
|
||||
// couldn't allocate in any of the existing pools, create a new one
|
||||
mPools.push_back(getOwner()->createChild<DescriptorPool>(mCreationArgs));
|
||||
return mPools.back()->allocateDescriptorSet(args); // raise any error that may occur
|
||||
}
|
||||
} // namespace iwa
|
||||
147
source/util/image_reference.cpp
Normal file
147
source/util/image_reference.cpp
Normal file
@@ -0,0 +1,147 @@
|
||||
|
||||
#include "iwa/util/image_reference.hpp"
|
||||
|
||||
#include <mijin/util/iterators.hpp>
|
||||
#include "iwa/command.hpp"
|
||||
#include "iwa/device.hpp"
|
||||
|
||||
namespace iwa
|
||||
{
|
||||
ImageReference::ImageReference(ObjectPtr<Device> owner) : super_t(std::move(owner))
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void ImageReference::finalize(ImageReferenceFinalizeArgs& /* args */) {}
|
||||
|
||||
mijin::Task<> ImageReference::c_present()
|
||||
{
|
||||
co_return;
|
||||
}
|
||||
|
||||
SwapchainImageReference::SwapchainImageReference(ObjectPtr<Device> owner, SwapchainImageReferenceCreationArgs args)
|
||||
: super_t(std::move(owner)), mSwapchain(std::move(args.swapchain))
|
||||
{
|
||||
mPresentReadySemaphores.resize(mSwapchain->getNumParallelFrames());
|
||||
for (ObjectPtr<Semaphore>& semaphore : mPresentReadySemaphores)
|
||||
{
|
||||
semaphore = getOwner()->createChild<Semaphore>();
|
||||
}
|
||||
createImageViews();
|
||||
mSwapchain->recreated.connect([this]()
|
||||
{
|
||||
createImageViews();
|
||||
});
|
||||
}
|
||||
|
||||
vk::Format SwapchainImageReference::getFormat()
|
||||
{
|
||||
return mSwapchain->getFormat();
|
||||
}
|
||||
|
||||
vk::Extent2D SwapchainImageReference::getExtent()
|
||||
{
|
||||
return mSwapchain->getExtent();
|
||||
}
|
||||
|
||||
ImageReferenceFrame SwapchainImageReference::getCurrentFrame()
|
||||
{
|
||||
return ImageReferenceFrame{
|
||||
.image = mSwapchain->getCurrentImage().getRaw(),
|
||||
.imageView = mImageViews[mSwapchain->getCurrentImageIdx()].getRaw()
|
||||
};
|
||||
}
|
||||
|
||||
void SwapchainImageReference::finalize(ImageReferenceFinalizeArgs& args)
|
||||
{
|
||||
args.waitSemaphores.push_back(*mSwapchain->getCurrentAvailableSemaphore());
|
||||
args.signalSemaphores.push_back(*mPresentReadySemaphores[mSwapchain->getCurrentFrameIdx()]);
|
||||
|
||||
mSwapchain->getCurrentImage()->applyTransition(args.cmdBuffer, ImageTransition{
|
||||
.stages = vk::PipelineStageFlagBits::eBottomOfPipe,
|
||||
.layout = vk::ImageLayout::ePresentSrcKHR,
|
||||
.access = {}
|
||||
});
|
||||
}
|
||||
|
||||
mijin::Task<> SwapchainImageReference::c_present()
|
||||
{
|
||||
// and present
|
||||
co_await mSwapchain->c_present({
|
||||
.queue = getOwner()->getGraphicsQueue(),
|
||||
.waitSemaphores = {mPresentReadySemaphores[mSwapchain->getCurrentFrameIdx()]->getVkHandle()}
|
||||
});
|
||||
}
|
||||
|
||||
void SwapchainImageReference::createImageViews()
|
||||
{
|
||||
mImageViews.resize(mSwapchain->getImages().size());
|
||||
for (auto [image, imageView] : mijin::zip(mSwapchain->getImages(), mImageViews))
|
||||
{
|
||||
imageView = image->createImageView();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
DirectImageReference::DirectImageReference(ObjectPtr<Device> owner, DirectImageReferenceCreationArgs args)
|
||||
: super_t(std::move(owner)), mImage(std::move(args.image)), mImageView(std::move(args.imageView))
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
vk::Format DirectImageReference::getFormat()
|
||||
{
|
||||
return mImage->getFormat();
|
||||
}
|
||||
|
||||
vk::Extent2D DirectImageReference::getExtent()
|
||||
{
|
||||
return {
|
||||
.width = mImage->getSize().width,
|
||||
.height = mImage->getSize().height
|
||||
};
|
||||
}
|
||||
|
||||
ImageReferenceFrame DirectImageReference::getCurrentFrame()
|
||||
{
|
||||
return ImageReferenceFrame{
|
||||
.image = mImage.getRaw(),
|
||||
.imageView = mImageView.getRaw()
|
||||
};
|
||||
}
|
||||
|
||||
AutoResizeImageReference::AutoResizeImageReference(ObjectPtr<Device> owner, AutoResizeImageReferenceCreationArgs args)
|
||||
: super_t(std::move(owner), DirectImageReferenceCreationArgs{}), mReferenceImageRef(std::move(args.referenceImageRef)),
|
||||
mImageCreationArgs(std::move(args.imageCreationArgs)), mImageViewCreationArgs(args.imageViewCreationArgs)
|
||||
{
|
||||
createImage();
|
||||
}
|
||||
|
||||
vk::Extent2D AutoResizeImageReference::getExtent()
|
||||
{
|
||||
return mReferenceImageRef->getExtent();
|
||||
}
|
||||
|
||||
ImageReferenceFrame AutoResizeImageReference::getCurrentFrame()
|
||||
{
|
||||
const vk::Extent2D extent = mReferenceImageRef->getExtent();
|
||||
if (extent.width != mImage->getSize().width || extent.height != mImage->getSize().height) {
|
||||
createImage();
|
||||
}
|
||||
return ImageReferenceFrame{
|
||||
.image = mImage.getRaw(),
|
||||
.imageView = mImageView.getRaw()
|
||||
};
|
||||
}
|
||||
|
||||
void AutoResizeImageReference::createImage()
|
||||
{
|
||||
const vk::Extent2D extent = mReferenceImageRef->getExtent();
|
||||
mImageCreationArgs.extent.width = extent.width;
|
||||
mImageCreationArgs.extent.height = extent.height;
|
||||
mImageCreationArgs.extent.depth = 1;
|
||||
mImage = getOwner()->createChild<Image>(mImageCreationArgs);
|
||||
mImage->allocateMemory();
|
||||
mImageView = mImage->createImageView(mImageViewCreationArgs);
|
||||
}
|
||||
} // namespace iwa
|
||||
587
source/util/reflect_glsl.cpp
Normal file
587
source/util/reflect_glsl.cpp
Normal file
@@ -0,0 +1,587 @@
|
||||
|
||||
#include "iwa/util/reflect_glsl.hpp"
|
||||
|
||||
#include <glslang/Include/InfoSink.h>
|
||||
#include <glslang/Public/ShaderLang.h>
|
||||
#include <glslang/MachineIndependent/localintermediate.h>
|
||||
#include <glslang/Public/ResourceLimits.h>
|
||||
|
||||
namespace iwa
|
||||
{
|
||||
namespace
|
||||
{
|
||||
class MetaCollectingTraverser : public glslang::TIntermTraverser
|
||||
{
|
||||
private:
|
||||
ShaderMeta& meta;
|
||||
vk::ShaderStageFlagBits shaderType;
|
||||
public:
|
||||
inline MetaCollectingTraverser(ShaderMeta& meta_, vk::ShaderStageFlagBits shaderType_) : meta(meta_), shaderType(shaderType_)
|
||||
{}
|
||||
|
||||
bool visitBinary(glslang::TVisit, glslang::TIntermBinary* node) override;
|
||||
bool visitUnary(glslang::TVisit, glslang::TIntermUnary* node) override;
|
||||
bool visitAggregate(glslang::TVisit, glslang::TIntermAggregate* node) override;
|
||||
bool visitSelection(glslang::TVisit, glslang::TIntermSelection* node) override;
|
||||
void visitConstantUnion(glslang::TIntermConstantUnion* node) override;
|
||||
void visitSymbol(glslang::TIntermSymbol* node) override;
|
||||
bool visitLoop(glslang::TVisit, glslang::TIntermLoop* node) override;
|
||||
bool visitBranch(glslang::TVisit, glslang::TIntermBranch* node) override;
|
||||
bool visitSwitch(glslang::TVisit, glslang::TIntermSwitch* node) override;
|
||||
};
|
||||
|
||||
vk::Format convertGlslangBaseType(const glslang::TType& type)
|
||||
{
|
||||
switch (type.getBasicType())
|
||||
{
|
||||
case glslang::EbtInt:
|
||||
return vk::Format::eR32Sint;
|
||||
case glslang::EbtUint:
|
||||
return vk::Format::eR32Uint;
|
||||
case glslang::EbtFloat:
|
||||
return vk::Format::eR32Sfloat;
|
||||
case glslang::EbtDouble:
|
||||
return vk::Format::eR64Sfloat;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
logAndDie("Don't know how to convert Glslang basic type :*(");
|
||||
}
|
||||
|
||||
vk::Format convertGlslangLayoutFormat(glslang::TLayoutFormat layoutFormat)
|
||||
{
|
||||
switch (layoutFormat)
|
||||
{
|
||||
case glslang::TLayoutFormat::ElfNone:
|
||||
return vk::Format::eUndefined;
|
||||
|
||||
// Float image
|
||||
case glslang::TLayoutFormat::ElfRgba32f:
|
||||
return vk::Format::eR32G32B32A32Sfloat;
|
||||
case glslang::TLayoutFormat::ElfRgba16f:
|
||||
return vk::Format::eR16G16B16A16Sfloat;
|
||||
case glslang::TLayoutFormat::ElfR32f:
|
||||
return vk::Format::eR32Sfloat;
|
||||
case glslang::TLayoutFormat::ElfRgba8:
|
||||
return vk::Format::eR8G8B8A8Unorm;
|
||||
case glslang::TLayoutFormat::ElfRgba8Snorm:
|
||||
return vk::Format::eR8G8B8A8Snorm;
|
||||
|
||||
|
||||
case glslang::TLayoutFormat::ElfRg32f:
|
||||
return vk::Format::eR32G32Sfloat;
|
||||
case glslang::TLayoutFormat::ElfRg16f:
|
||||
return vk::Format::eR16G16Sfloat;
|
||||
case glslang::TLayoutFormat::ElfR11fG11fB10f:
|
||||
return vk::Format::eB10G11R11UfloatPack32; // TODO: ?
|
||||
case glslang::TLayoutFormat::ElfR16f:
|
||||
return vk::Format::eR16Sfloat;
|
||||
case glslang::TLayoutFormat::ElfRgba16:
|
||||
return vk::Format::eR16G16B16A16Unorm;
|
||||
case glslang::TLayoutFormat::ElfRgb10A2:
|
||||
return vk::Format::eA2R10G10B10SnormPack32; // TODO: ?
|
||||
case glslang::TLayoutFormat::ElfRg16:
|
||||
return vk::Format::eR16G16Unorm;
|
||||
case glslang::TLayoutFormat::ElfRg8:
|
||||
return vk::Format::eR8G8Unorm;
|
||||
case glslang::TLayoutFormat::ElfR16:
|
||||
return vk::Format::eR16Unorm;
|
||||
case glslang::TLayoutFormat::ElfR8:
|
||||
return vk::Format::eR8Unorm;
|
||||
case glslang::TLayoutFormat::ElfRgba16Snorm:
|
||||
return vk::Format::eR16G16B16A16Snorm;
|
||||
case glslang::TLayoutFormat::ElfRg16Snorm:
|
||||
return vk::Format::eR16G16Unorm;
|
||||
case glslang::TLayoutFormat::ElfRg8Snorm:
|
||||
return vk::Format::eR8G8Snorm;
|
||||
case glslang::TLayoutFormat::ElfR16Snorm:
|
||||
return vk::Format::eR16G16Snorm;
|
||||
case glslang::TLayoutFormat::ElfR8Snorm:
|
||||
return vk::Format::eR8Snorm;
|
||||
|
||||
// Int image
|
||||
case glslang::TLayoutFormat::ElfRgba32i:
|
||||
return vk::Format::eR32G32B32A32Sint;
|
||||
case glslang::TLayoutFormat::ElfRgba16i:
|
||||
return vk::Format::eR16G16B16A16Sint;
|
||||
case glslang::TLayoutFormat::ElfRgba8i:
|
||||
return vk::Format::eR8G8B8A8Sint;
|
||||
case glslang::TLayoutFormat::ElfR32i:
|
||||
return vk::Format::eR32Sint;
|
||||
|
||||
case glslang::TLayoutFormat::ElfRg32i:
|
||||
return vk::Format::eR32G32Sint;
|
||||
case glslang::TLayoutFormat::ElfRg16i:
|
||||
return vk::Format::eR16G16Sint;
|
||||
case glslang::TLayoutFormat::ElfRg8i:
|
||||
return vk::Format::eR8G8Sint;
|
||||
case glslang::TLayoutFormat::ElfR16i:
|
||||
return vk::Format::eR16Sint;
|
||||
case glslang::TLayoutFormat::ElfR8i:
|
||||
return vk::Format::eR8Sint;
|
||||
case glslang::TLayoutFormat::ElfR64i:
|
||||
return vk::Format::eR64Sint;
|
||||
|
||||
// Uint image
|
||||
case glslang::TLayoutFormat::ElfRgba32ui:
|
||||
return vk::Format::eR32G32B32A32Uint;
|
||||
case glslang::TLayoutFormat::ElfRgba16ui:
|
||||
return vk::Format::eR16G16B16A16Uint;
|
||||
case glslang::TLayoutFormat::ElfRgba8ui:
|
||||
return vk::Format::eR8G8B8A8Uint;
|
||||
case glslang::TLayoutFormat::ElfR32ui:
|
||||
return vk::Format::eR32Uint;
|
||||
|
||||
|
||||
case glslang::TLayoutFormat::ElfRg32ui:
|
||||
return vk::Format::eR32G32Uint;
|
||||
case glslang::TLayoutFormat::ElfRg16ui:
|
||||
return vk::Format::eR16G16Uint;
|
||||
case glslang::TLayoutFormat::ElfRgb10a2ui:
|
||||
return vk::Format::eA2R10G10B10UintPack32;
|
||||
case glslang::TLayoutFormat::ElfRg8ui:
|
||||
return vk::Format::eR8G8Uint;
|
||||
case glslang::TLayoutFormat::ElfR16ui:
|
||||
return vk::Format::eR16Uint;
|
||||
case glslang::TLayoutFormat::ElfR8ui:
|
||||
return vk::Format::eR8Uint;
|
||||
case glslang::TLayoutFormat::ElfR64ui:
|
||||
return vk::Format::eR64Uint;
|
||||
|
||||
// other/unknown
|
||||
case glslang::TLayoutFormat::ElfSize1x8:
|
||||
case glslang::TLayoutFormat::ElfSize1x16:
|
||||
case glslang::TLayoutFormat::ElfSize1x32:
|
||||
case glslang::TLayoutFormat::ElfSize2x32:
|
||||
case glslang::TLayoutFormat::ElfSize4x32:
|
||||
case glslang::TLayoutFormat::ElfEsFloatGuard:
|
||||
case glslang::TLayoutFormat::ElfFloatGuard:
|
||||
case glslang::TLayoutFormat::ElfEsIntGuard:
|
||||
case glslang::TLayoutFormat::ElfIntGuard:
|
||||
case glslang::TLayoutFormat::ElfEsUintGuard:
|
||||
case glslang::TLayoutFormat::ElfExtSizeGuard:
|
||||
case glslang::TLayoutFormat::ElfCount:
|
||||
break;
|
||||
}
|
||||
|
||||
logAndDie("Unexpected format in convertGlslangLayoutFormat()."); // : {}", layoutFormat);
|
||||
}
|
||||
|
||||
vk::Format convertGlslangVectorType(glslang::TBasicType basicType, int vectorSize)
|
||||
{
|
||||
switch (basicType)
|
||||
{
|
||||
case glslang::EbtFloat:
|
||||
switch (vectorSize)
|
||||
{
|
||||
case 2:
|
||||
return vk::Format::eR32G32Sfloat;
|
||||
case 3:
|
||||
return vk::Format::eR32G32B32Sfloat;
|
||||
case 4:
|
||||
return vk::Format::eR32G32B32A32Sfloat;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case glslang::EbtDouble:
|
||||
switch (vectorSize)
|
||||
{
|
||||
case 2:
|
||||
return vk::Format::eR64G64Sfloat;
|
||||
case 3:
|
||||
return vk::Format::eR64G64B64Sfloat;
|
||||
case 4:
|
||||
return vk::Format::eR64G64B64A64Sfloat;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case glslang::EbtInt:
|
||||
switch (vectorSize)
|
||||
{
|
||||
case 2:
|
||||
return vk::Format::eR32G32Sint;
|
||||
case 3:
|
||||
return vk::Format::eR32G32B32Sint;
|
||||
case 4:
|
||||
return vk::Format::eR32G32B32A32Sint;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case glslang::EbtUint:
|
||||
switch (vectorSize)
|
||||
{
|
||||
case 2:
|
||||
return vk::Format::eR32G32Uint;
|
||||
case 3:
|
||||
return vk::Format::eR32G32B32Uint;
|
||||
case 4:
|
||||
return vk::Format::eR32G32B32A32Uint;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case glslang::EbtBool: // NOLINT(bugprone-branch-clone) TODO: ???
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
logAndDie("Don't know how to convert Glslang vector type :(");
|
||||
}
|
||||
|
||||
vk::Format convertGlslangVectorType(const glslang::TType& type)
|
||||
{
|
||||
assert(type.isVector());
|
||||
return convertGlslangVectorType(type.getBasicType(), type.getVectorSize());
|
||||
}
|
||||
|
||||
ShaderVariableMatrixType convertGlslangMatrixType(const glslang::TType& type)
|
||||
{
|
||||
assert(type.isMatrix());
|
||||
assert(type.getMatrixCols() == type.getMatrixRows()); // only supported types yet...
|
||||
switch (type.getMatrixCols())
|
||||
{
|
||||
case 2:
|
||||
return ShaderVariableMatrixType::MAT2;
|
||||
case 3:
|
||||
return ShaderVariableMatrixType::MAT3;
|
||||
case 4:
|
||||
return ShaderVariableMatrixType::MAT4;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
logAndDie("Don't know how to convert Glslang matrix type -.-");
|
||||
}
|
||||
|
||||
ImageDim convertGlslangSamplerDim(glslang::TSamplerDim dim)
|
||||
{
|
||||
switch (dim)
|
||||
{
|
||||
case glslang::TSamplerDim::Esd1D:
|
||||
return ImageDim::ONE;
|
||||
case glslang::TSamplerDim::Esd2D:
|
||||
return ImageDim::TWO;
|
||||
case glslang::TSamplerDim::Esd3D:
|
||||
return ImageDim::THREE;
|
||||
case glslang::TSamplerDim::EsdCube:
|
||||
return ImageDim::CUBE;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
logAndDie("Don't know how to convert Glslang sampler dimensions ...");
|
||||
}
|
||||
|
||||
ShaderVariableType convertGlslangType(const glslang::TType& type)
|
||||
{
|
||||
ShaderVariableType result;
|
||||
if (type.isVector())
|
||||
{
|
||||
result.baseType = ShaderVariableBaseType::SIMPLE;
|
||||
result.simple.format = convertGlslangVectorType(type);
|
||||
} else if (type.isMatrix())
|
||||
{
|
||||
result.baseType = ShaderVariableBaseType::MATRIX;
|
||||
result.matrixType = convertGlslangMatrixType(type);
|
||||
} else if (type.isStruct())
|
||||
{
|
||||
const std::size_t numMembers = type.getStruct()->size();
|
||||
result.baseType = ShaderVariableBaseType::STRUCT;
|
||||
result.struct_.members.reserve(numMembers);
|
||||
|
||||
std::size_t currentOffset = 0;
|
||||
for (const glslang::TTypeLoc& typeLoc: *type.getStruct())
|
||||
{
|
||||
ShaderVariableStructMember& member = result.struct_.members.emplace_back();
|
||||
member.name = typeLoc.type->getFieldName();
|
||||
member.type = convertGlslangType(*typeLoc.type);
|
||||
member.offset = currentOffset;
|
||||
if (typeLoc.type->getQualifier().hasSemantic())
|
||||
{
|
||||
member.semantic = typeLoc.type->getQualifier().layoutSemantic;
|
||||
}
|
||||
if (typeLoc.type->getQualifier().hasSemanticIndex())
|
||||
{
|
||||
member.semanticIdx = typeLoc.type->getQualifier().layoutSemanticIndex;
|
||||
}
|
||||
currentOffset = member.offset + calcShaderTypeSize(member.type); // TODO: padding
|
||||
}
|
||||
|
||||
} else if (type.getBasicType() == glslang::EbtSampler)
|
||||
{
|
||||
const glslang::TSampler& sampler = type.getSampler();
|
||||
result.baseType = ShaderVariableBaseType::IMAGE;
|
||||
result.image.dimensions = convertGlslangSamplerDim(sampler.dim);
|
||||
result.image.format = convertGlslangLayoutFormat(type.getQualifier().layoutFormat);
|
||||
} else
|
||||
{
|
||||
result.baseType = ShaderVariableBaseType::SIMPLE;
|
||||
result.simple.format = convertGlslangBaseType(type);
|
||||
}
|
||||
|
||||
if (type.isArray())
|
||||
{
|
||||
if (type.isArrayVariablyIndexed())
|
||||
{
|
||||
result.arraySize = 0;
|
||||
result.dynamicArraySize = true;
|
||||
} else
|
||||
{
|
||||
assert(type.getArraySizes()->getNumDims() == 1); // don't support multi dimensional arrays yet
|
||||
result.arraySize = type.getOuterArraySize();
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
vk::DescriptorType getGlslangDescriptorType(const glslang::TType& type)
|
||||
{
|
||||
if (type.getBasicType() == glslang::EbtSampler)
|
||||
{
|
||||
if (type.getSampler().combined)
|
||||
{
|
||||
return vk::DescriptorType::eCombinedImageSampler;
|
||||
}
|
||||
if (type.getSampler().isImage())
|
||||
{
|
||||
return vk::DescriptorType::eStorageImage;
|
||||
}
|
||||
} else if (type.isStruct())
|
||||
{
|
||||
if (type.getQualifier().isUniform())
|
||||
{
|
||||
return vk::DescriptorType::eUniformBuffer;
|
||||
}
|
||||
return vk::DescriptorType::eStorageBuffer;
|
||||
}
|
||||
logAndDie("No idea what to do with this type :/");
|
||||
}
|
||||
|
||||
bool MetaCollectingTraverser::visitBinary(glslang::TVisit, glslang::TIntermBinary* node)
|
||||
{
|
||||
(void) node;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MetaCollectingTraverser::visitUnary(glslang::TVisit, glslang::TIntermUnary* node)
|
||||
{
|
||||
(void) node;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MetaCollectingTraverser::visitAggregate(glslang::TVisit, glslang::TIntermAggregate* node)
|
||||
{
|
||||
switch (node->getOp())
|
||||
{
|
||||
case glslang::EOpSequence:
|
||||
return true;
|
||||
case glslang::EOpFunction:
|
||||
break;
|
||||
case glslang::EOpLinkerObjects:
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MetaCollectingTraverser::visitSelection(glslang::TVisit, glslang::TIntermSelection* node)
|
||||
{
|
||||
(void) node;
|
||||
return false;
|
||||
}
|
||||
|
||||
void MetaCollectingTraverser::visitConstantUnion(glslang::TIntermConstantUnion* node)
|
||||
{
|
||||
(void) node;
|
||||
}
|
||||
|
||||
void MetaCollectingTraverser::visitSymbol(glslang::TIntermSymbol* node)
|
||||
{
|
||||
const bool isLinkerObject = getParentNode()
|
||||
&& getParentNode()->getAsAggregate()
|
||||
&& getParentNode()->getAsAggregate()->getOp() == glslang::EOpLinkerObjects;
|
||||
if (isLinkerObject)
|
||||
{
|
||||
if (node->getQualifier().builtIn)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (node->getQualifier().isUniformOrBuffer())
|
||||
{
|
||||
if (node->getQualifier().isPushConstant())
|
||||
{
|
||||
ShaderPushConstantBlock pushConstantBlock;
|
||||
pushConstantBlock.type = convertGlslangType(node->getType());
|
||||
assert(pushConstantBlock.type.baseType == ShaderVariableBaseType::STRUCT);
|
||||
meta.extendPushConstant(pushConstantBlock, ShaderTypeBits::make(shaderType));
|
||||
return;
|
||||
}
|
||||
const unsigned setIdx = node->getQualifier().hasSet() ? node->getQualifier().layoutSet : UNSPECIFIED_INDEX;
|
||||
const unsigned binding = node->getQualifier().hasBinding() ? node->getQualifier().layoutBinding : UNSPECIFIED_INDEX;
|
||||
ShaderVariableSet& set = meta.getOrCreateInterfaceVariableSet(setIdx);
|
||||
assert(setIdx == UNSPECIFIED_INDEX || !set.getVariableAtBindingOpt(binding)); // multiple bindings at the same index?
|
||||
set.usedInStages.set(shaderType, true);
|
||||
|
||||
ShaderVariable& var = set.variables.emplace_back();
|
||||
var.binding = binding;
|
||||
var.name = node->getName();
|
||||
if (node->getQualifier().hasSemantic())
|
||||
{
|
||||
var.semantic = node->getQualifier().layoutSemantic;
|
||||
}
|
||||
if (node->getQualifier().hasSemanticIndex())
|
||||
{
|
||||
var.semanticIndex = node->getQualifier().layoutSemanticIndex;
|
||||
}
|
||||
|
||||
// uniform blocks are identified by the name of their type
|
||||
if (var.name.empty() || var.name.starts_with("anon@"))
|
||||
{
|
||||
const glslang::TString& typeName = node->getType().getTypeName();
|
||||
if (!typeName.empty())
|
||||
{
|
||||
var.name = typeName;
|
||||
}
|
||||
}
|
||||
var.descriptorType = getGlslangDescriptorType(node->getType());
|
||||
var.type = convertGlslangType(node->getType());
|
||||
}
|
||||
else if (node->getQualifier().storage == glslang::EvqVaryingIn)
|
||||
{
|
||||
ShaderAttribute attribute;
|
||||
attribute.stage = shaderType;
|
||||
attribute.type = convertGlslangType(node->getType());
|
||||
attribute.location = node->getQualifier().hasLocation() ? node->getQualifier().layoutLocation : UNSPECIFIED_INDEX;
|
||||
attribute.name = node->getName();
|
||||
if (node->getQualifier().hasSemantic())
|
||||
{
|
||||
attribute.semantic = node->getQualifier().layoutSemantic;
|
||||
}
|
||||
if (node->getQualifier().hasSemanticIndex())
|
||||
{
|
||||
attribute.semanticIndex = node->getQualifier().layoutSemanticIndex;
|
||||
}
|
||||
meta.addInputAttribute(std::move(attribute));
|
||||
}
|
||||
else if (node->getQualifier().storage == glslang::EvqVaryingOut)
|
||||
{
|
||||
ShaderAttribute attribute;
|
||||
attribute.stage = shaderType;
|
||||
attribute.type = convertGlslangType(node->getType());
|
||||
attribute.location = node->getQualifier().hasLocation() ? node->getQualifier().layoutLocation : UNSPECIFIED_INDEX;
|
||||
attribute.name = node->getName();
|
||||
if (node->getQualifier().hasSemantic())
|
||||
{
|
||||
attribute.semantic = node->getQualifier().layoutSemantic;
|
||||
}
|
||||
if (node->getQualifier().hasSemanticIndex())
|
||||
{
|
||||
attribute.semanticIndex = node->getQualifier().layoutSemanticIndex;
|
||||
}
|
||||
meta.addOutputAttribute(std::move(attribute));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool MetaCollectingTraverser::visitLoop(glslang::TVisit, glslang::TIntermLoop* node)
|
||||
{
|
||||
(void) node;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MetaCollectingTraverser::visitBranch(glslang::TVisit, glslang::TIntermBranch* node)
|
||||
{
|
||||
(void) node;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MetaCollectingTraverser::visitSwitch(glslang::TVisit, glslang::TIntermSwitch* node)
|
||||
{
|
||||
(void) node;
|
||||
return false;
|
||||
}
|
||||
|
||||
vk::ShaderStageFlagBits shaderStageFromGlslang(EShLanguage language)
|
||||
{
|
||||
switch (language)
|
||||
{
|
||||
case EShLangVertex:
|
||||
return vk::ShaderStageFlagBits::eVertex;
|
||||
case EShLangTessControl:
|
||||
return vk::ShaderStageFlagBits::eTessellationControl;
|
||||
case EShLangTessEvaluation:
|
||||
return vk::ShaderStageFlagBits::eTessellationEvaluation;
|
||||
case EShLangGeometry:
|
||||
return vk::ShaderStageFlagBits::eGeometry;
|
||||
case EShLangFragment:
|
||||
return vk::ShaderStageFlagBits::eFragment;
|
||||
case EShLangCompute:
|
||||
return vk::ShaderStageFlagBits::eCompute;
|
||||
case EShLangRayGen:
|
||||
return vk::ShaderStageFlagBits::eRaygenKHR;
|
||||
case EShLangIntersect:
|
||||
return vk::ShaderStageFlagBits::eIntersectionKHR;
|
||||
case EShLangAnyHit:
|
||||
return vk::ShaderStageFlagBits::eAnyHitKHR;
|
||||
case EShLangClosestHit:
|
||||
return vk::ShaderStageFlagBits::eClosestHitKHR;
|
||||
case EShLangMiss:
|
||||
return vk::ShaderStageFlagBits::eMissKHR;
|
||||
case EShLangCallable:
|
||||
return vk::ShaderStageFlagBits::eCallableKHR;
|
||||
case EShLangTask:
|
||||
return vk::ShaderStageFlagBits::eTaskEXT;
|
||||
case EShLangMesh:
|
||||
return vk::ShaderStageFlagBits::eMeshEXT;
|
||||
case EShLangCount:
|
||||
break; // fall through
|
||||
}
|
||||
logAndDie("Invalid value passed to shaderStageFromGlslang!");
|
||||
}
|
||||
}
|
||||
|
||||
ShaderMeta reflectShader(glslang::TShader& shader)
|
||||
{
|
||||
return reflectIntermediate(*shader.getIntermediate(), shaderStageFromGlslang(shader.getStage()));
|
||||
}
|
||||
|
||||
ShaderMeta reflectProgram(glslang::TProgram& program)
|
||||
{
|
||||
ShaderMeta result;
|
||||
for (int stage = 0; stage < EShLangCount; ++stage)
|
||||
{
|
||||
glslang::TIntermediate* intermediate = program.getIntermediate(static_cast<EShLanguage>(stage));
|
||||
if (intermediate == nullptr) {
|
||||
continue;
|
||||
}
|
||||
result.extend(reflectIntermediate(*intermediate, shaderStageFromGlslang(static_cast<EShLanguage>(stage))));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
ShaderMeta reflectIntermediate(glslang::TIntermediate& intermediate, vk::ShaderStageFlagBits stage)
|
||||
{
|
||||
ShaderMeta meta;
|
||||
MetaCollectingTraverser traverser(meta, stage);
|
||||
intermediate.getTreeRoot()->traverse(&traverser);
|
||||
|
||||
meta.stages.set(stage, true);
|
||||
|
||||
if (stage == vk::ShaderStageFlagBits::eCompute)
|
||||
{
|
||||
meta.localSizeX = static_cast<unsigned>(intermediate.getLocalSize(0));
|
||||
meta.localSizeY = static_cast<unsigned>(intermediate.getLocalSize(1));
|
||||
meta.localSizeZ = static_cast<unsigned>(intermediate.getLocalSize(2));
|
||||
}
|
||||
|
||||
return meta;
|
||||
}
|
||||
} // namespace iwa
|
||||
128
source/util/render_loop.cpp
Normal file
128
source/util/render_loop.cpp
Normal file
@@ -0,0 +1,128 @@
|
||||
|
||||
#include "iwa/util/render_loop.hpp"
|
||||
|
||||
#include <mijin/async/task_mutex.hpp>
|
||||
#include "iwa/device.hpp"
|
||||
#include "iwa/instance.hpp"
|
||||
|
||||
namespace iwa
|
||||
{
|
||||
namespace
|
||||
{
|
||||
// BIG BIG TODO: This is a dumb workaround for sharing images (e.g. the UI image) between multiple renderers.
|
||||
// The reason is that the layout change mechanism doesn't work if multiple command buffers (that are executed
|
||||
// sequentially) are recorded in parallel.
|
||||
// A possible fix could be to move the state tracking mechanism to the renderer and generate the barriers
|
||||
// before submitting.
|
||||
mijin::TaskMutex gRenderMutex;
|
||||
}
|
||||
|
||||
RenderLoop::RenderLoop(ObjectPtr<Device> owner, RenderLoopCreationArgs args)
|
||||
: super_t(std::move(owner)), mAdvanceDeleteQueue(args.flags.advanceDeleteQueue)
|
||||
{
|
||||
mAlternating.resize(args.parallelFrames);
|
||||
|
||||
ObjectPtr<CommandPool> commandPool = std::move(args.commandPool);
|
||||
if (!commandPool)
|
||||
{
|
||||
commandPool = getOwner()->createChild<CommandPool>(CommandPoolCreationArgs{
|
||||
.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer,
|
||||
.queueFamilyIndex = getOwner()->getDeviceInfo().graphicsQueueFamily
|
||||
});
|
||||
}
|
||||
for (Alternating& alt : mAlternating)
|
||||
{
|
||||
alt.commandBuffer = commandPool->allocateCommandBuffer();
|
||||
alt.renderDoneFence = getOwner()->createChild<Fence>(FenceCreationArgs{.flags = vk::FenceCreateFlagBits::eSignaled});
|
||||
}
|
||||
}
|
||||
|
||||
void RenderLoop::start() noexcept
|
||||
{
|
||||
addTask(c_renderLoop());
|
||||
}
|
||||
|
||||
mijin::Task<> RenderLoop::c_init()
|
||||
{
|
||||
co_return;
|
||||
}
|
||||
|
||||
mijin::SimpleTaskLoop& RenderLoop::getTaskLoop() const noexcept
|
||||
{
|
||||
return getOwner()->getOwner()->getMainTaskLoop();
|
||||
}
|
||||
|
||||
mijin::Task<> RenderLoop::c_renderLoop()
|
||||
{
|
||||
co_await c_init();
|
||||
|
||||
while (!getOwner()->getOwner()->isQuitRequested())
|
||||
{
|
||||
Alternating& alt = mAlternating.at(mFrameIdx);
|
||||
|
||||
// wait for the command buffer to be ready
|
||||
co_await alt.renderDoneFence->c_wait();
|
||||
|
||||
// reset the fence
|
||||
alt.renderDoneFence->reset();
|
||||
|
||||
vk::CommandBuffer cmdBuffer = alt.commandBuffer->getVkHandle();
|
||||
cmdBuffer.begin(vk::CommandBufferBeginInfo{
|
||||
.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit
|
||||
});
|
||||
|
||||
// record the commands
|
||||
RenderLoopRenderArgs renderArgs = {
|
||||
.cmdBuffer = *alt.commandBuffer,
|
||||
.frameIdx = mFrameIdx
|
||||
};
|
||||
{ // gRenderMutex lock
|
||||
const mijin::TaskMutexLock lock = co_await gRenderMutex.c_lock();
|
||||
co_await c_render(renderArgs);
|
||||
|
||||
std::vector<vk::Semaphore> waitSemaphores;
|
||||
std::vector<vk::Semaphore> signalSemaphores;
|
||||
|
||||
ImageReferenceFinalizeArgs finalizeArgs{
|
||||
.cmdBuffer = *alt.commandBuffer,
|
||||
.waitSemaphores = waitSemaphores,
|
||||
.signalSemaphores = signalSemaphores
|
||||
};
|
||||
for (const ObjectPtr<ImageReference>& imageRef: renderArgs.usedImageReferences)
|
||||
{
|
||||
imageRef->finalize(finalizeArgs);
|
||||
}
|
||||
|
||||
cmdBuffer.end();
|
||||
|
||||
// submit them
|
||||
const vk::PipelineStageFlags waitStage = vk::PipelineStageFlagBits::eFragmentShader;
|
||||
getOwner()->getGraphicsQueue().submit(vk::SubmitInfo{
|
||||
.waitSemaphoreCount = static_cast<std::uint32_t>(waitSemaphores.size()),
|
||||
.pWaitSemaphores = waitSemaphores.data(),
|
||||
.pWaitDstStageMask = &waitStage,
|
||||
.commandBufferCount = 1,
|
||||
.pCommandBuffers = &cmdBuffer,
|
||||
.signalSemaphoreCount = static_cast<std::uint32_t>(signalSemaphores.size()),
|
||||
.pSignalSemaphores = signalSemaphores.data()
|
||||
}, *alt.renderDoneFence);
|
||||
} // gRenderMutex lock
|
||||
|
||||
// finally present
|
||||
for (const ObjectPtr<ImageReference>& imageRef : renderArgs.usedImageReferences)
|
||||
{
|
||||
co_await imageRef->c_present();
|
||||
}
|
||||
|
||||
// tick deleters
|
||||
// TODO: what if there are multiple render loops?
|
||||
if (mAdvanceDeleteQueue)
|
||||
{
|
||||
getOwner()->getOwner()->tickDeleteQueue();
|
||||
}
|
||||
|
||||
mFrameIdx = (mFrameIdx + 1) % mAlternating.size();
|
||||
}
|
||||
co_return;
|
||||
}
|
||||
} // namespace iwa
|
||||
676
source/util/shader_meta.cpp
Normal file
676
source/util/shader_meta.cpp
Normal file
@@ -0,0 +1,676 @@
|
||||
|
||||
#include "iwa/util/shader_meta.hpp"
|
||||
|
||||
#include "iwa/log.hpp"
|
||||
#include "kazan/resource/mesh.hpp"
|
||||
#include "iwa/util/glsl_compiler.hpp"
|
||||
#include "iwa/util/vkutil.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
template<typename T>
|
||||
inline std::size_t calcCrcSizeAppend(T, std::size_t) noexcept
|
||||
{
|
||||
MIJIN_TRAP(); // TODO
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
namespace iwa
|
||||
{
|
||||
namespace
|
||||
{
|
||||
vk::ShaderStageFlags typeBitsToVkStages(ShaderTypeBits bits)
|
||||
{
|
||||
vk::ShaderStageFlags flags = {};
|
||||
if (bits.compute)
|
||||
{
|
||||
flags |= vk::ShaderStageFlagBits::eCompute;
|
||||
}
|
||||
if (bits.vertex)
|
||||
{
|
||||
flags |= vk::ShaderStageFlagBits::eVertex;
|
||||
}
|
||||
if (bits.fragment)
|
||||
{
|
||||
flags |= vk::ShaderStageFlagBits::eFragment;
|
||||
}
|
||||
if (bits.rayGeneration)
|
||||
{
|
||||
flags |= vk::ShaderStageFlagBits::eRaygenKHR;
|
||||
}
|
||||
if (bits.rayClosestHit)
|
||||
{
|
||||
flags |= vk::ShaderStageFlagBits::eClosestHitKHR;
|
||||
}
|
||||
if (bits.rayAnyHit)
|
||||
{
|
||||
flags |= vk::ShaderStageFlagBits::eAnyHitKHR;
|
||||
}
|
||||
if (bits.rayMiss)
|
||||
{
|
||||
flags |= vk::ShaderStageFlagBits::eMissKHR;
|
||||
}
|
||||
if (bits.rayIntersection)
|
||||
{
|
||||
flags |= vk::ShaderStageFlagBits::eIntersectionKHR;
|
||||
}
|
||||
if (bits.callable)
|
||||
{
|
||||
flags |= vk::ShaderStageFlagBits::eCallableKHR;
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
void addShaderAttribute(std::vector<ShaderAttribute>& attributes, ShaderAttribute&& attribute)
|
||||
{
|
||||
bool doInsert = true;
|
||||
for (const ShaderAttribute& myAttribute: attributes)
|
||||
{
|
||||
if (myAttribute.stage == attribute.stage && myAttribute.location == attribute.location && myAttribute.location != UNSPECIFIED_INDEX)
|
||||
{
|
||||
// same location, type must be the same
|
||||
if (myAttribute.type != attribute.type)
|
||||
{
|
||||
logAndDie(
|
||||
"Attempting to merge incompatible shader metas, attributes {} and {} are incompatible. {} != {}",
|
||||
myAttribute.name, attribute.name, myAttribute.type, attribute.type);
|
||||
}
|
||||
doInsert = false; // member already exists, don't insert
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!doInsert)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto it = attributes.begin();
|
||||
for (; it != attributes.end(); ++it)
|
||||
{
|
||||
if (static_cast<unsigned>(it->stage) > static_cast<unsigned>(attribute.stage)
|
||||
|| (it->stage == attribute.stage && it->location > attribute.location))
|
||||
{
|
||||
break; // insert here
|
||||
}
|
||||
}
|
||||
attributes.insert(it, std::move(attribute));
|
||||
}
|
||||
}
|
||||
ShaderVariableStructType::ShaderVariableStructType() {} // NOLINT(modernize-use-equals-default)
|
||||
ShaderVariableStructType::~ShaderVariableStructType() {} // NOLINT(modernize-use-equals-default)
|
||||
|
||||
void ShaderMeta::extendPushConstant(ShaderPushConstantBlock pushConstantBlock_, ShaderTypeBits stages)
|
||||
{
|
||||
if (pushConstantBlock_.type.baseType == ShaderVariableBaseType::NONE) {
|
||||
return;
|
||||
}
|
||||
if (pushConstantBlock.type.baseType == ShaderVariableBaseType::NONE)
|
||||
{
|
||||
pushConstantBlock = std::move(pushConstantBlock_);
|
||||
pushConstantStages = stages;
|
||||
return;
|
||||
}
|
||||
|
||||
// now comes the actual merging
|
||||
assert(pushConstantBlock.type.baseType == ShaderVariableBaseType::STRUCT);
|
||||
assert(pushConstantBlock_.type.baseType == ShaderVariableBaseType::STRUCT);
|
||||
assert(stages);
|
||||
|
||||
for (ShaderVariableStructMember& member : pushConstantBlock_.type.struct_.members)
|
||||
{
|
||||
bool doInsert = true;
|
||||
for (const ShaderVariableStructMember& myMember : pushConstantBlock.type.struct_.members)
|
||||
{
|
||||
if (myMember.offset == member.offset)
|
||||
{
|
||||
// same offset, type must be the same
|
||||
if (myMember.type != member.type)
|
||||
{
|
||||
logAndDie("Attempting to merge incompatible push constant blocks, members {} and {} are incompatible. {} != {}",
|
||||
myMember.name, member.name, myMember.type, member.type);
|
||||
}
|
||||
doInsert = false; // member already exists, don't insert
|
||||
continue;
|
||||
}
|
||||
|
||||
// otherwise check for overlaps
|
||||
if ((myMember.offset < member.offset && myMember.offset + calcShaderTypeSize(myMember.type) > member.offset)
|
||||
|| (myMember.offset > member.offset && myMember.offset < member.offset + calcShaderTypeSize(member.type)))
|
||||
{
|
||||
logAndDie("Attempting to merge incompatible push constant blocks, members {} and {} are overlapping.",
|
||||
myMember.name, member.name);
|
||||
}
|
||||
}
|
||||
|
||||
if (!doInsert) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto it = pushConstantBlock.type.struct_.members.begin();
|
||||
for (; it != pushConstantBlock.type.struct_.members.end(); ++it)
|
||||
{
|
||||
if (it->offset > member.offset) {
|
||||
break; // insert here
|
||||
}
|
||||
}
|
||||
pushConstantBlock.type.struct_.members.insert(it, std::move(member));
|
||||
}
|
||||
|
||||
pushConstantStages |= stages;
|
||||
}
|
||||
|
||||
void ShaderMeta::addInputAttribute(ShaderAttribute attribute)
|
||||
{
|
||||
addShaderAttribute(inputAttributes, std::move(attribute));
|
||||
}
|
||||
|
||||
void ShaderMeta::addOutputAttribute(ShaderAttribute attribute)
|
||||
{
|
||||
addShaderAttribute(outputAttributes, std::move(attribute));
|
||||
}
|
||||
|
||||
ObjectPtr<DescriptorSetLayout> DescriptorSetMeta::createDescriptorSetLayout(Device& device) const
|
||||
{
|
||||
assert(bindings.size() == bindingFlags.size());
|
||||
return device.createChild<DescriptorSetLayout>(DescriptorSetLayoutCreationArgs{
|
||||
.bindings = bindings,
|
||||
.bindingFlags = bindingFlags,
|
||||
.flags = flags,
|
||||
});
|
||||
}
|
||||
|
||||
std::vector<ObjectPtr<DescriptorSet>> PipelineAndDescriptorSetLayouts::createDescriptorSets(DescriptorPool& pool) const
|
||||
{
|
||||
std::vector<ObjectPtr<DescriptorSet>> result;
|
||||
result.reserve(descriptorSetLayouts.size());
|
||||
|
||||
for (const ObjectPtr<DescriptorSetLayout>& layout : descriptorSetLayouts)
|
||||
{
|
||||
result.push_back(pool.allocateDescriptorSet({
|
||||
.layout = layout
|
||||
}));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
ObjectPtr<DescriptorSet> PipelineAndDescriptorSetLayouts::createDescriptorSet(DescriptorPool& pool, unsigned setIdx) const
|
||||
{
|
||||
MIJIN_ASSERT(setIdx < descriptorSetLayouts.size(), "Invalid set index.");
|
||||
return pool.allocateDescriptorSet({
|
||||
.layout = descriptorSetLayouts[setIdx]
|
||||
});
|
||||
}
|
||||
|
||||
PipelineAndDescriptorSetLayouts PipelineLayoutMeta::createPipelineLayout(Device& device) const
|
||||
{
|
||||
std::vector<ObjectPtr<DescriptorSetLayout>> descSetLayouts;
|
||||
descSetLayouts.reserve(descriptorSets.size());
|
||||
|
||||
for (const DescriptorSetMeta& dslMeta : descriptorSets)
|
||||
{
|
||||
descSetLayouts.push_back(dslMeta.createDescriptorSetLayout(device));
|
||||
}
|
||||
|
||||
std::vector<vk::PushConstantRange> pushConstantRanges;
|
||||
if (pushConstantRange.stageFlags)
|
||||
{
|
||||
pushConstantRanges.push_back(pushConstantRange);
|
||||
}
|
||||
|
||||
ObjectPtr<PipelineLayout> pipelineLayout = device.createChild<PipelineLayout>(PipelineLayoutCreationArgs{
|
||||
.setLayouts = descSetLayouts,
|
||||
.pushConstantRanges = std::move(pushConstantRanges)
|
||||
});
|
||||
return
|
||||
{
|
||||
.descriptorSetLayouts = std::move(descSetLayouts),
|
||||
.pipelineLayout = std::move(pipelineLayout)
|
||||
};
|
||||
}
|
||||
|
||||
void ShaderVariable::verifyCompatible(const ShaderVariable& other) const
|
||||
{
|
||||
std::vector<std::string> errors;
|
||||
if (other.binding != binding) {
|
||||
errors.push_back(fmt::format("Variable bindings do not match: {} != {}.", binding, other.binding)); // NOLINT
|
||||
}
|
||||
if (other.descriptorType != descriptorType) {
|
||||
errors.push_back(fmt::format("Descriptor types do not match: {} != {}.",
|
||||
magic_enum::enum_name(descriptorType),
|
||||
magic_enum::enum_name(other.descriptorType)));
|
||||
}
|
||||
if (other.name != name) {
|
||||
logMsg("Warning: shader variable names do not match, variable will only be referrable to by one of them! ({} != {})",
|
||||
name, other.name);
|
||||
}
|
||||
if (other.type != type) {
|
||||
errors.push_back(fmt::format("Variable types do not match: {} != {}.", type, other.type));
|
||||
}
|
||||
|
||||
if (errors.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
logMsg("Error(s) verifying shader variable compatibility:");
|
||||
for (const std::string& error : errors) {
|
||||
logMsg(error);
|
||||
}
|
||||
std::abort();
|
||||
}
|
||||
|
||||
std::size_t ShaderVariable::calcHash(std::size_t appendTo) const
|
||||
{
|
||||
(void) appendTo;
|
||||
MIJIN_TRAP(); // TODO
|
||||
return 0;
|
||||
#if 0
|
||||
std::size_t hash = appendTo;
|
||||
hash = type.calcHash(hash);
|
||||
hash = calcCrcSizeAppend(descriptorType, hash);
|
||||
hash = calcCrcSizeAppend(binding, hash);
|
||||
hash = calcCrcSizeAppend(name, hash);
|
||||
return hash;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if 0
|
||||
ShaderSource ShaderSource::fromFile(std::string fileName, std::string name)
|
||||
{
|
||||
(void) fileName;
|
||||
(void) name;
|
||||
MIJIN_TRAP(); // TODO
|
||||
return {};
|
||||
std::string code = readFileText(fileName);
|
||||
return {
|
||||
.code = std::move(code),
|
||||
.fileName = std::move(fileName),
|
||||
#if !defined(KAZAN_RELEASE)
|
||||
.name = std::move(name)
|
||||
#endif
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
||||
bool ShaderVariableSet::find(std::string_view varName, ShaderVariableFindResult& outResult) const noexcept
|
||||
{
|
||||
for (const ShaderVariable& var : variables)
|
||||
{
|
||||
if (var.name == varName)
|
||||
{
|
||||
outResult.setIndex = setIndex;
|
||||
outResult.bindIndex = var.binding;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ShaderVariableSet::find(unsigned semantic, unsigned semanticIdx, ShaderVariableFindResult& outResult) const noexcept
|
||||
{
|
||||
for (const ShaderVariable& var : variables)
|
||||
{
|
||||
if (var.semantic == semantic && var.semanticIndex == semanticIdx)
|
||||
{
|
||||
outResult.setIndex = setIndex;
|
||||
outResult.bindIndex = var.binding;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const ShaderVariable& ShaderVariableSet::getVariableAtBinding(unsigned bindingIdx) const
|
||||
{
|
||||
for (const ShaderVariable& var : variables)
|
||||
{
|
||||
if (var.binding == bindingIdx)
|
||||
{
|
||||
return var;
|
||||
}
|
||||
}
|
||||
|
||||
logAndDie("Could not find shader variable with binding {}!", bindingIdx);
|
||||
}
|
||||
|
||||
const ShaderVariable* ShaderVariableSet::getVariableAtBindingOpt(unsigned bindingIdx) const
|
||||
{
|
||||
for (const ShaderVariable& var : variables)
|
||||
{
|
||||
if (var.binding == bindingIdx)
|
||||
{
|
||||
return &var;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const ShaderVariable* ShaderVariableSet::getVariableAtSemanticOpt(unsigned semantic, unsigned semanticIdx) const
|
||||
{
|
||||
for (const ShaderVariable& var : variables)
|
||||
{
|
||||
if (var.semantic == semantic && var.semanticIndex == semanticIdx)
|
||||
{
|
||||
return &var;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::size_t ShaderVariableSet::calcHash(std::size_t appendTo) const
|
||||
{
|
||||
std::size_t hash = appendTo;
|
||||
for (const ShaderVariable& var : variables) {
|
||||
hash = var.calcHash(hash);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
void ShaderMeta::extend(ShaderMeta other)
|
||||
{
|
||||
for (ShaderVariableSet& set : other.interfaceVariableSets)
|
||||
{
|
||||
ShaderVariableSet& mySet = getOrCreateInterfaceVariableSet(set.setIndex);
|
||||
mySet.usedInStages.bits |= set.usedInStages.bits;
|
||||
|
||||
for (ShaderVariable& variable : set.variables)
|
||||
{
|
||||
const ShaderVariable* myVariable = nullptr;
|
||||
if (variable.binding != UNSPECIFIED_INDEX)
|
||||
{
|
||||
myVariable = mySet.getVariableAtBindingOpt(variable.binding);
|
||||
}
|
||||
else if (variable.semantic != UNSPECIFIED_INDEX)
|
||||
{
|
||||
myVariable = mySet.getVariableAtSemanticOpt(variable.semantic, variable.semanticIndex);
|
||||
}
|
||||
if (myVariable)
|
||||
{
|
||||
myVariable->verifyCompatible(variable);
|
||||
continue;
|
||||
}
|
||||
mySet.variables.push_back(std::move(variable));
|
||||
}
|
||||
}
|
||||
|
||||
for (ShaderAttribute& attribute : other.inputAttributes)
|
||||
{
|
||||
addInputAttribute(std::move(attribute));
|
||||
}
|
||||
|
||||
for (ShaderAttribute& attribute : other.outputAttributes)
|
||||
{
|
||||
addOutputAttribute(std::move(attribute));
|
||||
}
|
||||
|
||||
extendPushConstant(other.pushConstantBlock, other.pushConstantStages);
|
||||
stages |= other.stages;
|
||||
|
||||
if (localSizeX == 0 && localSizeY == 0 && localSizeZ == 0)
|
||||
{
|
||||
localSizeX = other.localSizeX;
|
||||
localSizeY = other.localSizeY;
|
||||
localSizeZ = other.localSizeZ;
|
||||
}
|
||||
else if ((other.localSizeX != 0 || other.localSizeY != 0 || other.localSizeZ != 0) &&
|
||||
(localSizeX != other.localSizeX || localSizeY != other.localSizeY || localSizeZ != other.localSizeZ))
|
||||
{
|
||||
logAndDie("Error merging shader metas, conflicting local size!");
|
||||
}
|
||||
|
||||
hash = 0;
|
||||
}
|
||||
|
||||
bool ShaderMeta::findInterfaceVariable(std::string_view varName, ShaderVariableFindResult& outResult) const noexcept
|
||||
{
|
||||
for (const ShaderVariableSet& set : interfaceVariableSets)
|
||||
{
|
||||
if (set.find(varName, outResult)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ShaderMeta::findInterfaceVariable(unsigned semantic, unsigned semanticIdx, ShaderVariableFindResult& outResult) const noexcept
|
||||
{
|
||||
for (const ShaderVariableSet& set : interfaceVariableSets)
|
||||
{
|
||||
if (set.find(semantic, semanticIdx, outResult)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const ShaderVariableSet& ShaderMeta::getInterfaceVariableSet(unsigned setIdx) const
|
||||
{
|
||||
const ShaderVariableSet* variableSet = getInterfaceVariableSetOpt(setIdx);
|
||||
MIJIN_ASSERT(variableSet != nullptr, "Could not find interface variable set.");
|
||||
return *variableSet;
|
||||
}
|
||||
|
||||
const ShaderVariableSet* ShaderMeta::getInterfaceVariableSetOpt(unsigned setIdx) const
|
||||
{
|
||||
for (const ShaderVariableSet& set : interfaceVariableSets)
|
||||
{
|
||||
if (set.setIndex == setIdx) {
|
||||
return &set;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const ShaderVariableType& ShaderMeta::getInterfaceVariableType(unsigned setIdx, unsigned bindingIdx) const
|
||||
{
|
||||
return getInterfaceVariableSet(setIdx).getVariableAtBinding(bindingIdx).type;
|
||||
}
|
||||
|
||||
VertexInput ShaderMeta::generateVertexInput(const NamedVertexInput& namedInput) const noexcept
|
||||
{
|
||||
VertexInput result{
|
||||
.bindings = namedInput.bindings
|
||||
};
|
||||
|
||||
for (const ShaderAttribute& attribute : inputAttributes)
|
||||
{
|
||||
if (attribute.stage != vk::ShaderStageFlagBits::eVertex) {
|
||||
continue;
|
||||
}
|
||||
MIJIN_ASSERT_FATAL(attribute.type.baseType == ShaderVariableBaseType::SIMPLE, "Vertex shader input must be a simple type.");
|
||||
auto itAttribute = namedInput.attributes.find(attribute.name);
|
||||
MIJIN_ASSERT_FATAL(itAttribute != namedInput.attributes.end(), "Missing attribute in input.");
|
||||
result.attributes.push_back(vk::VertexInputAttributeDescription{
|
||||
.location = attribute.location,
|
||||
.binding = itAttribute->second.binding,
|
||||
.format = attribute.type.simple.format,
|
||||
.offset = itAttribute->second.offset
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
VertexInput ShaderMeta::generateVertexInputFromLayout(const VertexLayout& layout) const noexcept
|
||||
{
|
||||
VertexInput result{
|
||||
.bindings = {
|
||||
vk::VertexInputBindingDescription{
|
||||
.binding = 0,
|
||||
.stride = layout.stride,
|
||||
.inputRate = vk::VertexInputRate::eVertex
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (const ShaderAttribute& attribute : inputAttributes)
|
||||
{
|
||||
if (attribute.stage != vk::ShaderStageFlagBits::eVertex) {
|
||||
continue;
|
||||
}
|
||||
if (attribute.semantic == UNSPECIFIED_INDEX) {
|
||||
continue;
|
||||
}
|
||||
MIJIN_ASSERT_FATAL(attribute.type.baseType == ShaderVariableBaseType::SIMPLE, "Vertex shader input must be a simple type.");
|
||||
auto itAttribute = std::ranges::find_if(layout.attributes, [&attribute](const VertexAttribute& attrib) {
|
||||
return static_cast<unsigned>(attrib.semantic) == attribute.semantic && attrib.semanticIdx == attribute.semanticIndex;
|
||||
});
|
||||
MIJIN_ASSERT_FATAL(itAttribute != layout.attributes.end(), "Missing attribute in vertex layout.");
|
||||
result.attributes.push_back(vk::VertexInputAttributeDescription{
|
||||
.location = attribute.location,
|
||||
.binding = 0,
|
||||
.format = attribute.type.simple.format,
|
||||
.offset = itAttribute->offset
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
DescriptorSetMeta ShaderMeta::generateDescriptorSetLayout(const ShaderVariableSet& set, const GenerateDescriptorSetLayoutArgs& args) const
|
||||
{
|
||||
DescriptorSetMeta setInfo{
|
||||
.flags = args.flags
|
||||
};
|
||||
|
||||
for (const ShaderVariable& var : set.variables)
|
||||
{
|
||||
auto itVar = std::ranges::find_if(setInfo.bindings, [&](const vk::DescriptorSetLayoutBinding& binding) {
|
||||
return binding.binding == var.binding;
|
||||
});
|
||||
assert(itVar == setInfo.bindings.end()); // should have been merged!
|
||||
if (itVar != setInfo.bindings.end())
|
||||
{
|
||||
itVar->stageFlags |= typeBitsToVkStages(set.usedInStages);
|
||||
continue; // TODO: verify the bindings are compatible
|
||||
}
|
||||
vk::DescriptorSetLayoutBinding& binding = setInfo.bindings.emplace_back();
|
||||
vk::DescriptorBindingFlags& flags = setInfo.bindingFlags.emplace_back();
|
||||
binding.binding = var.binding;
|
||||
binding.descriptorType = var.descriptorType;
|
||||
binding.descriptorCount = 1;
|
||||
binding.stageFlags = typeBitsToVkStages(set.usedInStages);
|
||||
|
||||
// support for dynamically sized descriptors
|
||||
auto itCounts = args.descriptorCounts.find(var.binding);
|
||||
if (itCounts != args.descriptorCounts.end() && itCounts->second > 0)
|
||||
{
|
||||
binding.descriptorCount = itCounts->second;
|
||||
flags |= vk::DescriptorBindingFlagBits::ePartiallyBound;
|
||||
}
|
||||
|
||||
if (setInfo.descriptorTypes.size() <= var.binding) {
|
||||
setInfo.descriptorTypes.resize(var.binding + 1);
|
||||
}
|
||||
setInfo.descriptorTypes[var.binding] = var.descriptorType;
|
||||
}
|
||||
|
||||
return setInfo;
|
||||
}
|
||||
|
||||
PipelineLayoutMeta ShaderMeta::generatePipelineLayout(const GeneratePipelineLayoutArgs& args) const
|
||||
{
|
||||
static const std::vector<std::uint32_t> NO_DESCRIPTOR_COUNTS = {};
|
||||
static const GenerateDescriptorSetLayoutArgs NO_DESCRIPTOR_SET_ARGS = {};
|
||||
|
||||
PipelineLayoutMeta result;
|
||||
for (const ShaderVariableSet& set : interfaceVariableSets)
|
||||
{
|
||||
if (set.setIndex >= result.descriptorSets.size()) {
|
||||
result.descriptorSets.resize(set.setIndex + 1);
|
||||
}
|
||||
auto itSet = args.descriptorSets.find(set.setIndex);
|
||||
const GenerateDescriptorSetLayoutArgs setArgs =
|
||||
itSet != args.descriptorSets.end()
|
||||
? itSet->second
|
||||
: NO_DESCRIPTOR_SET_ARGS;
|
||||
result.descriptorSets[set.setIndex] = generateDescriptorSetLayout(set, setArgs);
|
||||
}
|
||||
|
||||
if (pushConstantBlock.type.baseType != ShaderVariableBaseType::NONE)
|
||||
{
|
||||
assert(pushConstantStages);
|
||||
result.pushConstantRange.stageFlags = typeBitsToVkStages(pushConstantStages);
|
||||
result.pushConstantRange.size = pushConstantBlock.offset + calcShaderTypeSize(pushConstantBlock.type);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool ShaderMeta::empty() const
|
||||
{
|
||||
static_assert(ShaderMeta::STRUCT_VERSION == 1, "Update me");
|
||||
return interfaceVariableSets.empty()
|
||||
&& inputAttributes.empty()
|
||||
&& outputAttributes.empty()
|
||||
&& pushConstantStages == ShaderTypeBits()
|
||||
&& pushConstantBlock.type.baseType == ShaderVariableBaseType::NONE
|
||||
&& localSizeX == 0
|
||||
&& localSizeY == 0
|
||||
&& localSizeZ == 0;
|
||||
}
|
||||
|
||||
std::size_t ShaderMeta::getHash() const
|
||||
{
|
||||
if (hash == 0)
|
||||
{
|
||||
hash = 1; // TODO
|
||||
MIJIN_TRAP();
|
||||
#if 0
|
||||
for (const ShaderVariableSet& variableSet : interfaceVariableSets) {
|
||||
hash = variableSet.calcHash(hash);
|
||||
}
|
||||
hash = calcCrcSizeAppend(pushConstantStages.bits, hash);
|
||||
hash = pushConstantBlock.type.calcHash(hash);
|
||||
hash = calcCrcSizeAppend(pushConstantBlock.offset, hash);
|
||||
hash = calcCrcSizeAppend(localSizeX, hash);
|
||||
hash = calcCrcSizeAppend(localSizeY, hash);
|
||||
hash = calcCrcSizeAppend(localSizeZ, hash);
|
||||
#endif
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
unsigned calcShaderTypeSize(const ShaderVariableType& type, bool ignoreArraySize) noexcept
|
||||
{
|
||||
unsigned size = 0;
|
||||
switch (type.baseType)
|
||||
{
|
||||
case ShaderVariableBaseType::SIMPLE:
|
||||
size = vkFormatSize(type.simple.format);
|
||||
break;
|
||||
case ShaderVariableBaseType::MATRIX:
|
||||
switch (type.matrixType)
|
||||
{
|
||||
case ShaderVariableMatrixType::MAT2:
|
||||
size = 16;
|
||||
break;
|
||||
case ShaderVariableMatrixType::MAT3:
|
||||
size = 36;
|
||||
break;
|
||||
case ShaderVariableMatrixType::MAT4:
|
||||
size = 64;
|
||||
break;
|
||||
default:
|
||||
logAndDie("Lol, what's this?");
|
||||
}
|
||||
break;
|
||||
case ShaderVariableBaseType::STRUCT:
|
||||
assert(!type.struct_.members.empty());
|
||||
size = static_cast<unsigned>(type.struct_.members.back().offset + calcShaderTypeSize(type.struct_.members.back().type));
|
||||
break;
|
||||
default:
|
||||
logAndDie("How would I know?");
|
||||
}
|
||||
if (!ignoreArraySize) {
|
||||
size *= type.arraySize;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
} // namespace iwa
|
||||
402
source/util/texture_atlas.cpp
Normal file
402
source/util/texture_atlas.cpp
Normal file
@@ -0,0 +1,402 @@
|
||||
|
||||
#include "iwa/util/texture_atlas.hpp"
|
||||
|
||||
#include <bit>
|
||||
#include "iwa/device.hpp"
|
||||
#include "iwa/instance.hpp"
|
||||
#include "iwa/resource/bitmap.hpp"
|
||||
|
||||
namespace iwa
|
||||
{
|
||||
TextureSlot::TextureSlot(ObjectPtr<TextureAtlas> owner, const TextureSlotCreationArgs& args)
|
||||
: super_t(std::move(owner)), mUsedSpace(args.usedSpace), mLayer(args.layer), mUvOffset(args.uvOffset), mUvScale(args.uvScale)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
TextureAtlas::TextureAtlas(ObjectPtr<> owner, const TextureAtlasCreationArgs& args)
|
||||
: super_t(std::move(owner)), mLayerSize(args.layerSize)
|
||||
{
|
||||
// start with a single layer with one free space that takes up the entire layer
|
||||
mLayers.push_back({
|
||||
.freeSpaces = {
|
||||
vk::Rect2D{
|
||||
.offset = { .x = 0, .y = 0 },
|
||||
.extent = args.layerSize
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ObjectPtr<TextureSlot> TextureAtlas::allocateSlot(vk::Extent2D slotSize)
|
||||
{
|
||||
// only uses multiples of 2
|
||||
// TODO: check if this actually improves the results
|
||||
const vk::Extent2D size = {
|
||||
.width = std::bit_ceil(slotSize.width),
|
||||
.height = std::bit_ceil(slotSize.height)
|
||||
};
|
||||
|
||||
// check if it can even fit
|
||||
if (size.width > mLayerSize.width || size.height > mLayerSize.height) {
|
||||
throw std::runtime_error("Cannot allocate texture slot, size too big.");
|
||||
}
|
||||
|
||||
// find the best fit (minimize product of "wasted" space)
|
||||
unsigned lowestWasteSum = std::numeric_limits<unsigned>::max();
|
||||
unsigned lowestWasteProduct = std::numeric_limits<unsigned>::max();
|
||||
std::vector<TextureAtlasLayer>::iterator foundLayer = mLayers.end();
|
||||
std::vector<vk::Rect2D>::iterator foundSpace;
|
||||
for (auto itLayer = mLayers.begin(); itLayer != mLayers.end(); ++itLayer)
|
||||
{
|
||||
for (auto itSpace = itLayer->freeSpaces.begin(); itSpace != itLayer->freeSpaces.end(); ++itSpace)
|
||||
{
|
||||
if (itSpace->extent.width < size.width || itSpace->extent.height < size.height) {
|
||||
continue;
|
||||
}
|
||||
const unsigned wasteWidth = itSpace->extent.width - size.width;
|
||||
const unsigned wasteHeight = itSpace->extent.height - size.height;
|
||||
const unsigned wasteProduct = wasteWidth * wasteHeight;
|
||||
if (wasteProduct <= lowestWasteProduct)
|
||||
{
|
||||
const unsigned wasteSum = wasteWidth + wasteHeight;
|
||||
if (wasteProduct < lowestWasteProduct || wasteSum < lowestWasteSum)
|
||||
{
|
||||
lowestWasteSum = wasteSum;
|
||||
lowestWasteProduct = wasteProduct;
|
||||
foundLayer = itLayer;
|
||||
foundSpace = itSpace;
|
||||
}
|
||||
}
|
||||
} // for (itLayer->freeSpaces)
|
||||
} // for (mLayers)
|
||||
|
||||
// if no space was found, make space
|
||||
if (foundLayer == mLayers.end())
|
||||
{
|
||||
mLayers.resize(mLayers.size() + 1);
|
||||
mLayers.back().freeSpaces.push_back({
|
||||
.offset = { .x = 0, .y = 0},
|
||||
.extent = mLayerSize
|
||||
});
|
||||
foundLayer = std::prev(mLayers.end());
|
||||
foundSpace = foundLayer->freeSpaces.begin();
|
||||
}
|
||||
|
||||
// save in case the iterator gets invalidated
|
||||
const vk::Rect2D space = *foundSpace;
|
||||
|
||||
// remove it
|
||||
foundLayer->freeSpaces.erase(foundSpace);
|
||||
|
||||
// now split the space, if necessary
|
||||
const bool splitX = space.extent.width > size.width;
|
||||
const bool splitY = space.extent.height > size.height;
|
||||
if (splitX)
|
||||
{
|
||||
foundLayer->freeSpaces.push_back({
|
||||
.offset = {
|
||||
.x = static_cast<std::int32_t>(space.offset.x + size.width),
|
||||
.y = space.offset.y
|
||||
},
|
||||
.extent = {
|
||||
.width = space.extent.width - size.width,
|
||||
.height = size.height
|
||||
}
|
||||
});
|
||||
}
|
||||
if (splitY)
|
||||
{
|
||||
foundLayer->freeSpaces.push_back({
|
||||
.offset = {
|
||||
.x = space.offset.x,
|
||||
.y = static_cast<std::int32_t>(space.offset.y + size.height)
|
||||
},
|
||||
.extent = {
|
||||
.width = size.width,
|
||||
.height = space.extent.height - size.height
|
||||
}
|
||||
});
|
||||
}
|
||||
if (splitX && splitY)
|
||||
{
|
||||
foundLayer->freeSpaces.push_back({
|
||||
.offset = {
|
||||
.x = static_cast<std::int32_t>(space.offset.x + size.width),
|
||||
.y = static_cast<std::int32_t>(space.offset.y + size.height)
|
||||
},
|
||||
.extent = {
|
||||
.width = space.extent.width - size.width,
|
||||
.height = space.extent.height - size.height
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// return the result
|
||||
return createChild<TextureSlot>(TextureSlotCreationArgs{
|
||||
.usedSpace = {
|
||||
.offset = space.offset,
|
||||
.extent = slotSize
|
||||
},
|
||||
.layer = static_cast<unsigned>(std::distance(mLayers.begin(), foundLayer)),
|
||||
.uvOffset = {
|
||||
static_cast<float>(space.offset.x) / static_cast<float>(mLayerSize.width),
|
||||
static_cast<float>(space.offset.y) / static_cast<float>(mLayerSize.height)
|
||||
},
|
||||
.uvScale = {
|
||||
static_cast<float>(slotSize.width) / static_cast<float>(mLayerSize.width),
|
||||
static_cast<float>(slotSize.height) / static_cast<float>(mLayerSize.height)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
AtlasedImage::AtlasedImage(ObjectPtr<Device> owner, const AtlasedImageCreationArgs& args)
|
||||
: super_t(std::move(owner)), mAtlas(TextureAtlas::create(TextureAtlasCreationArgs{.layerSize = args.size})),
|
||||
mFormat(args.format), mMipLevels(args.mipLevels), mUsage(args.usage | vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst)
|
||||
{
|
||||
mImage = allocateImage(args.initialLayers);
|
||||
mImageView = mImage->createImageView({
|
||||
.viewType = vk::ImageViewType::e2DArray
|
||||
});
|
||||
}
|
||||
|
||||
mijin::Task<ObjectPtr<TextureSlot>> AtlasedImage::c_allocateSlot(vk::Extent2D slotSize)
|
||||
{
|
||||
IWA_CORO_ENSURE_MAIN_THREAD(*getOwner()->getOwner());
|
||||
|
||||
ObjectPtr<TextureSlot> slot = mAtlas->allocateSlot(slotSize);
|
||||
if (slot->getLayer() >= mImage->getArrayLayers())
|
||||
{
|
||||
const mijin::TaskMutexLock lock = co_await mImageMutex.c_lock();
|
||||
|
||||
// image is too small, resize it
|
||||
// this includes a complete copy of the existing image
|
||||
ObjectPtr<Image> newImage = allocateImage(slot->getLayer() + 1);
|
||||
ObjectPtr<CommandBuffer> cmdBufferPtr = getOwner()->beginScratchCommandBuffer();
|
||||
vk::CommandBuffer cmdBuffer = *cmdBufferPtr;
|
||||
mImage->applyTransition(cmdBuffer, IMAGE_TRANSITION_TRANSFER_READ);
|
||||
newImage->applyTransition(cmdBuffer, IMAGE_TRANSITION_TRANSFER_WRITE);
|
||||
|
||||
// copy ALL the mip levels
|
||||
std::vector<vk::ImageCopy> regions;
|
||||
regions.reserve(mImage->getMipLevels());
|
||||
for (unsigned level = 0; level < mImage->getMipLevels(); ++level)
|
||||
{
|
||||
const vk::ImageSubresourceLayers copySubresource{
|
||||
.aspectMask = vk::ImageAspectFlagBits::eColor,
|
||||
.mipLevel = level,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = mImage->getArrayLayers()
|
||||
};
|
||||
regions.push_back({
|
||||
.srcSubresource = copySubresource,
|
||||
.srcOffset = {.x = 0, .y = 0, .z = 0},
|
||||
.dstSubresource = copySubresource,
|
||||
.dstOffset = {.x = 0, .y = 0, .z = 0},
|
||||
.extent = {
|
||||
.width = mAtlas->getLayerSize().width,
|
||||
.height = mAtlas->getLayerSize().height,
|
||||
.depth = 1
|
||||
}
|
||||
});
|
||||
}
|
||||
cmdBuffer.copyImage(
|
||||
/* srcImage = */ *mImage,
|
||||
/* srcImageLayout = */ vk::ImageLayout::eTransferSrcOptimal,
|
||||
/* dstImage = */ *newImage,
|
||||
/* dstImageLayout = */ vk::ImageLayout::eTransferDstOptimal,
|
||||
/* regions = */ regions
|
||||
);
|
||||
co_await getOwner()->endScratchCommandBuffer(cmdBufferPtr);
|
||||
mImage = std::move(newImage);
|
||||
mImageView = mImage->createImageView({
|
||||
.viewType = vk::ImageViewType::e2DArray
|
||||
});
|
||||
imageRecreated.emit();
|
||||
}
|
||||
co_return slot;
|
||||
}
|
||||
|
||||
mijin::Task<> AtlasedImage::c_upload(const TextureSlot& slot, const Bitmap& bitmap) const noexcept
|
||||
{
|
||||
IWA_CORO_ENSURE_MAIN_THREAD(*getOwner()->getOwner());
|
||||
MIJIN_ASSERT(slot.getUsedSpace().extent.width >= bitmap.getSize().width
|
||||
&& slot.getUsedSpace().extent.height >= bitmap.getSize().height, "Can't upload image, invalid size.");
|
||||
|
||||
const mijin::TaskMutexLock lock = co_await mImageMutex.c_lock();
|
||||
co_await mImage->c_upload(
|
||||
/* bitmap = */ bitmap,
|
||||
/* imageOffset = */ {
|
||||
.x = slot.getUsedSpace().offset.x,
|
||||
.y = slot.getUsedSpace().offset.y,
|
||||
.z = 0
|
||||
},
|
||||
/* baseLayer = */ slot.getLayer()
|
||||
);
|
||||
}
|
||||
|
||||
mijin::Task<> AtlasedImage::c_upload(const TextureSlot& slot, const void* data, std::size_t bytes, const vk::Extent2D& bufferImageSize) const noexcept
|
||||
{
|
||||
IWA_CORO_ENSURE_MAIN_THREAD(*getOwner()->getOwner());
|
||||
MIJIN_ASSERT(slot.getUsedSpace().extent.width >= bufferImageSize.width
|
||||
&& slot.getUsedSpace().extent.height >= bufferImageSize.height, "Can't upload image, invalid size.");
|
||||
|
||||
const mijin::TaskMutexLock lock = co_await mImageMutex.c_lock();
|
||||
co_await mImage->c_upload(
|
||||
/* data = */ data,
|
||||
/* bytes = */ bytes,
|
||||
/* bufferImageSize = */ {
|
||||
.width = bufferImageSize.width,
|
||||
.height = bufferImageSize.height,
|
||||
.depth = 1
|
||||
},
|
||||
/* imageOffset = */ {
|
||||
.x = slot.getUsedSpace().offset.x,
|
||||
.y = slot.getUsedSpace().offset.y,
|
||||
.z = 0
|
||||
},
|
||||
/* baseLayer = */ slot.getLayer()
|
||||
);
|
||||
}
|
||||
|
||||
mijin::Task<> AtlasedImage::c_blit(const TextureSlot& slot, Image& srcImage) const noexcept
|
||||
{
|
||||
IWA_CORO_ENSURE_MAIN_THREAD(*getOwner()->getOwner());
|
||||
MIJIN_ASSERT(slot.getUsedSpace().extent.width >= srcImage.getSize().width
|
||||
&& slot.getUsedSpace().extent.height >= srcImage.getSize().height
|
||||
&& srcImage.getSize().depth == 1, "Can't upload image, invalid size.");
|
||||
|
||||
const mijin::TaskMutexLock lock = co_await mImageMutex.c_lock();
|
||||
co_await mImage->c_blitFrom(
|
||||
/* srcImage = */ srcImage,
|
||||
/* regions = */ {
|
||||
vk::ImageBlit{
|
||||
.srcSubresource = DEFAULT_SUBRESOURCE_LAYERS,
|
||||
.srcOffsets = std::array{
|
||||
vk::Offset3D{
|
||||
.x = 0, .y = 0, .z = 0
|
||||
},
|
||||
vk::Offset3D{
|
||||
.x = static_cast<std::int32_t>(srcImage.getSize().width),
|
||||
.y = static_cast<std::int32_t>(srcImage.getSize().height),
|
||||
.z = 1
|
||||
}
|
||||
},
|
||||
.dstSubresource = vk::ImageSubresourceLayers{
|
||||
.aspectMask = vk::ImageAspectFlagBits::eColor,
|
||||
.mipLevel = 0,
|
||||
.baseArrayLayer = slot.getLayer(),
|
||||
.layerCount = 1
|
||||
},
|
||||
.dstOffsets = std::array{
|
||||
vk::Offset3D{
|
||||
.x = slot.getUsedSpace().offset.x,
|
||||
.y = slot.getUsedSpace().offset.y,
|
||||
.z = 0
|
||||
},
|
||||
vk::Offset3D{
|
||||
.x = slot.getUsedSpace().offset.x + static_cast<std::int32_t>(slot.getUsedSpace().extent.width),
|
||||
.y = slot.getUsedSpace().offset.y + static_cast<std::int32_t>(slot.getUsedSpace().extent.height),
|
||||
.z = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
mijin::Task<> AtlasedImage::c_blit(const TextureSlot& slot, const Bitmap& bitmap) const noexcept
|
||||
{
|
||||
IWA_CORO_ENSURE_MAIN_THREAD(*getOwner()->getOwner());
|
||||
MIJIN_ASSERT(slot.getUsedSpace().extent.width >= bitmap.getSize().width
|
||||
&& slot.getUsedSpace().extent.height >= bitmap.getSize().height, "Can't upload image, invalid size.");
|
||||
|
||||
const mijin::TaskMutexLock lock = co_await mImageMutex.c_lock();
|
||||
co_await mImage->c_blitFrom(
|
||||
/* bitmap = */ bitmap,
|
||||
/* regions = */ {
|
||||
vk::ImageBlit{
|
||||
.srcSubresource = DEFAULT_SUBRESOURCE_LAYERS,
|
||||
.srcOffsets = std::array{
|
||||
vk::Offset3D{
|
||||
.x = 0, .y = 0, .z = 0
|
||||
},
|
||||
vk::Offset3D{
|
||||
.x = static_cast<std::int32_t>(bitmap.getSize().width),
|
||||
.y = static_cast<std::int32_t>(bitmap.getSize().height),
|
||||
.z = 1
|
||||
}
|
||||
},
|
||||
.dstSubresource = vk::ImageSubresourceLayers{
|
||||
.aspectMask = vk::ImageAspectFlagBits::eColor,
|
||||
.mipLevel = 0,
|
||||
.baseArrayLayer = slot.getLayer(),
|
||||
.layerCount = 1
|
||||
},
|
||||
.dstOffsets = std::array{
|
||||
vk::Offset3D{
|
||||
.x = slot.getUsedSpace().offset.x,
|
||||
.y = slot.getUsedSpace().offset.y,
|
||||
.z = 0
|
||||
},
|
||||
vk::Offset3D{
|
||||
.x = slot.getUsedSpace().offset.x + static_cast<std::int32_t>(slot.getUsedSpace().extent.width),
|
||||
.y = slot.getUsedSpace().offset.y + static_cast<std::int32_t>(slot.getUsedSpace().extent.height),
|
||||
.z = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
mijin::Task<> AtlasedImage::c_copy(const TextureSlot& slot, Image& srcImage) const noexcept
|
||||
{
|
||||
IWA_CORO_ENSURE_MAIN_THREAD(*getOwner()->getOwner());
|
||||
MIJIN_ASSERT(slot.getUsedSpace().extent.width >= srcImage.getSize().width
|
||||
&& slot.getUsedSpace().extent.height >= srcImage.getSize().height
|
||||
&& srcImage.getSize().depth == 1, "Can't upload image, invalid size.");
|
||||
|
||||
const mijin::TaskMutexLock lock = co_await mImageMutex.c_lock();
|
||||
co_await mImage->c_copyFrom(
|
||||
/* srcImage = */ srcImage,
|
||||
/* regions = */ {
|
||||
vk::ImageCopy{
|
||||
.srcSubresource = DEFAULT_SUBRESOURCE_LAYERS,
|
||||
.srcOffset = {
|
||||
.x = 0, .y = 0, .z = 0
|
||||
},
|
||||
.dstSubresource = vk::ImageSubresourceLayers{
|
||||
.aspectMask = vk::ImageAspectFlagBits::eColor,
|
||||
.mipLevel = 0,
|
||||
.baseArrayLayer = slot.getLayer(),
|
||||
.layerCount = 1
|
||||
},
|
||||
.dstOffset = {
|
||||
.x = slot.getUsedSpace().offset.x,
|
||||
.y = slot.getUsedSpace().offset.y,
|
||||
.z = 0
|
||||
},
|
||||
.extent = srcImage.getSize()
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
ObjectPtr<Image> AtlasedImage::allocateImage(unsigned layers)
|
||||
{
|
||||
ObjectPtr<Image> image = getOwner()->createChild<Image>(ImageCreationArgs{
|
||||
.format = mFormat,
|
||||
.extent = {
|
||||
.width = mAtlas->getLayerSize().width,
|
||||
.height = mAtlas->getLayerSize().height,
|
||||
.depth = 1
|
||||
},
|
||||
.mipLevels = mMipLevels,
|
||||
.arrayLayers = layers,
|
||||
.usage = mUsage
|
||||
});
|
||||
image->allocateMemory();
|
||||
return image;
|
||||
}
|
||||
} // namespace iwa
|
||||
29
source/util/vertex_layout.cpp
Normal file
29
source/util/vertex_layout.cpp
Normal file
@@ -0,0 +1,29 @@
|
||||
|
||||
#include "iwa/util/vertex_layout.hpp"
|
||||
|
||||
namespace iwa
|
||||
{
|
||||
mijin::Optional<VertexAttribute&> VertexLayout::findAttribute(VertexAttributeSemantic semantic, unsigned semanticIdx) noexcept
|
||||
{
|
||||
for (VertexAttribute& attribute : attributes)
|
||||
{
|
||||
if (attribute.semantic == semantic && attribute.semanticIdx == semanticIdx)
|
||||
{
|
||||
return attribute;
|
||||
}
|
||||
}
|
||||
return mijin::NULL_OPTIONAL;
|
||||
}
|
||||
|
||||
mijin::Optional<const VertexAttribute&> VertexLayout::findAttribute(VertexAttributeSemantic semantic, unsigned semanticIdx) const noexcept
|
||||
{
|
||||
for (const VertexAttribute& attribute : attributes)
|
||||
{
|
||||
if (attribute.semantic == semantic && attribute.semanticIdx == semanticIdx)
|
||||
{
|
||||
return attribute;
|
||||
}
|
||||
}
|
||||
return mijin::NULL_OPTIONAL;
|
||||
}
|
||||
} // namespace iwa
|
||||
275
source/util/vkutil.cpp
Normal file
275
source/util/vkutil.cpp
Normal file
@@ -0,0 +1,275 @@
|
||||
|
||||
#include "iwa/util/vkutil.hpp"
|
||||
|
||||
#include "iwa/device.hpp"
|
||||
#include "iwa/log.hpp"
|
||||
|
||||
namespace iwa
|
||||
{
|
||||
unsigned vkFormatSize(vk::Format format) noexcept
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
// 8 bit integer
|
||||
case vk::Format::eR8Uint:
|
||||
case vk::Format::eR8Sint:
|
||||
case vk::Format::eR8Unorm:
|
||||
case vk::Format::eR8Srgb:
|
||||
return 1;
|
||||
case vk::Format::eR8G8Uint:
|
||||
case vk::Format::eR8G8Sint:
|
||||
case vk::Format::eR8G8Unorm:
|
||||
case vk::Format::eR8G8Srgb:
|
||||
return 2;
|
||||
case vk::Format::eR8G8B8Uint:
|
||||
case vk::Format::eR8G8B8Sint:
|
||||
case vk::Format::eR8G8B8Unorm:
|
||||
case vk::Format::eR8G8B8Srgb:
|
||||
return 3;
|
||||
case vk::Format::eR8G8B8A8Uint:
|
||||
case vk::Format::eR8G8B8A8Sint:
|
||||
case vk::Format::eR8G8B8A8Unorm:
|
||||
case vk::Format::eR8G8B8A8Srgb:
|
||||
return 4;
|
||||
// 16 bit integer
|
||||
case vk::Format::eR16Uint:
|
||||
case vk::Format::eR16Sint:
|
||||
case vk::Format::eR16Unorm:
|
||||
return 2;
|
||||
case vk::Format::eR16G16Uint:
|
||||
case vk::Format::eR16G16Sint:
|
||||
case vk::Format::eR16G16Unorm:
|
||||
return 4;
|
||||
case vk::Format::eR16G16B16Uint:
|
||||
case vk::Format::eR16G16B16Sint:
|
||||
case vk::Format::eR16G16B16Unorm:
|
||||
return 6;
|
||||
case vk::Format::eR16G16B16A16Uint:
|
||||
case vk::Format::eR16G16B16A16Sint:
|
||||
case vk::Format::eR16G16B16A16Unorm:
|
||||
return 8;
|
||||
// 32 bit integer
|
||||
case vk::Format::eR32Uint:
|
||||
case vk::Format::eR32Sint:
|
||||
return 4;
|
||||
case vk::Format::eR32G32Uint:
|
||||
case vk::Format::eR32G32Sint:
|
||||
return 8;
|
||||
case vk::Format::eR32G32B32Uint:
|
||||
case vk::Format::eR32G32B32Sint:
|
||||
return 12;
|
||||
case vk::Format::eR32G32B32A32Uint:
|
||||
case vk::Format::eR32G32B32A32Sint:
|
||||
return 16;
|
||||
// 64 bit integer
|
||||
case vk::Format::eR64Uint:
|
||||
case vk::Format::eR64Sint:
|
||||
return 8;
|
||||
case vk::Format::eR64G64Uint:
|
||||
case vk::Format::eR64G64Sint:
|
||||
return 16;
|
||||
case vk::Format::eR64G64B64Uint:
|
||||
case vk::Format::eR64G64B64Sint:
|
||||
return 24;
|
||||
case vk::Format::eR64G64B64A64Uint:
|
||||
case vk::Format::eR64G64B64A64Sint:
|
||||
return 32;
|
||||
// 16 bit float
|
||||
case vk::Format::eR16Sfloat:
|
||||
return 2;
|
||||
case vk::Format::eR16G16Sfloat:
|
||||
return 4;
|
||||
case vk::Format::eR16G16B16Sfloat:
|
||||
return 6;
|
||||
case vk::Format::eR16G16B16A16Sfloat:
|
||||
return 8;
|
||||
// 32 bit float
|
||||
case vk::Format::eR32Sfloat:
|
||||
return 4;
|
||||
case vk::Format::eR32G32Sfloat:
|
||||
return 8;
|
||||
case vk::Format::eR32G32B32Sfloat:
|
||||
return 12;
|
||||
case vk::Format::eR32G32B32A32Sfloat:
|
||||
return 16;
|
||||
// 64 bit float
|
||||
case vk::Format::eR64Sfloat:
|
||||
return 8;
|
||||
case vk::Format::eR64G64Sfloat:
|
||||
return 16;
|
||||
case vk::Format::eR64G64B64Sfloat:
|
||||
return 24;
|
||||
case vk::Format::eR64G64B64A64Sfloat:
|
||||
return 32;
|
||||
default:
|
||||
logAndDie("I've never seen this format :(");
|
||||
}
|
||||
}
|
||||
|
||||
unsigned vkIndexTypeSize(vk::IndexType indexType) noexcept
|
||||
{
|
||||
switch (indexType)
|
||||
{
|
||||
case vk::IndexType::eNoneKHR:
|
||||
return 0;
|
||||
case vk::IndexType::eUint8EXT:
|
||||
return 1;
|
||||
case vk::IndexType::eUint16:
|
||||
return 2;
|
||||
case vk::IndexType::eUint32:
|
||||
return 4;
|
||||
default:
|
||||
logAndDie("What is this sorcery?");
|
||||
}
|
||||
}
|
||||
|
||||
bool isDepthFormat(vk::Format format) noexcept
|
||||
{
|
||||
for (const vk::Format depthFormat : DEPTH_FORMATS) {
|
||||
if (format == depthFormat) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isStencilFormat(vk::Format format) noexcept
|
||||
{
|
||||
for (const vk::Format stencilFormat : STENCIL_FORMATS) {
|
||||
if (format == stencilFormat) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#if 0
|
||||
std::string formatVkVariant(const VkVariantMWN& variant)
|
||||
{
|
||||
switch (variant.type)
|
||||
{
|
||||
case VK_VARIANT_TYPE_UNKNOWN_MWN:
|
||||
return "???";
|
||||
case VK_VARIANT_TYPE_NONE_MWN:
|
||||
return "<none>";
|
||||
case VK_VARIANT_TYPE_BOOL_MWN:
|
||||
return variant.uintValue ? "true" : "false";
|
||||
case VK_VARIANT_TYPE_UINT8_MWN:
|
||||
case VK_VARIANT_TYPE_UINT16_MWN:
|
||||
case VK_VARIANT_TYPE_UINT32_MWN:
|
||||
case VK_VARIANT_TYPE_UINT64_MWN:
|
||||
return std::to_string(variant.uintValue);
|
||||
case VK_VARIANT_TYPE_INT8_MWN:
|
||||
case VK_VARIANT_TYPE_INT16_MWN:
|
||||
case VK_VARIANT_TYPE_INT32_MWN:
|
||||
case VK_VARIANT_TYPE_INT64_MWN:
|
||||
return std::to_string(variant.intValue);
|
||||
case VK_VARIANT_TYPE_FLOAT_MWN:
|
||||
case VK_VARIANT_TYPE_DOUBLE_MWN:
|
||||
return std::to_string(variant.doubleValue);
|
||||
case VK_VARIANT_TYPE_STRING_MWN:
|
||||
return fmt::format("\"{}\"", variant.stringValue);
|
||||
case VK_VARIANT_TYPE_VOID_POINTER_MWN:
|
||||
return fmt::format("{}", fmt::ptr(variant.voidPointerValue)); // TODO: this doesnt make sense, store the original pointer!
|
||||
case VK_VARIANT_TYPE_POINTER_MWN:
|
||||
return fmt::format("{}", fmt::ptr(variant.pointerValue)); // TODO: this doesnt make sense, store the original pointer!
|
||||
case VK_VARIANT_TYPE_ARRAY_MWN:
|
||||
return fmt::format("<array of {}>", variant.arrayValue.numElements);
|
||||
case VK_VARIANT_TYPE_IN_STRUCTURE_MWN:
|
||||
return "<in struct>";
|
||||
case VK_VARIANT_TYPE_OUT_STRUCTURE_MWN:
|
||||
return "<out struct>";
|
||||
case VK_VARIANT_TYPE_OBJECT_MWN:
|
||||
return "<handle>";
|
||||
default:
|
||||
assert(0);
|
||||
return "???";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
std::size_t calcVkStructHash(const void* structure, std::size_t appendTo)
|
||||
{
|
||||
if (structure == nullptr) {
|
||||
return appendTo;
|
||||
}
|
||||
|
||||
const vk::BaseInStructure* inStruct = static_cast<const vk::BaseInStructure*>(structure);
|
||||
|
||||
std::size_t hash = appendTo;
|
||||
switch (inStruct->sType)
|
||||
{
|
||||
case vk::StructureType::eDescriptorSetLayoutBindingFlagsCreateInfo: {
|
||||
const auto& flagsInfo = *static_cast<const vk::DescriptorSetLayoutBindingFlagsCreateInfo*>(structure);
|
||||
for (std::uint32_t bindingIdx = 0; bindingIdx < flagsInfo.bindingCount; ++bindingIdx) {
|
||||
hash = calcCrcSizeAppend(flagsInfo.pBindingFlags[bindingIdx], hash);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
assert(false); // missing struct here, bad
|
||||
break;
|
||||
}
|
||||
|
||||
return calcVkStructHash(inStruct->pNext, hash);
|
||||
}
|
||||
#endif
|
||||
|
||||
vk::SampleCountFlagBits samplesToVk(unsigned samples) noexcept
|
||||
{
|
||||
switch (samples)
|
||||
{
|
||||
case 1:
|
||||
return vk::SampleCountFlagBits::e1;
|
||||
case 2:
|
||||
return vk::SampleCountFlagBits::e2;
|
||||
case 4:
|
||||
return vk::SampleCountFlagBits::e4;
|
||||
case 8:
|
||||
return vk::SampleCountFlagBits::e8;
|
||||
case 16:
|
||||
return vk::SampleCountFlagBits::e16;
|
||||
case 32:
|
||||
return vk::SampleCountFlagBits::e32;
|
||||
case 64:
|
||||
return vk::SampleCountFlagBits::e64;
|
||||
default:
|
||||
logAndDie("Invalid sample count: {}.", samples);
|
||||
}
|
||||
}
|
||||
|
||||
vk::Format detectDepthBufferFormat(Device& device, unsigned samples) noexcept
|
||||
{
|
||||
const vk::SampleCountFlagBits sampleCount = samplesToVk(samples);
|
||||
for (const vk::Format depthFormat : DEPTH_FORMATS)
|
||||
{
|
||||
try
|
||||
{
|
||||
const vk::ImageFormatProperties props = device.getVkPhysicalDevice().getImageFormatProperties(depthFormat, vk::ImageType::e2D, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eDepthStencilAttachment);
|
||||
if (props.sampleCounts & sampleCount) {
|
||||
return depthFormat;
|
||||
}
|
||||
}
|
||||
catch(vk::FormatNotSupportedError&)
|
||||
{
|
||||
continue; // not supported
|
||||
}
|
||||
}
|
||||
return vk::Format::eUndefined;
|
||||
}
|
||||
|
||||
std::vector<unsigned> detectSupportedSampleCounts(Device& device) noexcept
|
||||
{
|
||||
std::vector<unsigned> result = {1};
|
||||
|
||||
for (const unsigned samples : {2, 4, 8, 16, 32, 64})
|
||||
{
|
||||
if (detectDepthBufferFormat(device, samples) != vk::Format::eUndefined) {
|
||||
result.push_back(samples);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
} // namespace iwa
|
||||
319
source/window.cpp
Normal file
319
source/window.cpp
Normal file
@@ -0,0 +1,319 @@
|
||||
|
||||
#include "iwa/window.hpp"
|
||||
|
||||
#include <SDL_vulkan.h>
|
||||
#include "iwa/log.hpp"
|
||||
#include "iwa/instance.hpp"
|
||||
|
||||
namespace iwa
|
||||
{
|
||||
inline constexpr const char* WINDOW_DATA_NAME = "iwa_window";
|
||||
|
||||
namespace
|
||||
{
|
||||
Window* gLastEventWindow = nullptr;
|
||||
|
||||
Window* getWindowFromEvent(Uint32 windowID) noexcept
|
||||
{
|
||||
SDL_Window* sdlWindow = SDL_GetWindowFromID(windowID);
|
||||
Window* iwaWindow = static_cast<Window*>(SDL_GetWindowData(sdlWindow, WINDOW_DATA_NAME));
|
||||
|
||||
if (iwaWindow != nullptr)
|
||||
{
|
||||
gLastEventWindow = iwaWindow;
|
||||
}
|
||||
return gLastEventWindow;
|
||||
}
|
||||
|
||||
void handleWindowEvent(const SDL_Event& event)
|
||||
{
|
||||
Window* iwaWindow = getWindowFromEvent(event.window.windowID);
|
||||
if (iwaWindow == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event.window.event)
|
||||
{
|
||||
case SDL_WINDOWEVENT_FOCUS_GAINED:
|
||||
iwaWindow->focusGained.emit();
|
||||
break;
|
||||
case SDL_WINDOWEVENT_FOCUS_LOST:
|
||||
iwaWindow->focusLost.emit();
|
||||
break;
|
||||
case SDL_WINDOWEVENT_ENTER:
|
||||
iwaWindow->mouseEntered.emit();
|
||||
break;
|
||||
case SDL_WINDOWEVENT_LEAVE:
|
||||
iwaWindow->mouseLeft.emit();
|
||||
break;
|
||||
case SDL_WINDOWEVENT_CLOSE:
|
||||
iwaWindow->closeRequested.emit();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void handleKeyEvent(const SDL_Event& sdlEvent)
|
||||
{
|
||||
Window* iwaWindow = getWindowFromEvent(sdlEvent.key.windowID);
|
||||
if (iwaWindow == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const KeyEvent event = {
|
||||
.keyCode = static_cast<KeyCode>(sdlEvent.key.keysym.sym),
|
||||
.scanCode = static_cast<ScanCode>(sdlEvent.key.keysym.scancode),
|
||||
.modifiers = {
|
||||
.leftShift = (sdlEvent.key.keysym.mod & KMOD_LSHIFT) != 0,
|
||||
.rightShift = (sdlEvent.key.keysym.mod & KMOD_RSHIFT) != 0,
|
||||
.leftCtrl = (sdlEvent.key.keysym.mod & KMOD_LCTRL) != 0,
|
||||
.rightCtrl = (sdlEvent.key.keysym.mod & KMOD_RCTRL) != 0,
|
||||
.leftAlt = (sdlEvent.key.keysym.mod & KMOD_LALT) != 0,
|
||||
.rightAlt = (sdlEvent.key.keysym.mod & KMOD_RALT) != 0,
|
||||
.leftMeta = (sdlEvent.key.keysym.mod & KMOD_LGUI) != 0,
|
||||
.rightMeta = (sdlEvent.key.keysym.mod & KMOD_RGUI) != 0,
|
||||
},
|
||||
.down = sdlEvent.type == SDL_KEYDOWN,
|
||||
.repeat = sdlEvent.key.repeat > 0
|
||||
};
|
||||
iwaWindow->keyChanged.emit(event);
|
||||
}
|
||||
|
||||
void handleMouseMotion(const SDL_Event& sdlEvent)
|
||||
{
|
||||
Window* iwaWindow = getWindowFromEvent(sdlEvent.motion.windowID);
|
||||
if (iwaWindow == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const MouseMoveEvent event = {
|
||||
.relativeX = sdlEvent.motion.xrel,
|
||||
.relativeY = sdlEvent.motion.yrel,
|
||||
.absoluteX = sdlEvent.motion.x,
|
||||
.absoluteY = sdlEvent.motion.y,
|
||||
.warped = false // TODO?
|
||||
};
|
||||
iwaWindow->mouseMoved.emit(event);
|
||||
}
|
||||
|
||||
void handleMouseButtonEvent(const SDL_Event& sdlEvent)
|
||||
{
|
||||
Window* iwaWindow = getWindowFromEvent(sdlEvent.button.windowID);
|
||||
if (iwaWindow == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const MouseButtonEvent event = {
|
||||
.button = static_cast<MouseButton>(sdlEvent.button.button),
|
||||
.clicks = sdlEvent.button.clicks,
|
||||
.down = sdlEvent.type == SDL_MOUSEBUTTONDOWN
|
||||
};
|
||||
iwaWindow->mouseButtonChanged.emit(event);
|
||||
}
|
||||
|
||||
void handleMouseWheelEvent(const SDL_Event& sdlEvent)
|
||||
{
|
||||
Window* iwaWindow = getWindowFromEvent(sdlEvent.wheel.windowID);
|
||||
if (iwaWindow == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const MouseWheelEvent event = {
|
||||
.relativeX = sdlEvent.wheel.x,
|
||||
.relativeY = sdlEvent.wheel.y
|
||||
};
|
||||
iwaWindow->mouseScrolled.emit(event);
|
||||
}
|
||||
|
||||
void handleTextInputEvent(const SDL_Event& sdlEvent)
|
||||
{
|
||||
Window* iwaWindow = getWindowFromEvent(sdlEvent.text.windowID);
|
||||
if (iwaWindow == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const TextInputEvent event = {
|
||||
.text = sdlEvent.text.text
|
||||
};
|
||||
iwaWindow->textEntered.emit(event);
|
||||
}
|
||||
|
||||
void handleEvent(const SDL_Event& event)
|
||||
{
|
||||
switch (event.type)
|
||||
{
|
||||
case SDL_WINDOWEVENT:
|
||||
handleWindowEvent(event);
|
||||
break;
|
||||
case SDL_KEYDOWN:
|
||||
case SDL_KEYUP:
|
||||
handleKeyEvent(event);
|
||||
break;
|
||||
case SDL_MOUSEMOTION:
|
||||
handleMouseMotion(event);
|
||||
break;
|
||||
case SDL_MOUSEBUTTONDOWN:
|
||||
case SDL_MOUSEBUTTONUP:
|
||||
handleMouseButtonEvent(event);
|
||||
break;
|
||||
case SDL_MOUSEWHEEL:
|
||||
handleMouseWheelEvent(event);
|
||||
break;
|
||||
case SDL_TEXTINPUT:
|
||||
handleTextInputEvent(event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
mijin::Task<> c_sdlLoop(ObjectPtr<Instance> instance)
|
||||
{
|
||||
while (!instance->isQuitRequested())
|
||||
{
|
||||
SDL_Event event;
|
||||
while (SDL_PollEvent(&event))
|
||||
{
|
||||
handleEvent(event);
|
||||
}
|
||||
co_await mijin::c_suspend();
|
||||
}
|
||||
// SDL_Quit();
|
||||
co_return;
|
||||
}
|
||||
|
||||
void initSDL(Instance& instance)
|
||||
{
|
||||
static bool sdlInited = false;
|
||||
if (sdlInited) {
|
||||
return;
|
||||
}
|
||||
sdlInited = true;
|
||||
if (SDL_Init(0) != 0)
|
||||
{
|
||||
logAndDie("Error initializing SDL: {}.", SDL_GetError());
|
||||
}
|
||||
SDL_SetHint(SDL_HINT_MOUSE_AUTO_CAPTURE, "0");
|
||||
instance.getMainTaskLoop().addTask(c_sdlLoop(instance.getPointer()));
|
||||
}
|
||||
}
|
||||
|
||||
Window::Window(ObjectPtr<Instance> owner, const WindowCreationArgs& args) : super_t(std::move(owner))
|
||||
{
|
||||
initSDL(*getOwner());
|
||||
const Uint32 flags = SDL_WINDOW_VULKAN
|
||||
| (args.flags.hidden ? SDL_WINDOW_HIDDEN : 0)
|
||||
| (args.flags.resizable ? SDL_WINDOW_RESIZABLE : 0)
|
||||
| (args.flags.borderless ? SDL_WINDOW_BORDERLESS : 0)
|
||||
| (args.flags.alwayOnTop ? SDL_WINDOW_ALWAYS_ON_TOP : 0)
|
||||
| (args.flags.skipTaskbar ? SDL_WINDOW_UTILITY : 0);
|
||||
mHandle = SDL_CreateWindow(
|
||||
/* title = */ args.title.c_str(),
|
||||
/* x = */ SDL_WINDOWPOS_CENTERED,
|
||||
/* y = */ SDL_WINDOWPOS_CENTERED,
|
||||
/* w = */ args.width,
|
||||
/* h = */ args.height,
|
||||
/* flags = */ flags
|
||||
);
|
||||
if (mHandle == nullptr)
|
||||
{
|
||||
logAndDie("Error creating SDL window: {}.", SDL_GetError());
|
||||
}
|
||||
SDL_SetWindowData(mHandle, WINDOW_DATA_NAME, this);
|
||||
|
||||
VkSurfaceKHR surface = VK_NULL_HANDLE;
|
||||
if (!SDL_Vulkan_CreateSurface(mHandle, getOwner()->getVkHandle(), &surface))
|
||||
{
|
||||
logAndDie("Error creating Vulkan surface for SDL window: {}", SDL_GetError());
|
||||
}
|
||||
mSurface = surface;
|
||||
getOwner()->windowCreated.emit(*this);
|
||||
}
|
||||
|
||||
Window::~Window() noexcept
|
||||
{
|
||||
getOwner()->queueDelete([surface=mSurface, handle=mHandle, instance= getOwner()]
|
||||
{
|
||||
if (surface)
|
||||
{
|
||||
instance->getVkHandle().destroySurfaceKHR(surface);
|
||||
}
|
||||
if (handle)
|
||||
{
|
||||
SDL_DestroyWindow(handle);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool Window::isVisible() const noexcept
|
||||
{
|
||||
return (SDL_GetWindowFlags(mHandle) & SDL_WINDOW_HIDDEN) == 0;
|
||||
}
|
||||
|
||||
void Window::setVisible(bool visible) noexcept
|
||||
{
|
||||
if (visible) {
|
||||
SDL_ShowWindow(mHandle);
|
||||
}
|
||||
else {
|
||||
SDL_HideWindow(mHandle);
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<int, int> Window::getSize() const noexcept
|
||||
{
|
||||
std::pair<int, int> size;
|
||||
SDL_GetWindowSize(mHandle, &size.first, &size.second);
|
||||
return size;
|
||||
}
|
||||
|
||||
void Window::setSize(int width, int height) noexcept
|
||||
{
|
||||
SDL_SetWindowSize(mHandle, std::max(width, 1), std::max(height, 1));
|
||||
}
|
||||
|
||||
std::pair<int, int> Window::getPosition() const noexcept
|
||||
{
|
||||
std::pair<int, int> position;
|
||||
SDL_GetWindowPosition(mHandle, &position.first, &position.second);
|
||||
return position;
|
||||
}
|
||||
|
||||
void Window::setPosition(int xPos, int yPos) noexcept
|
||||
{
|
||||
SDL_SetWindowPosition(mHandle, xPos, yPos);
|
||||
}
|
||||
|
||||
WindowBorder Window::getWindowBorder() const noexcept
|
||||
{
|
||||
WindowBorder windowBorder;
|
||||
SDL_GetWindowBordersSize(mHandle, &windowBorder.top, &windowBorder.left, &windowBorder.bottom, &windowBorder.right);
|
||||
return windowBorder;
|
||||
}
|
||||
|
||||
bool Window::isFocused() const noexcept
|
||||
{
|
||||
return (SDL_GetWindowFlags(mHandle) & SDL_WINDOW_INPUT_FOCUS) != 0;
|
||||
}
|
||||
|
||||
void Window::focus() noexcept
|
||||
{
|
||||
SDL_RaiseWindow(mHandle);
|
||||
}
|
||||
|
||||
void Window::setMouseMode(MouseMode mouseMode) noexcept
|
||||
{
|
||||
switch (mouseMode)
|
||||
{
|
||||
case MouseMode::NORMAL:
|
||||
SDL_SetRelativeMouseMode(SDL_FALSE);
|
||||
SDL_SetWindowMouseGrab(mHandle, SDL_FALSE);
|
||||
break;
|
||||
case MouseMode::CAPTURED:
|
||||
SDL_SetRelativeMouseMode(SDL_TRUE);
|
||||
SDL_SetWindowMouseGrab(mHandle, SDL_TRUE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Window::setModalFor(mijin::Optional<const Window&> parent) noexcept
|
||||
{
|
||||
SDL_SetWindowModalFor(mHandle, parent.empty() ? nullptr : parent->getSDLWindow());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user