Compare commits

...

12 Commits

Author SHA1 Message Date
92912a321c Initial Wayland support
Signed-off-by: Slendi <slendi@socopon.com>
2026-01-27 00:55:28 +02:00
e04f1cf291 Add useful wayland RAII wrappers
Signed-off-by: Slendi <slendi@socopon.com>
2026-01-26 18:27:27 +02:00
efa6e289b6 Add logging for KMS backend
Signed-off-by: Slendi <slendi@socopon.com>
2026-01-17 15:05:25 +02:00
596af80622 Hands and formatting
Signed-off-by: Slendi <slendi@socopon.com>
2026-01-17 14:55:15 +02:00
f4fad2c1ac Hand tracking
Signed-off-by: Slendi <slendi@socopon.com>
2026-01-17 14:40:34 +02:00
e9ae017e9b Make OpenXR work!
Signed-off-by: Slendi <slendi@socopon.com>
2026-01-17 12:42:23 +02:00
cddfa30cfe Add openxr related stuff
Signed-off-by: Slendi <slendi@socopon.com>
2026-01-17 12:36:41 +02:00
5ca02ed9e2 Add untested OpenXR
Signed-off-by: Slendi <slendi@socopon.com>
2026-01-12 19:36:25 +02:00
9f2dab344d DRM/KMS backebd
Signed-off-by: Slendi <slendi@socopon.com>
2026-01-12 16:38:28 +02:00
402cdd43da Make the constructor and destructor of Application private
Application is a singleton now, no need to have it be constructed
outside of the().

Signed-off-by: Slendi <slendi@socopon.com>
2026-01-11 17:45:10 +02:00
fc66ce2fd3 Make Application into a singleton
Signed-off-by: Slendi <slendi@socopon.com>
2026-01-11 17:44:02 +02:00
46f5fab55e Fix initializers, more stuff
Signed-off-by: Slendi <slendi@socopon.com>
2026-01-11 16:55:10 +02:00
35 changed files with 9139 additions and 420 deletions

View File

@@ -23,6 +23,7 @@
pkg-config pkg-config
glslang glslang
shaderc shaderc
wayland-scanner
]; ];
buildInputs = with pkgs; [ buildInputs = with pkgs; [
vulkan-loader vulkan-loader

View File

@@ -1,4 +1,4 @@
project('vr-compositor', 'cpp', project('vr-compositor', 'c', 'cpp',
version: '0.1', version: '0.1',
default_options: [ default_options: [
'cpp_std=c++26', 'cpp_std=c++26',
@@ -17,14 +17,32 @@ fastgltf_opts.set_override_option('werror', 'false')
fastgltf = cmake.subproject('fastgltf', options: fastgltf_opts) fastgltf = cmake.subproject('fastgltf', options: fastgltf_opts)
cc = meson.get_compiler('cpp') cc = meson.get_compiler('cpp')
wl_mod = import('wayland')
wayland_dep = dependency('wayland-server', include_type: 'system') wayland_dep = dependency('wayland-server', include_type: 'system')
wayland_client_dep = dependency('wayland-client', include_type: 'system')
vulkan_dep = dependency('vulkan', include_type: 'system') vulkan_dep = dependency('vulkan', include_type: 'system')
openxr_dep = dependency('openxr', include_type: 'system') openxr_dep = dependency('openxr', include_type: 'system')
zlib_dep = dependency('zlib', include_type: 'system') zlib_dep = dependency('zlib', include_type: 'system')
sdl3_dep = dependency('sdl3', include_type: 'system') sdl3_dep = dependency('sdl3', include_type: 'system')
libinput_dep = dependency('libinput', include_type: 'system') libinput_dep = dependency('libinput', include_type: 'system')
libudev_dep = dependency('libudev', include_type: 'system') libudev_dep = dependency('libudev', include_type: 'system')
wayland_protocol = wl_mod.scan_xml(
'protocols/wayland.xml',
client: false,
server: true,
)
wayland_protocol_source = wayland_protocol[0]
wayland_server_header = wayland_protocol[1]
xdg_shell_protocol = wl_mod.scan_xml(
'protocols/xdg-shell.xml',
client: true,
server: true,
)
xdg_shell_protocol_source = xdg_shell_protocol[0]
xdg_shell_client_header = xdg_shell_protocol[1]
xdg_shell_server_header = xdg_shell_protocol[2]
imgui_src = files( imgui_src = files(
'thirdparty/imgui/imgui.cpp', 'thirdparty/imgui/imgui.cpp',
'thirdparty/imgui/imgui_draw.cpp', 'thirdparty/imgui/imgui_draw.cpp',
@@ -161,12 +179,24 @@ exe = executable('vr-compositor',
'src/Loader.cpp', 'src/Loader.cpp',
'src/DescriptorWriter.cpp', 'src/DescriptorWriter.cpp',
'src/CPUTexture.cpp', 'src/CPUTexture.cpp',
'src/wayland/WaylandServer.cpp',
'src/wayland/Surface.cpp',
'src/wayland/Shm.cpp',
'src/wayland/protocols/CompositorProtocol.cpp',
'src/wayland/protocols/ShmProtocol.cpp',
'src/wayland/protocols/XdgShellProtocol.cpp',
'src/Skybox.cpp', 'src/Skybox.cpp',
'src/VulkanRenderer.cpp', 'src/VulkanRenderer.cpp',
'src/Application.cpp', 'src/Application.cpp',
wayland_protocol_source,
xdg_shell_protocol_source,
wayland_server_header,
xdg_shell_server_header,
], ],
c_args: ['-Wno-missing-variable-declarations'],
include_directories: [ include_directories: [
vkbootstrap_inc, vkbootstrap_inc,
include_directories('.'),
imgui_inc, imgui_inc,
'thirdparty/smath/include' 'thirdparty/smath/include'
], ],
@@ -176,3 +206,11 @@ exe = executable('vr-compositor',
'--embed-dir=' + join_paths(meson.project_build_root(), 'shaders'), '--embed-dir=' + join_paths(meson.project_build_root(), 'shaders'),
], ],
) )
executable('shm-life',
'tools/shm_life.cpp',
dependencies: [wayland_client_dep],
sources: [xdg_shell_protocol_source, xdg_shell_client_header],
c_args: ['-Wno-missing-variable-declarations'],
cpp_args: ['-Wno-cast-qual'],
)

3314
protocols/wayland.xml Normal file

File diff suppressed because it is too large Load Diff

1418
protocols/xdg-shell.xml Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -3,16 +3,22 @@
#include <array> #include <array>
#include <cstdint> #include <cstdint>
#include <filesystem> #include <filesystem>
#include <functional>
#include <memory> #include <memory>
#include <vector>
#include <SDL3/SDL_video.h> #include <SDL3/SDL_video.h>
#include <imgui.h> #include <imgui.h>
#include <linux/input-event-codes.h> #include <linux/input-event-codes.h>
#include <openxr/openxr.h>
#include "smath.hpp"
#include "Loader.h" #include "Loader.h"
#include "Logger.h" #include "Logger.h"
#include "Skybox.h" #include "Skybox.h"
#include "Types.h" #include "Types.h"
#include "wayland/Display.h"
struct libinput; struct libinput;
struct libinput_event_keyboard; struct libinput_event_keyboard;
@@ -22,29 +28,63 @@ struct udev;
namespace Lunar { namespace Lunar {
struct VulkanRenderer; struct VulkanRenderer;
struct OpenXrState;
namespace Wayland {
struct WaylandServer;
}
struct Application { struct Application {
Application();
~Application();
auto run() -> void; auto run() -> void;
auto binary_directory() const -> std::filesystem::path; auto binary_directory() const -> std::filesystem::path;
auto mouse_captured(bool new_state) -> void; auto mouse_captured(bool new_state) -> void;
auto mouse_captured() const -> bool { return m_mouse_captured; } auto mouse_captured() const -> bool { return m_mouse_captured; }
auto toggle_mouse_captured() -> void { mouse_captured(!m_mouse_captured); } auto toggle_mouse_captured() -> void { mouse_captured(!m_mouse_captured); }
auto is_key_down(uint32_t key) const -> bool;
auto is_key_up(uint32_t key) const -> bool;
auto is_key_pressed(uint32_t key) const -> bool; auto is_key_pressed(uint32_t key) const -> bool;
auto is_key_released(uint32_t key) const -> bool;
static auto the() -> Application &;
private: private:
enum class Backend {
SDL,
KMS,
};
Application();
~Application();
auto init_input() -> void; auto init_input() -> void;
auto init_test_meshes() -> void; auto init_test_meshes() -> void;
auto init_wayland() -> void;
auto asset_directory() -> std::filesystem::path; auto asset_directory() -> std::filesystem::path;
auto shutdown_input() -> void; auto shutdown_input() -> void;
auto process_libinput_events() -> void; auto process_libinput_events() -> void;
auto handle_keyboard_event(libinput_event_keyboard *event) -> void; auto handle_keyboard_event(libinput_event_keyboard *event) -> void;
auto handle_pointer_motion(libinput_event_pointer *event) -> void;
auto handle_pointer_button(libinput_event_pointer *event) -> void;
auto handle_pointer_axis(libinput_event_pointer *event) -> void;
auto handle_pointer_frame() -> void;
auto handle_pointer_end_frame() -> void;
auto handle_pointer_cancel() -> void;
auto handle_keyboard_key(std::optional<uint32_t> key, bool pressed) -> void;
auto clamp_mouse_to_window(int width, int height) -> void; auto clamp_mouse_to_window(int width, int height) -> void;
auto init_openxr() -> void;
auto init_openxr_session() -> void;
auto shutdown_openxr() -> void;
auto poll_openxr_events() -> void;
auto render_openxr_frame(
std::function<void(VulkanRenderer::GL &)> const &record,
float dt_seconds) -> bool;
auto update_camera_from_xr_view(XrView const &view) -> void;
auto update_hands(XrTime display_time) -> void;
auto render_hands(
VulkanRenderer::GL &gl, smath::Mat4 const &view_projection) -> void;
SDL_Window *m_window { nullptr }; SDL_Window *m_window { nullptr };
Backend m_backend { Backend::SDL };
Logger m_logger { "Lunar" }; Logger m_logger { "Lunar" };
std::unique_ptr<VulkanRenderer> m_renderer; std::unique_ptr<VulkanRenderer> m_renderer;
Skybox m_skybox; Skybox m_skybox;
@@ -53,9 +93,13 @@ private:
udev *m_udev { nullptr }; udev *m_udev { nullptr };
libinput *m_libinput { nullptr }; libinput *m_libinput { nullptr };
std::unique_ptr<OpenXrState> m_openxr {};
std::unique_ptr<Wayland::WaylandServer> m_wayland {};
bool m_running { true }; bool m_running { true };
bool m_mouse_captured { false }; bool m_mouse_captured { false };
bool m_show_imgui { false }; bool m_show_imgui { false };
bool m_window_focused { true };
int m_ctrl_pressed_count { 0 }; int m_ctrl_pressed_count { 0 };
std::uint32_t m_screenshot_index { 0 }; std::uint32_t m_screenshot_index { 0 };
@@ -66,9 +110,17 @@ private:
float m_mouse_sensitivity { 0.001f }; float m_mouse_sensitivity { 0.001f };
std::array<bool, KEY_MAX + 1> m_key_state {}; std::array<bool, KEY_MAX + 1> m_key_state {};
std::array<bool, KEY_MAX + 1> m_key_state_previous {};
Camera m_camera; Camera m_camera;
PolarCoordinate m_cursor; PolarCoordinate m_cursor;
static inline std::array<smath::Vec3, XR_HAND_JOINT_COUNT_EXT>
m_left_joints {};
static inline std::array<smath::Vec3, XR_HAND_JOINT_COUNT_EXT>
m_right_joints {};
static inline bool m_left_hand_valid { false };
static inline bool m_right_hand_valid { false };
}; };
} // namespace Lunar } // namespace Lunar

View File

@@ -59,11 +59,11 @@ namespace Lunar {
CPUTexture::CPUTexture(std::filesystem::path const &path) CPUTexture::CPUTexture(std::filesystem::path const &path)
{ {
int width_out = 0; int width_out { 0 };
int height_out = 0; int height_out { 0 };
int channels_out = 0; int channels_out { 0 };
stbi_uc *data = stbi_load(path.string().c_str(), &width_out, &height_out, stbi_uc *data { stbi_load(path.string().c_str(), &width_out, &height_out,
&channels_out, STBI_rgb_alpha); &channels_out, STBI_rgb_alpha) };
if (!data) { if (!data) {
throw std::runtime_error( throw std::runtime_error(
std::format("Failed to load texture: {}", path.string())); std::format("Failed to load texture: {}", path.string()));
@@ -76,4 +76,13 @@ CPUTexture::CPUTexture(std::filesystem::path const &path)
stbi_image_free(data); stbi_image_free(data);
} }
CPUTexture::CPUTexture(std::vector<uint8_t> pixels, uint32_t width,
uint32_t height, vk::Format format)
: pixels(std::move(pixels))
, width(width)
, height(height)
, format(format)
{
}
} // namespace Lunar } // namespace Lunar

View File

@@ -15,6 +15,8 @@ struct CPUTexture {
vk::Format format { vk::Format::eR8G8B8A8Unorm }; vk::Format format { vk::Format::eR8G8B8A8Unorm };
explicit CPUTexture(std::filesystem::path const &path); explicit CPUTexture(std::filesystem::path const &path);
CPUTexture(std::vector<uint8_t> pixels, uint32_t width, uint32_t height,
vk::Format format);
}; };
} // namespace Lunar } // namespace Lunar

View File

@@ -78,7 +78,7 @@ auto DescriptorAllocatorGrowable::destroy_pools(VkDevice dev) -> void
auto DescriptorAllocatorGrowable::allocate(Logger &logger, VkDevice dev, auto DescriptorAllocatorGrowable::allocate(Logger &logger, VkDevice dev,
VkDescriptorSetLayout layout, void *p_next) -> VkDescriptorSet VkDescriptorSetLayout layout, void *p_next) -> VkDescriptorSet
{ {
auto pool_to_use = get_pool(dev); auto pool_to_use { get_pool(dev) };
VkDescriptorSetAllocateInfo alloci {}; VkDescriptorSetAllocateInfo alloci {};
alloci.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; alloci.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;

View File

@@ -61,7 +61,7 @@ auto Mesh::load_gltf_meshes(
{ {
renderer.logger().debug("Loading GLTF from file: {}", path); renderer.logger().debug("Loading GLTF from file: {}", path);
auto data = fastgltf::GltfDataBuffer::FromPath(path); auto data { fastgltf::GltfDataBuffer::FromPath(path) };
if (data.error() != fastgltf::Error::None) { if (data.error() != fastgltf::Error::None) {
renderer.logger().err("Failed to open glTF file: {} (error {})", path, renderer.logger().err("Failed to open glTF file: {} (error {})", path,
fastgltf::to_underlying(data.error())); fastgltf::to_underlying(data.error()));
@@ -98,7 +98,7 @@ auto Mesh::load_gltf_meshes(
new_surface.count = static_cast<uint32_t>( new_surface.count = static_cast<uint32_t>(
gltf.accessors[p.indicesAccessor.value()].count); gltf.accessors[p.indicesAccessor.value()].count);
size_t initial_vertex = vertices.size(); size_t initial_vertex { vertices.size() };
{ // Indices { // Indices
auto &accessor = gltf.accessors[p.indicesAccessor.value()]; auto &accessor = gltf.accessors[p.indicesAccessor.value()];
@@ -128,7 +128,7 @@ auto Mesh::load_gltf_meshes(
if (auto attr = p.findAttribute("NORMAL")) { // Normals if (auto attr = p.findAttribute("NORMAL")) { // Normals
auto &accessor = gltf.accessors[attr->accessorIndex]; auto &accessor = gltf.accessors[attr->accessorIndex];
size_t local_index = 0; size_t local_index { 0 };
for (auto normal : for (auto normal :
fastgltf::iterateAccessor<smath::Vec3>(gltf, accessor)) { fastgltf::iterateAccessor<smath::Vec3>(gltf, accessor)) {
vertices[initial_vertex + local_index].normal = normal; vertices[initial_vertex + local_index].normal = normal;
@@ -138,7 +138,7 @@ auto Mesh::load_gltf_meshes(
if (auto attr = p.findAttribute("TEXCOORD_0")) { // UVs if (auto attr = p.findAttribute("TEXCOORD_0")) { // UVs
auto &accessor = gltf.accessors[attr->accessorIndex]; auto &accessor = gltf.accessors[attr->accessorIndex];
size_t local_index = 0; size_t local_index { 0 };
for (auto uv : for (auto uv :
fastgltf::iterateAccessor<smath::Vec2>(gltf, accessor)) { fastgltf::iterateAccessor<smath::Vec2>(gltf, accessor)) {
uv.unpack(vertices[initial_vertex + local_index].u, uv.unpack(vertices[initial_vertex + local_index].u,
@@ -149,7 +149,7 @@ auto Mesh::load_gltf_meshes(
if (auto attr = p.findAttribute("COLOR_0")) { // Colors if (auto attr = p.findAttribute("COLOR_0")) { // Colors
auto &accessor = gltf.accessors[attr->accessorIndex]; auto &accessor = gltf.accessors[attr->accessorIndex];
size_t local_index = 0; size_t local_index { 0 };
switch (accessor.type) { switch (accessor.type) {
case fastgltf::AccessorType::Vec3: { case fastgltf::AccessorType::Vec3: {

View File

@@ -34,7 +34,7 @@
static std::filesystem::path get_log_path(std::string_view app_name) static std::filesystem::path get_log_path(std::string_view app_name)
{ {
#ifdef _WIN32 #ifdef _WIN32
PWSTR path = nullptr; PWSTR path { nullptr };
SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &path); SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &path);
std::wstring wpath(path); std::wstring wpath(path);
CoTaskMemFree(path); CoTaskMemFree(path);
@@ -70,7 +70,7 @@ static int compress_file(std::filesystem::path const &input_path,
std::vector<char> buffer(chunk_size); std::vector<char> buffer(chunk_size);
while (in) { while (in) {
in.read(buffer.data(), static_cast<std::streamsize>(buffer.size())); in.read(buffer.data(), static_cast<std::streamsize>(buffer.size()));
std::streamsize bytes = in.gcount(); std::streamsize bytes { in.gcount() };
if (bytes > 0) if (bytes > 0)
gzwrite(out, buffer.data(), static_cast<unsigned int>(bytes)); gzwrite(out, buffer.data(), static_cast<unsigned int>(bytes));
} }
@@ -99,20 +99,20 @@ Logger::Logger(std::string_view app_name)
if (!file.is_regular_file()) if (!file.is_regular_file())
continue; continue;
auto name = file.path().filename().stem().string(); auto name { file.path().filename().stem().string() };
constexpr std::string_view prefix = "log_"; constexpr std::string_view prefix = "log_";
if (name.rfind(prefix, 0) != 0) { if (name.rfind(prefix, 0) != 0) {
continue; continue;
} }
int v = std::stoi(name.substr(prefix.size())); int v { std::stoi(name.substr(prefix.size())) };
if (v > max) if (v > max)
max = v; max = v;
auto ext = file.path().filename().extension().string(); auto ext { file.path().filename().extension().string() };
if (ext == ".txt") { if (ext == ".txt") {
auto np = file.path(); auto np { file.path() };
np.replace_extension(ext + ".gz"); np.replace_extension(ext + ".gz");
compress_file(file.path(), np); compress_file(file.path(), np);
} }
@@ -153,7 +153,7 @@ static std::string get_current_time_string()
void Logger::log(Level level, std::string_view msg) void Logger::log(Level level, std::string_view msg)
{ {
auto time_str = get_current_time_string(); auto time_str { get_current_time_string() };
std::string level_str; std::string level_str;
switch (level) { switch (level) {
case Logger::Level::Debug: case Logger::Level::Debug:

View File

@@ -27,13 +27,13 @@ auto Pipeline::Builder::set_push_constant_ranges(
auto Pipeline::Builder::build_compute( auto Pipeline::Builder::build_compute(
vk::PipelineShaderStageCreateInfo const &stage) -> Pipeline vk::PipelineShaderStageCreateInfo const &stage) -> Pipeline
{ {
auto pipeline_layout = build_layout(); auto pipeline_layout { build_layout() };
vk::ComputePipelineCreateInfo pipeline_ci {}; vk::ComputePipelineCreateInfo pipeline_ci {};
pipeline_ci.layout = pipeline_layout.get(); pipeline_ci.layout = pipeline_layout.get();
pipeline_ci.stage = stage; pipeline_ci.stage = stage;
auto pipeline_ret = m_device.createComputePipelineUnique({}, pipeline_ci); auto pipeline_ret { m_device.createComputePipelineUnique({}, pipeline_ci) };
VK_CHECK(m_logger, pipeline_ret.result); VK_CHECK(m_logger, pipeline_ret.result);
return Pipeline { return Pipeline {
@@ -46,14 +46,14 @@ auto Pipeline::Builder::build_graphics(
std::function<GraphicsPipelineBuilder &(GraphicsPipelineBuilder &)> const std::function<GraphicsPipelineBuilder &(GraphicsPipelineBuilder &)> const
&configure) -> Pipeline &configure) -> Pipeline
{ {
auto pipeline_layout = build_layout(); auto pipeline_layout { build_layout() };
auto builder = GraphicsPipelineBuilder { m_logger }; auto builder { GraphicsPipelineBuilder { m_logger } };
builder.set_pipeline_layout( builder.set_pipeline_layout(
static_cast<VkPipelineLayout>(pipeline_layout.get())); static_cast<VkPipelineLayout>(pipeline_layout.get()));
configure(builder); configure(builder);
auto pipeline_handle = builder.build(static_cast<VkDevice>(m_device)); auto pipeline_handle { builder.build(static_cast<VkDevice>(m_device)) };
vk::UniquePipeline pipeline_unique(pipeline_handle, vk::UniquePipeline pipeline_unique(pipeline_handle,
vk::detail::ObjectDestroy<vk::Device, vk::detail::ObjectDestroy<vk::Device,
VULKAN_HPP_DEFAULT_DISPATCHER_TYPE>(m_device)); VULKAN_HPP_DEFAULT_DISPATCHER_TYPE>(m_device));

View File

@@ -23,7 +23,7 @@ struct FaceOffset {
uint32_t y; uint32_t y;
}; };
constexpr std::array<FaceOffset, 6> kCrossOffsets { constexpr std::array<FaceOffset, 6> CROSS_OFFSETS {
FaceOffset { 2, 1 }, // +X FaceOffset { 2, 1 }, // +X
FaceOffset { 0, 1 }, // -X FaceOffset { 0, 1 }, // -X
FaceOffset { 1, 0 }, // +Y FaceOffset { 1, 0 }, // +Y
@@ -34,6 +34,83 @@ constexpr std::array<FaceOffset, 6> kCrossOffsets {
} // namespace } // namespace
auto Skybox::rebuild_pipeline(VulkanRenderer &renderer) -> bool
{
Pipeline::Builder pipeline_builder { renderer.device(), renderer.logger() };
uint8_t skybox_vert_shader_data[] {
#embed "skybox_vert.spv"
};
auto skybox_vert_shader
= vkutil::load_shader_module(std::span<uint8_t>(skybox_vert_shader_data,
sizeof(skybox_vert_shader_data)),
renderer.device());
if (!skybox_vert_shader) {
renderer.logger().err("Failed to load skybox vert shader");
return false;
}
uint8_t skybox_frag_shader_data[] {
#embed "skybox_frag.spv"
};
auto skybox_frag_shader
= vkutil::load_shader_module(std::span<uint8_t>(skybox_frag_shader_data,
sizeof(skybox_frag_shader_data)),
renderer.device());
if (!skybox_frag_shader) {
renderer.logger().err("Failed to load skybox frag shader");
return false;
}
vk::PushConstantRange push_constant_range {};
push_constant_range.stageFlags = vk::ShaderStageFlagBits::eVertex;
push_constant_range.offset = 0;
push_constant_range.size = sizeof(SkyboxPushConstants);
std::array push_constant_ranges { push_constant_range };
pipeline_builder.set_push_constant_ranges(push_constant_ranges);
std::array descriptor_set_layouts {
renderer.single_image_descriptor_layout()
};
pipeline_builder.set_descriptor_set_layouts(descriptor_set_layouts);
VkVertexInputBindingDescription binding {};
binding.binding = 0;
binding.stride = sizeof(Vertex);
binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
VkVertexInputAttributeDescription attribute {};
attribute.location = 0;
attribute.binding = 0;
attribute.format = VK_FORMAT_R32G32B32_SFLOAT;
attribute.offset = offsetof(Vertex, position);
std::array bindings { binding };
std::array attributes { attribute };
m_pipeline = pipeline_builder.build_graphics(
[&](GraphicsPipelineBuilder &builder) -> GraphicsPipelineBuilder & {
builder.set_vertex_input(bindings, attributes);
return builder
.set_shaders(skybox_vert_shader.get(), skybox_frag_shader.get())
.set_input_topology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST)
.set_polygon_mode(VK_POLYGON_MODE_FILL)
.set_cull_mode(
VK_CULL_MODE_FRONT_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE)
.set_multisampling(
static_cast<VkSampleCountFlagBits>(renderer.msaa_samples()))
.disable_blending()
.enable_depth_testing(false, VK_COMPARE_OP_LESS_OR_EQUAL)
.set_color_attachment_format(
static_cast<VkFormat>(renderer.draw_image_format()))
.set_depth_format(
static_cast<VkFormat>(renderer.depth_image_format()));
});
m_pipeline_samples = renderer.msaa_samples();
return true;
}
auto Skybox::init(VulkanRenderer &renderer, std::filesystem::path const &path) auto Skybox::init(VulkanRenderer &renderer, std::filesystem::path const &path)
-> void -> void
{ {
@@ -61,10 +138,10 @@ auto Skybox::init(VulkanRenderer &renderer, std::filesystem::path const &path)
uint32_t const face_size = texture.width / 4; uint32_t const face_size = texture.width / 4;
size_t const face_bytes = static_cast<size_t>(face_size) * face_size * 4; size_t const face_bytes = static_cast<size_t>(face_size) * face_size * 4;
std::vector<uint8_t> cubemap_pixels(face_bytes * kCrossOffsets.size()); std::vector<uint8_t> cubemap_pixels(face_bytes * CROSS_OFFSETS.size());
for (size_t face = 0; face < kCrossOffsets.size(); ++face) { for (size_t face = 0; face < CROSS_OFFSETS.size(); ++face) {
auto const offset = kCrossOffsets[face]; auto const offset = CROSS_OFFSETS[face];
for (uint32_t y = 0; y < face_size; ++y) { for (uint32_t y = 0; y < face_size; ++y) {
for (uint32_t x = 0; x < face_size; ++x) { for (uint32_t x = 0; x < face_size; ++x) {
uint32_t const src_x = offset.x * face_size + x; uint32_t const src_x = offset.x * face_size + x;
@@ -108,7 +185,9 @@ auto Skybox::init(VulkanRenderer &renderer, std::filesystem::path const &path)
vk::DescriptorSetAllocateInfo alloc_info {}; vk::DescriptorSetAllocateInfo alloc_info {};
alloc_info.descriptorPool = m_descriptor_pool.get(); alloc_info.descriptorPool = m_descriptor_pool.get();
alloc_info.descriptorSetCount = 1; alloc_info.descriptorSetCount = 1;
vk::DescriptorSetLayout layout = renderer.single_image_descriptor_layout(); vk::DescriptorSetLayout layout {
renderer.single_image_descriptor_layout()
};
alloc_info.pSetLayouts = &layout; alloc_info.pSetLayouts = &layout;
m_descriptor_set m_descriptor_set
= renderer.device().allocateDescriptorSets(alloc_info).front(); = renderer.device().allocateDescriptorSets(alloc_info).front();
@@ -183,79 +262,11 @@ auto Skybox::init(VulkanRenderer &renderer, std::filesystem::path const &path)
m_index_count = static_cast<uint32_t>(indices.size()); m_index_count = static_cast<uint32_t>(indices.size());
m_cube_mesh = renderer.upload_mesh(indices, vertices); m_cube_mesh = renderer.upload_mesh(indices, vertices);
Pipeline::Builder pipeline_builder { renderer.device(), renderer.logger() }; if (!rebuild_pipeline(renderer)) {
uint8_t skybox_vert_shader_data[] {
#embed "skybox_vert.spv"
};
auto skybox_vert_shader
= vkutil::load_shader_module(std::span<uint8_t>(skybox_vert_shader_data,
sizeof(skybox_vert_shader_data)),
renderer.device());
if (!skybox_vert_shader) {
renderer.logger().err("Failed to load skybox vert shader");
ok = false; ok = false;
return; return;
} }
uint8_t skybox_frag_shader_data[] {
#embed "skybox_frag.spv"
};
auto skybox_frag_shader
= vkutil::load_shader_module(std::span<uint8_t>(skybox_frag_shader_data,
sizeof(skybox_frag_shader_data)),
renderer.device());
if (!skybox_frag_shader) {
renderer.logger().err("Failed to load skybox frag shader");
ok = false;
return;
}
vk::PushConstantRange push_constant_range {};
push_constant_range.stageFlags = vk::ShaderStageFlagBits::eVertex;
push_constant_range.offset = 0;
push_constant_range.size = sizeof(SkyboxPushConstants);
std::array push_constant_ranges { push_constant_range };
pipeline_builder.set_push_constant_ranges(push_constant_ranges);
std::array descriptor_set_layouts {
renderer.single_image_descriptor_layout()
};
pipeline_builder.set_descriptor_set_layouts(descriptor_set_layouts);
VkVertexInputBindingDescription binding {};
binding.binding = 0;
binding.stride = sizeof(Vertex);
binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
VkVertexInputAttributeDescription attribute {};
attribute.location = 0;
attribute.binding = 0;
attribute.format = VK_FORMAT_R32G32B32_SFLOAT;
attribute.offset = offsetof(Vertex, position);
std::array bindings { binding };
std::array attributes { attribute };
m_pipeline = pipeline_builder.build_graphics(
[&](GraphicsPipelineBuilder &builder) -> GraphicsPipelineBuilder & {
builder.set_vertex_input(bindings, attributes);
return builder
.set_shaders(skybox_vert_shader.get(), skybox_frag_shader.get())
.set_input_topology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST)
.set_polygon_mode(VK_POLYGON_MODE_FILL)
.set_cull_mode(
VK_CULL_MODE_FRONT_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE)
.set_multisampling(
static_cast<VkSampleCountFlagBits>(renderer.msaa_samples()))
.disable_blending()
.enable_depth_testing(false, VK_COMPARE_OP_LESS_OR_EQUAL)
.set_color_attachment_format(
static_cast<VkFormat>(renderer.draw_image_format()))
.set_depth_format(
static_cast<VkFormat>(renderer.depth_image_format()));
});
ok = true; ok = true;
} }
@@ -273,6 +284,7 @@ auto Skybox::destroy(VulkanRenderer &renderer) -> void
m_sampler.reset(); m_sampler.reset();
m_descriptor_pool.reset(); m_descriptor_pool.reset();
m_pipeline.reset(); m_pipeline.reset();
m_pipeline_samples = vk::SampleCountFlagBits::e1;
m_descriptor_set = vk::DescriptorSet {}; m_descriptor_set = vk::DescriptorSet {};
m_cube_mesh = {}; m_cube_mesh = {};
m_cubemap = {}; m_cubemap = {};
@@ -280,14 +292,21 @@ auto Skybox::destroy(VulkanRenderer &renderer) -> void
ok = false; ok = false;
} }
auto Skybox::draw(VulkanRenderer::GL &gl, smath::Mat4 const &mvp) -> void auto Skybox::draw(VulkanRenderer::GL &gl, VulkanRenderer &renderer,
smath::Mat4 const &mvp) -> void
{ {
if (!ok) { if (!ok) {
return; return;
} }
if (m_pipeline_samples != renderer.msaa_samples()) {
if (!rebuild_pipeline(renderer)) {
return;
}
}
SkyboxPushConstants push_constants { mvp }; SkyboxPushConstants push_constants { mvp };
auto bytes = std::as_bytes(std::span { &push_constants, 1 }); auto bytes { std::as_bytes(std::span { &push_constants, 1 }) };
gl.draw_indexed(m_pipeline, m_descriptor_set, m_cube_mesh.vertex_buffer, gl.draw_indexed(m_pipeline, m_descriptor_set, m_cube_mesh.vertex_buffer,
m_cube_mesh.index_buffer, m_index_count, bytes); m_cube_mesh.index_buffer, m_index_count, bytes);
} }

View File

@@ -17,15 +17,19 @@ struct Skybox {
auto init(VulkanRenderer &renderer, std::filesystem::path const &path) auto init(VulkanRenderer &renderer, std::filesystem::path const &path)
-> void; -> void;
auto destroy(VulkanRenderer &renderer) -> void; auto destroy(VulkanRenderer &renderer) -> void;
auto draw(VulkanRenderer::GL &gl, smath::Mat4 const &mvp) -> void; auto draw(VulkanRenderer::GL &gl, VulkanRenderer &renderer,
smath::Mat4 const &mvp) -> void;
private: private:
auto rebuild_pipeline(VulkanRenderer &renderer) -> bool;
Pipeline m_pipeline {}; Pipeline m_pipeline {};
GPUMeshBuffers m_cube_mesh {}; GPUMeshBuffers m_cube_mesh {};
AllocatedImage m_cubemap {}; AllocatedImage m_cubemap {};
vk::UniqueSampler m_sampler {}; vk::UniqueSampler m_sampler {};
vk::UniqueDescriptorPool m_descriptor_pool {}; vk::UniqueDescriptorPool m_descriptor_pool {};
vk::DescriptorSet m_descriptor_set {}; vk::DescriptorSet m_descriptor_set {};
vk::SampleCountFlagBits m_pipeline_samples { vk::SampleCountFlagBits::e1 };
uint32_t m_index_count { 0 }; uint32_t m_index_count { 0 };
}; };

View File

@@ -96,7 +96,7 @@ struct PolarCoordinate {
smath::Vec3 to_vec3() const smath::Vec3 to_vec3() const
{ {
float sin_phi = std::sin(phi); float sin_phi { std::sin(phi) };
return smath::Vec3 { r * sin_phi * std::cos(theta), r * std::cos(phi), return smath::Vec3 { r * sin_phi * std::cos(theta), r * std::cos(phi),
r * sin_phi * std::sin(theta) }; r * sin_phi * std::sin(theta) };

View File

@@ -29,7 +29,7 @@ template<typename F> privDefer<F> defer_func(F f) { return privDefer<F>(f); }
#define VK_CHECK(logger, x) \ #define VK_CHECK(logger, x) \
do { \ do { \
auto err { x }; \ auto err { x }; \
auto result = vk::Result(err); \ auto result { vk::Result(err) }; \
if (result != vk::Result::eSuccess) { \ if (result != vk::Result::eSuccess) { \
(logger).err("Detected Vulkan error: {}", vk::to_string(result)); \ (logger).err("Detected Vulkan error: {}", vk::to_string(result)); \
throw std::runtime_error("Vulkan error"); \ throw std::runtime_error("Vulkan error"); \

View File

@@ -62,10 +62,10 @@ auto VulkanRenderer::GL::begin_drawing(vk::CommandBuffer cmd,
m_current_uv = { 0.0f, 0.0f }; m_current_uv = { 0.0f, 0.0f };
m_bound_texture = &m_renderer.m_vk.error_image; m_bound_texture = &m_renderer.m_vk.error_image;
auto const extent = vk::Extent2D { auto const extent { vk::Extent2D {
m_color_target->extent.width, m_color_target->extent.width,
m_color_target->extent.height, m_color_target->extent.height,
}; } };
vk::RenderingAttachmentInfo color_att {}; vk::RenderingAttachmentInfo color_att {};
vk::ClearValue clear {}; vk::ClearValue clear {};
@@ -189,7 +189,8 @@ auto VulkanRenderer::GL::set_culling(bool enabled) -> void
} }
if (m_active_pipeline == &m_renderer.m_vk.mesh_pipeline if (m_active_pipeline == &m_renderer.m_vk.mesh_pipeline
|| m_active_pipeline == &m_renderer.m_vk.mesh_pipeline_culled) { || m_active_pipeline == &m_renderer.m_vk.mesh_pipeline_culled
|| m_active_pipeline == &m_renderer.m_vk.wayland_pipeline) {
m_active_pipeline = enabled ? &m_renderer.m_vk.mesh_pipeline_culled m_active_pipeline = enabled ? &m_renderer.m_vk.mesh_pipeline_culled
: &m_renderer.m_vk.mesh_pipeline; : &m_renderer.m_vk.mesh_pipeline;
} else if (m_active_pipeline == &m_renderer.m_vk.triangle_pipeline } else if (m_active_pipeline == &m_renderer.m_vk.triangle_pipeline
@@ -206,7 +207,7 @@ auto VulkanRenderer::GL::end() -> void
if (!m_inside_primitive) if (!m_inside_primitive)
return; return;
auto const count = m_vertices.size() - m_primitive_start; auto const count { m_vertices.size() - m_primitive_start };
emit_indices(m_primitive_start, count); emit_indices(m_primitive_start, count);
m_inside_primitive = false; m_inside_primitive = false;
} }
@@ -220,8 +221,8 @@ auto VulkanRenderer::GL::flush() -> void
auto const index_data_size { m_indices.size() * sizeof(uint32_t) }; auto const index_data_size { m_indices.size() * sizeof(uint32_t) };
auto const staging_size { vertex_data_size + index_data_size }; auto const staging_size { vertex_data_size + index_data_size };
auto staging = m_renderer.create_buffer(staging_size, auto staging { m_renderer.create_buffer(staging_size,
vk::BufferUsageFlagBits::eTransferSrc, VMA_MEMORY_USAGE_CPU_ONLY); vk::BufferUsageFlagBits::eTransferSrc, VMA_MEMORY_USAGE_CPU_ONLY) };
void *staging_dst = staging.info.pMappedData; void *staging_dst = staging.info.pMappedData;
bool staging_mapped_here { false }; bool staging_mapped_here { false };
@@ -273,7 +274,8 @@ auto VulkanRenderer::GL::flush() -> void
bind_pipeline_if_needed(); bind_pipeline_if_needed();
if (m_active_pipeline == &m_renderer.m_vk.mesh_pipeline if (m_active_pipeline == &m_renderer.m_vk.mesh_pipeline
|| m_active_pipeline == &m_renderer.m_vk.mesh_pipeline_culled) { || m_active_pipeline == &m_renderer.m_vk.mesh_pipeline_culled
|| m_active_pipeline == &m_renderer.m_vk.wayland_pipeline) {
auto const image_set { auto const image_set {
m_renderer.m_vk.get_current_frame().frame_descriptors.allocate( m_renderer.m_vk.get_current_frame().frame_descriptors.allocate(
m_renderer.m_logger, m_renderer.m_vkb.dev.device, m_renderer.m_logger, m_renderer.m_vkb.dev.device,
@@ -290,7 +292,7 @@ auto VulkanRenderer::GL::flush() -> void
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER) VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER)
.update_set(m_renderer.m_vkb.dev.device, image_set); .update_set(m_renderer.m_vkb.dev.device, image_set);
auto vk_image_set = vk::DescriptorSet { image_set }; auto vk_image_set { vk::DescriptorSet { image_set } };
cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics,
m_active_pipeline->get_layout(), 0, vk_image_set, {}); m_active_pipeline->get_layout(), 0, vk_image_set, {});
@@ -327,6 +329,8 @@ auto VulkanRenderer::GL::use_pipeline(Pipeline &pipeline) -> void
resolved_pipeline = m_culling_enabled resolved_pipeline = m_culling_enabled
? &m_renderer.m_vk.mesh_pipeline_culled ? &m_renderer.m_vk.mesh_pipeline_culled
: &m_renderer.m_vk.mesh_pipeline; : &m_renderer.m_vk.mesh_pipeline;
} else if (&pipeline == &m_renderer.m_vk.wayland_pipeline) {
resolved_pipeline = &m_renderer.m_vk.wayland_pipeline;
} else if (&pipeline == &m_renderer.m_vk.triangle_pipeline } else if (&pipeline == &m_renderer.m_vk.triangle_pipeline
|| &pipeline == &m_renderer.m_vk.triangle_pipeline_culled) { || &pipeline == &m_renderer.m_vk.triangle_pipeline_culled) {
resolved_pipeline = m_culling_enabled resolved_pipeline = m_culling_enabled
@@ -369,23 +373,23 @@ auto VulkanRenderer::GL::pop_transform() -> void
auto VulkanRenderer::GL::draw_rectangle(smath::Vec2 pos, smath::Vec2 size, auto VulkanRenderer::GL::draw_rectangle(smath::Vec2 pos, smath::Vec2 size,
smath::Vec4 rect_color, float rotation) -> void smath::Vec4 rect_color, float rotation) -> void
{ {
auto const half_size = size * 0.5f; auto const half_size { size * 0.5f };
auto const center = pos + half_size; auto const center { pos + half_size };
auto rotate = [&](smath::Vec2 const &p) { auto rotate { [&](smath::Vec2 const &p) {
float const c = std::cos(rotation); float const c = std::cos(rotation);
float const s = std::sin(rotation); float const s = std::sin(rotation);
return smath::Vec2 { c * p.x() - s * p.y(), s * p.x() + c * p.y() }; return smath::Vec2 { c * p.x() - s * p.y(), s * p.x() + c * p.y() };
}; } };
auto const br auto const br { center
= center + rotate(smath::Vec2 { half_size.x(), -half_size.y() }); + rotate(smath::Vec2 { half_size.x(), -half_size.y() }) };
auto const tr auto const tr { center
= center + rotate(smath::Vec2 { half_size.x(), half_size.y() }); + rotate(smath::Vec2 { half_size.x(), half_size.y() }) };
auto const bl auto const bl { center
= center + rotate(smath::Vec2 { -half_size.x(), -half_size.y() }); + rotate(smath::Vec2 { -half_size.x(), -half_size.y() }) };
auto const tl auto const tl { center
= center + rotate(smath::Vec2 { -half_size.x(), half_size.y() }); + rotate(smath::Vec2 { -half_size.x(), half_size.y() }) };
begin(GeometryKind::Quads); begin(GeometryKind::Quads);
@@ -423,57 +427,70 @@ auto VulkanRenderer::GL::draw_sphere(smath::Vec3 center, float radius,
float const pi = 3.14159265358979323846f; float const pi = 3.14159265358979323846f;
// Use caller color if provided, otherwise keep current GL color state.
if (sphere_color.has_value()) if (sphere_color.has_value())
color(*sphere_color); color(*sphere_color);
// Build as latitude strips begin(GeometryKind::Triangles);
for (int y = 0; y < rings; y++) { for (int y = 0; y < rings; y++) {
float const v0 = static_cast<float>(y) / static_cast<float>(rings); float const v1 = static_cast<float>(y) / static_cast<float>(rings);
float const v1 = static_cast<float>(y + 1) / static_cast<float>(rings); float const v2 = static_cast<float>(y + 1) / static_cast<float>(rings);
float const theta0 = v0 * pi;
float const theta1 = v1 * pi; float const theta1 = v1 * pi;
float const theta2 = v2 * pi;
float const sin0 = std::sin(theta0); float const s1 = std::sin(theta1);
float const cos0 = std::cos(theta0); float const c1 = std::cos(theta1);
float const sin1 = std::sin(theta1); float const s2 = std::sin(theta2);
float const cos1 = std::cos(theta1); float const c2 = std::cos(theta2);
begin(GeometryKind::TriangleStrip); for (int x = 0; x < segments; x++) {
float const u1
for (int x = 0; x <= segments; x++) {
float const u
= static_cast<float>(x) / static_cast<float>(segments); = static_cast<float>(x) / static_cast<float>(segments);
float const phi = u * (2.0f * pi); float const u2
= static_cast<float>(x + 1) / static_cast<float>(segments);
float const sp = std::sin(phi); float const phi1 = u1 * (2.0f * pi);
float const cp = std::cos(phi); float const phi2 = u2 * (2.0f * pi);
// Vertex on ring y+1 float const sp1 = std::sin(phi1);
{ float const cp1 = std::cos(phi1);
smath::Vec3 n { sin1 * cp, cos1, sin1 * sp }; float const sp2 = std::sin(phi2);
normal(n); float const cp2 = std::cos(phi2);
uv(smath::Vec2 { u, 1.0f - v1 });
smath::Vec3 p = center + n * radius; smath::Vec3 n1 { s1 * cp1, c1, s1 * sp1 };
vert(p); smath::Vec3 n2 { s1 * cp2, c1, s1 * sp2 };
} smath::Vec3 n3 { s2 * cp1, c2, s2 * sp1 };
smath::Vec3 n4 { s2 * cp2, c2, s2 * sp2 };
// Vertex on ring y normal(n1);
{ uv(smath::Vec2 { u1, 1.0f - v1 });
smath::Vec3 n { sin0 * cp, cos0, sin0 * sp }; vert(center + n1 * radius);
normal(n);
uv(smath::Vec2 { u, 1.0f - v0 });
smath::Vec3 p = center + n * radius; normal(n2);
vert(p); uv(smath::Vec2 { u2, 1.0f - v1 });
vert(center + n2 * radius);
normal(n3);
uv(smath::Vec2 { u1, 1.0f - v2 });
vert(center + n3 * radius);
normal(n2);
uv(smath::Vec2 { u2, 1.0f - v1 });
vert(center + n2 * radius);
normal(n4);
uv(smath::Vec2 { u2, 1.0f - v2 });
vert(center + n4 * radius);
normal(n3);
uv(smath::Vec2 { u1, 1.0f - v2 });
vert(center + n3 * radius);
} }
} }
end(); end();
} }
}
auto VulkanRenderer::GL::draw_mesh(GPUMeshBuffers const &mesh, auto VulkanRenderer::GL::draw_mesh(GPUMeshBuffers const &mesh,
smath::Mat4 const &transform, uint32_t index_count, uint32_t first_index, smath::Mat4 const &transform, uint32_t index_count, uint32_t first_index,
@@ -501,7 +518,7 @@ auto VulkanRenderer::GL::draw_mesh(GPUMeshBuffers const &mesh,
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER) VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER)
.update_set(m_renderer.m_vkb.dev.device, image_set); .update_set(m_renderer.m_vkb.dev.device, image_set);
auto vk_image_set = vk::DescriptorSet { image_set }; auto vk_image_set { vk::DescriptorSet { image_set } };
m_cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, m_cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics,
mesh_pipeline.get_layout(), 0, vk_image_set, {}); mesh_pipeline.get_layout(), 0, vk_image_set, {});
@@ -541,7 +558,7 @@ auto VulkanRenderer::GL::draw_indexed(Pipeline &pipeline,
push_constants.data()); push_constants.data());
} }
vk::DeviceSize offset = 0; vk::DeviceSize offset { 0 };
cmd.bindVertexBuffers(0, vertex_buffer.buffer, offset); cmd.bindVertexBuffers(0, vertex_buffer.buffer, offset);
cmd.bindIndexBuffer(index_buffer.buffer, 0, vk::IndexType::eUint32); cmd.bindIndexBuffer(index_buffer.buffer, 0, vk::IndexType::eUint32);
cmd.drawIndexed(index_count, 1, 0, 0, 0); cmd.drawIndexed(index_count, 1, 0, 0, 0);
@@ -627,15 +644,24 @@ auto VulkanRenderer::GL::bind_pipeline_if_needed() -> void
vk::PipelineBindPoint::eGraphics, m_active_pipeline->get()); vk::PipelineBindPoint::eGraphics, m_active_pipeline->get());
} }
VulkanRenderer::VulkanRenderer(SDL_Window *window, Logger &logger) VulkanRenderer::VulkanRenderer(SDL_Window *window, Logger &logger,
std::span<std::string const> instance_extensions,
std::span<std::string const> device_extensions)
: gl(*this) : gl(*this)
, m_window(window) , m_window(window)
, m_logger(logger) , m_logger(logger)
, m_extra_instance_extensions(
instance_extensions.begin(), instance_extensions.end())
, m_extra_device_extensions(
device_extensions.begin(), device_extensions.end())
{ {
if (m_window == nullptr) { if (m_window == nullptr) {
throw std::runtime_error("VulkanRenderer requires a valid window"); throw std::runtime_error("VulkanRenderer requires a valid window");
} }
m_use_kms = false;
m_imgui_enabled = true;
vk_init(); vk_init();
swapchain_init(); swapchain_init();
commands_init(); commands_init();
@@ -646,6 +672,28 @@ VulkanRenderer::VulkanRenderer(SDL_Window *window, Logger &logger)
imgui_init(); imgui_init();
} }
VulkanRenderer::VulkanRenderer(KmsSurfaceConfig /*config*/, Logger &logger,
std::span<std::string const> instance_extensions,
std::span<std::string const> device_extensions)
: gl(*this)
, m_logger(logger)
, m_extra_instance_extensions(
instance_extensions.begin(), instance_extensions.end())
, m_extra_device_extensions(
device_extensions.begin(), device_extensions.end())
{
m_use_kms = true;
m_imgui_enabled = false;
vk_init();
swapchain_init();
commands_init();
sync_init();
descriptors_init();
pipelines_init();
default_data_init();
}
VulkanRenderer::~VulkanRenderer() VulkanRenderer::~VulkanRenderer()
{ {
m_device.waitIdle(); m_device.waitIdle();
@@ -666,6 +714,7 @@ VulkanRenderer::~VulkanRenderer()
m_vk.triangle_pipeline_culled.reset(); m_vk.triangle_pipeline_culled.reset();
m_vk.mesh_pipeline.reset(); m_vk.mesh_pipeline.reset();
m_vk.mesh_pipeline_culled.reset(); m_vk.mesh_pipeline_culled.reset();
m_vk.wayland_pipeline.reset();
m_vk.default_sampler_linear.reset(); m_vk.default_sampler_linear.reset();
m_vk.default_sampler_nearest.reset(); m_vk.default_sampler_nearest.reset();
@@ -687,8 +736,12 @@ VulkanRenderer::~VulkanRenderer()
} }
if (m_vk.surface) { if (m_vk.surface) {
SDL_Vulkan_DestroySurface( if (m_use_kms) {
m_vkb.instance, static_cast<VkSurfaceKHR>(m_vk.surface), nullptr); m_instance.destroySurfaceKHR(m_vk.surface);
} else {
SDL_Vulkan_DestroySurface(m_vkb.instance,
static_cast<VkSurfaceKHR>(m_vk.surface), nullptr);
}
m_vk.surface = nullptr; m_vk.surface = nullptr;
} }
@@ -701,6 +754,26 @@ auto VulkanRenderer::resize(uint32_t width, uint32_t height) -> void
recreate_swapchain(width, height); recreate_swapchain(width, height);
} }
auto VulkanRenderer::set_offscreen_extent(vk::Extent2D extent) -> void
{
if (extent.width == 0 || extent.height == 0) {
return;
}
if (m_vk.draw_image.extent.width == extent.width
&& m_vk.draw_image.extent.height == extent.height) {
return;
}
m_device.waitIdle();
destroy_draw_image();
destroy_msaa_color_image();
destroy_depth_image();
create_draw_image(extent.width, extent.height);
create_msaa_color_image(extent.width, extent.height);
create_depth_image(extent.width, extent.height);
}
auto VulkanRenderer::set_antialiasing(AntiAliasingKind kind) -> void auto VulkanRenderer::set_antialiasing(AntiAliasingKind kind) -> void
{ {
enqueue_render_command(RenderCommand { enqueue_render_command(RenderCommand {
@@ -715,7 +788,7 @@ auto VulkanRenderer::set_antialiasing_immediate(AntiAliasingKind kind) -> void
auto VulkanRenderer::apply_antialiasing(AntiAliasingKind kind) -> void auto VulkanRenderer::apply_antialiasing(AntiAliasingKind kind) -> void
{ {
auto requested_samples = [&](AntiAliasingKind aa) { auto requested_samples { [&](AntiAliasingKind aa) {
switch (aa) { switch (aa) {
case AntiAliasingKind::NONE: case AntiAliasingKind::NONE:
return vk::SampleCountFlagBits::e1; return vk::SampleCountFlagBits::e1;
@@ -727,14 +800,14 @@ auto VulkanRenderer::apply_antialiasing(AntiAliasingKind kind) -> void
return vk::SampleCountFlagBits::e8; return vk::SampleCountFlagBits::e8;
} }
return vk::SampleCountFlagBits::e1; return vk::SampleCountFlagBits::e1;
}(kind); }(kind) };
auto best_supported = [&](vk::SampleCountFlagBits requested) { auto best_supported { [&](vk::SampleCountFlagBits requested) {
auto const supported = m_vk.supported_framebuffer_samples; auto const supported { m_vk.supported_framebuffer_samples };
auto pick_if_supported = [&](vk::SampleCountFlagBits candidate) { auto pick_if_supported { [&](vk::SampleCountFlagBits candidate) {
return (supported & candidate) == candidate; return (supported & candidate) == candidate;
}; } };
if (requested >= vk::SampleCountFlagBits::e64 if (requested >= vk::SampleCountFlagBits::e64
&& pick_if_supported(vk::SampleCountFlagBits::e64)) { && pick_if_supported(vk::SampleCountFlagBits::e64)) {
@@ -761,9 +834,9 @@ auto VulkanRenderer::apply_antialiasing(AntiAliasingKind kind) -> void
return vk::SampleCountFlagBits::e2; return vk::SampleCountFlagBits::e2;
} }
return vk::SampleCountFlagBits::e1; return vk::SampleCountFlagBits::e1;
}(requested_samples); }(requested_samples) };
auto kind_for_samples = [](vk::SampleCountFlagBits samples) { auto kind_for_samples { [](vk::SampleCountFlagBits samples) {
switch (samples) { switch (samples) {
case vk::SampleCountFlagBits::e2: case vk::SampleCountFlagBits::e2:
return AntiAliasingKind::MSAA_2X; return AntiAliasingKind::MSAA_2X;
@@ -774,9 +847,9 @@ auto VulkanRenderer::apply_antialiasing(AntiAliasingKind kind) -> void
default: default:
return AntiAliasingKind::NONE; return AntiAliasingKind::NONE;
} }
}; } };
auto const effective_kind = kind_for_samples(best_supported); auto const effective_kind { kind_for_samples(best_supported) };
if (m_vk.antialiasing_kind == effective_kind if (m_vk.antialiasing_kind == effective_kind
&& m_vk.msaa_samples == best_supported) { && m_vk.msaa_samples == best_supported) {
return; return;
@@ -863,6 +936,129 @@ auto VulkanRenderer::immediate_submit(
} }
} }
auto VulkanRenderer::setup_kms_surface() -> void
{
auto const devices { m_instance.enumeratePhysicalDevices() };
if (devices.empty()) {
m_logger.err("No Vulkan physical devices available for KMS");
throw std::runtime_error("App init fail");
}
m_logger.info("Found {} Vulkan physical device(s)", devices.size());
for (auto const &device : devices) {
auto const props { device.getProperties() };
m_logger.info("Checking device: {}", std::string(props.deviceName));
auto const displays { device.getDisplayPropertiesKHR() };
if (displays.empty()) {
m_logger.info(" Device has no display properties");
continue;
}
m_logger.info(" Device has {} display(s)", displays.size());
for (auto const &display_props : displays) {
m_logger.info(" Checking display: {}",
display_props.displayName
? std::string(display_props.displayName)
: "(unnamed)");
auto const modes
= device.getDisplayModePropertiesKHR(display_props.display);
if (modes.empty()) {
m_logger.info(" Display has no modes");
continue;
}
m_logger.info(" Display has {} mode(s)", modes.size());
auto const best_mode_it { std::max_element(modes.begin(),
modes.end(), [](auto const &lhs, auto const &rhs) {
auto const lhs_extent { lhs.parameters.visibleRegion };
auto const rhs_extent { rhs.parameters.visibleRegion };
auto const lhs_area { static_cast<uint64_t>(
lhs_extent.width)
* static_cast<uint64_t>(lhs_extent.height) };
auto const rhs_area { static_cast<uint64_t>(
rhs_extent.width)
* static_cast<uint64_t>(rhs_extent.height) };
if (lhs_area == rhs_area) {
return lhs.parameters.refreshRate
< rhs.parameters.refreshRate;
}
return lhs_area < rhs_area;
}) };
auto const planes { device.getDisplayPlanePropertiesKHR() };
std::optional<uint32_t> plane_index;
uint32_t plane_stack_index { 0 };
for (uint32_t i = 0; i < planes.size(); ++i) {
auto const supported_displays
= device.getDisplayPlaneSupportedDisplaysKHR(i);
if (std::find(supported_displays.begin(),
supported_displays.end(), display_props.display)
!= supported_displays.end()) {
plane_index = i;
plane_stack_index = planes[i].currentStackIndex;
break;
}
}
if (!plane_index) {
m_logger.info(" No display plane supports this display ({} "
"plane(s) checked)",
planes.size());
continue;
}
m_logger.info(
" Found suitable display on plane {}", *plane_index);
auto const extent { best_mode_it->parameters.visibleRegion };
KmsState state {};
state.display = display_props.display;
state.mode = best_mode_it->displayMode;
state.extent = extent;
state.plane_index = *plane_index;
state.plane_stack_index = plane_stack_index;
if (display_props.displayName) {
state.display_name = display_props.displayName;
}
m_kms_state = state;
m_kms_extent = extent;
m_kms_physical_device = device;
m_kms_physical_device_set = true;
m_logger.info("Using KMS display {} ({}x{} @ {} mHz)",
state.display_name.empty() ? "unnamed" : state.display_name,
extent.width, extent.height,
best_mode_it->parameters.refreshRate);
break;
}
if (m_kms_state) {
break;
}
}
if (!m_kms_state) {
m_logger.err("No suitable KMS display found");
throw std::runtime_error("App init fail");
}
vk::DisplaySurfaceCreateInfoKHR surface_info {};
surface_info.displayMode = m_kms_state->mode;
surface_info.planeIndex = m_kms_state->plane_index;
surface_info.planeStackIndex = m_kms_state->plane_stack_index;
surface_info.transform = vk::SurfaceTransformFlagBitsKHR::eIdentity;
surface_info.alphaMode = vk::DisplayPlaneAlphaFlagBitsKHR::eOpaque;
surface_info.globalAlpha = 1.0f;
surface_info.imageExtent = m_kms_state->extent;
m_vk.surface = m_instance.createDisplayPlaneSurfaceKHR(surface_info);
}
auto VulkanRenderer::vk_init() -> void auto VulkanRenderer::vk_init() -> void
{ {
VULKAN_HPP_DEFAULT_DISPATCHER.init(vkGetInstanceProcAddr); VULKAN_HPP_DEFAULT_DISPATCHER.init(vkGetInstanceProcAddr);
@@ -881,7 +1077,7 @@ auto VulkanRenderer::vk_init() -> void
void *user_data) { void *user_data) {
auto renderer { reinterpret_cast<VulkanRenderer *>(user_data) }; auto renderer { reinterpret_cast<VulkanRenderer *>(user_data) };
auto level = Logger::Level::Debug; auto level { Logger::Level::Debug };
if (message_severity if (message_severity
& VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) { & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) {
level = Logger::Level::Error; level = Logger::Level::Error;
@@ -900,6 +1096,12 @@ auto VulkanRenderer::vk_init() -> void
return VK_FALSE; return VK_FALSE;
}); });
if (m_use_kms) {
instance_builder.enable_extension(VK_KHR_DISPLAY_EXTENSION_NAME);
}
for (auto const &extension : m_extra_instance_extensions) {
instance_builder.enable_extension(extension.c_str());
}
#ifndef NDEBUG #ifndef NDEBUG
instance_builder.request_validation_layers(); instance_builder.request_validation_layers();
#endif #endif
@@ -914,6 +1116,9 @@ auto VulkanRenderer::vk_init() -> void
m_instance = vk::Instance { m_vkb.instance.instance }; m_instance = vk::Instance { m_vkb.instance.instance };
VULKAN_HPP_DEFAULT_DISPATCHER.init(m_instance); VULKAN_HPP_DEFAULT_DISPATCHER.init(m_instance);
if (m_use_kms) {
setup_kms_surface();
} else {
VkSurfaceKHR raw_surface {}; VkSurfaceKHR raw_surface {};
if (!SDL_Vulkan_CreateSurface( if (!SDL_Vulkan_CreateSurface(
m_window, m_vkb.instance, nullptr, &raw_surface)) { m_window, m_vkb.instance, nullptr, &raw_surface)) {
@@ -921,6 +1126,7 @@ auto VulkanRenderer::vk_init() -> void
throw std::runtime_error("App init fail"); throw std::runtime_error("App init fail");
} }
m_vk.surface = vk::SurfaceKHR { raw_surface }; m_vk.surface = vk::SurfaceKHR { raw_surface };
}
vkb::PhysicalDeviceSelector phys_device_selector { m_vkb.instance }; vkb::PhysicalDeviceSelector phys_device_selector { m_vkb.instance };
VkPhysicalDeviceVulkan13Features features_13 {}; VkPhysicalDeviceVulkan13Features features_13 {};
@@ -933,8 +1139,7 @@ auto VulkanRenderer::vk_init() -> void
buffer_device_address_features.sType buffer_device_address_features.sType
= VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES; = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES;
buffer_device_address_features.bufferDeviceAddress = VK_TRUE; buffer_device_address_features.bufferDeviceAddress = VK_TRUE;
phys_device_selector.set_surface(m_vk.surface) std::vector<char const *> desired_extensions {
.add_desired_extensions({
VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME, VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME,
VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME, VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME,
VK_EXT_EXTERNAL_MEMORY_DMA_BUF_EXTENSION_NAME, VK_EXT_EXTERNAL_MEMORY_DMA_BUF_EXTENSION_NAME,
@@ -947,9 +1152,18 @@ auto VulkanRenderer::vk_init() -> void
VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME, VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME,
VK_KHR_COPY_COMMANDS_2_EXTENSION_NAME, VK_KHR_COPY_COMMANDS_2_EXTENSION_NAME,
VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME, VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME,
}) };
std::vector<char const *> required_extensions {};
for (auto const &extension : m_extra_device_extensions) {
required_extensions.push_back(extension.c_str());
}
phys_device_selector.set_surface(m_vk.surface)
.add_desired_extensions(desired_extensions)
.set_required_features_13(features_13) .set_required_features_13(features_13)
.add_required_extension_features(buffer_device_address_features); .add_required_extension_features(buffer_device_address_features);
if (!required_extensions.empty()) {
phys_device_selector.add_required_extensions(required_extensions);
}
auto physical_device_selector_return { phys_device_selector.select() }; auto physical_device_selector_return { phys_device_selector.select() };
if (!physical_device_selector_return) { if (!physical_device_selector_return) {
std::println(std::cerr, std::println(std::cerr,
@@ -962,8 +1176,12 @@ auto VulkanRenderer::vk_init() -> void
m_logger.info("Chosen Vulkan physical device: {}", m_logger.info("Chosen Vulkan physical device: {}",
m_vkb.phys_dev.properties.deviceName); m_vkb.phys_dev.properties.deviceName);
if (m_use_kms && m_kms_physical_device_set
&& m_physical_device != m_kms_physical_device) {
m_logger.warn("KMS display is not on the selected physical device");
}
auto const props = m_physical_device.getProperties(); auto const props { m_physical_device.getProperties() };
m_vk.supported_framebuffer_samples m_vk.supported_framebuffer_samples
= props.limits.framebufferColorSampleCounts = props.limits.framebufferColorSampleCounts
& props.limits.framebufferDepthSampleCounts; & props.limits.framebufferDepthSampleCounts;
@@ -990,6 +1208,13 @@ auto VulkanRenderer::vk_init() -> void
} }
m_vk.graphics_queue_family = queue_family_ret.value(); m_vk.graphics_queue_family = queue_family_ret.value();
m_vk.graphics_queue = m_device.getQueue(m_vk.graphics_queue_family, 0); m_vk.graphics_queue = m_device.getQueue(m_vk.graphics_queue_family, 0);
if (m_use_kms) {
if (!m_physical_device.getSurfaceSupportKHR(
m_vk.graphics_queue_family, m_vk.surface)) {
m_logger.err("Selected device does not support KMS surface");
throw std::runtime_error("App init fail");
}
}
VmaAllocatorCreateInfo allocator_ci {}; VmaAllocatorCreateInfo allocator_ci {};
allocator_ci.physicalDevice = m_vkb.phys_dev.physical_device; allocator_ci.physicalDevice = m_vkb.phys_dev.physical_device;
@@ -1001,12 +1226,21 @@ auto VulkanRenderer::vk_init() -> void
auto VulkanRenderer::swapchain_init() -> void auto VulkanRenderer::swapchain_init() -> void
{ {
int w, h; uint32_t width { 0 };
uint32_t height { 0 };
if (m_use_kms) {
width = m_kms_extent.width;
height = m_kms_extent.height;
} else {
int w {}, h {};
SDL_GetWindowSize(m_window, &w, &h); SDL_GetWindowSize(m_window, &w, &h);
create_swapchain(static_cast<uint32_t>(w), static_cast<uint32_t>(h)); width = static_cast<uint32_t>(w);
create_draw_image(static_cast<uint32_t>(w), static_cast<uint32_t>(h)); height = static_cast<uint32_t>(h);
create_msaa_color_image(static_cast<uint32_t>(w), static_cast<uint32_t>(h)); }
create_depth_image(static_cast<uint32_t>(w), static_cast<uint32_t>(h)); create_swapchain(width, height);
create_draw_image(width, height);
create_msaa_color_image(width, height);
create_depth_image(width, height);
} }
auto VulkanRenderer::commands_init() -> void auto VulkanRenderer::commands_init() -> void
@@ -1106,10 +1340,10 @@ auto VulkanRenderer::triangle_pipeline_init() -> void
uint8_t triangle_vert_shader_data[] { uint8_t triangle_vert_shader_data[] {
#embed "triangle_vert.spv" #embed "triangle_vert.spv"
}; };
auto triangle_vert_shader = vkutil::load_shader_module( auto triangle_vert_shader { vkutil::load_shader_module(
std::span<uint8_t>( std::span<uint8_t>(
triangle_vert_shader_data, sizeof(triangle_vert_shader_data)), triangle_vert_shader_data, sizeof(triangle_vert_shader_data)),
m_device); m_device) };
if (!triangle_vert_shader) { if (!triangle_vert_shader) {
m_logger.err("Failed to load triangle vert shader"); m_logger.err("Failed to load triangle vert shader");
} }
@@ -1117,10 +1351,10 @@ auto VulkanRenderer::triangle_pipeline_init() -> void
uint8_t triangle_frag_shader_data[] { uint8_t triangle_frag_shader_data[] {
#embed "triangle_frag.spv" #embed "triangle_frag.spv"
}; };
auto triangle_frag_shader = vkutil::load_shader_module( auto triangle_frag_shader { vkutil::load_shader_module(
std::span<uint8_t>( std::span<uint8_t>(
triangle_frag_shader_data, sizeof(triangle_frag_shader_data)), triangle_frag_shader_data, sizeof(triangle_frag_shader_data)),
m_device); m_device) };
if (!triangle_frag_shader) { if (!triangle_frag_shader) {
m_logger.err("Failed to load triangle frag shader"); m_logger.err("Failed to load triangle frag shader");
} }
@@ -1170,10 +1404,10 @@ auto VulkanRenderer::mesh_pipeline_init() -> void
uint8_t triangle_vert_shader_data[] { uint8_t triangle_vert_shader_data[] {
#embed "triangle_mesh_vert.spv" #embed "triangle_mesh_vert.spv"
}; };
auto triangle_vert_shader = vkutil::load_shader_module( auto triangle_vert_shader { vkutil::load_shader_module(
std::span<uint8_t>( std::span<uint8_t>(
triangle_vert_shader_data, sizeof(triangle_vert_shader_data)), triangle_vert_shader_data, sizeof(triangle_vert_shader_data)),
m_device); m_device) };
if (!triangle_vert_shader) { if (!triangle_vert_shader) {
m_logger.err("Failed to load triangle vert shader"); m_logger.err("Failed to load triangle vert shader");
} }
@@ -1181,10 +1415,10 @@ auto VulkanRenderer::mesh_pipeline_init() -> void
uint8_t triangle_frag_shader_data[] { uint8_t triangle_frag_shader_data[] {
#embed "tex_image_frag.spv" #embed "tex_image_frag.spv"
}; };
auto triangle_frag_shader = vkutil::load_shader_module( auto triangle_frag_shader { vkutil::load_shader_module(
std::span<uint8_t>( std::span<uint8_t>(
triangle_frag_shader_data, sizeof(triangle_frag_shader_data)), triangle_frag_shader_data, sizeof(triangle_frag_shader_data)),
m_device); m_device) };
if (!triangle_frag_shader) { if (!triangle_frag_shader) {
m_logger.err("Failed to load triangle frag shader"); m_logger.err("Failed to load triangle frag shader");
} }
@@ -1217,6 +1451,24 @@ auto VulkanRenderer::mesh_pipeline_init() -> void
.set_depth_format( .set_depth_format(
static_cast<VkFormat>(m_vk.depth_image.format)); static_cast<VkFormat>(m_vk.depth_image.format));
}); });
m_vk.wayland_pipeline
= builder.build_graphics([&](GraphicsPipelineBuilder &pipeline_builder)
-> GraphicsPipelineBuilder & {
return pipeline_builder
.set_shaders(
triangle_vert_shader.get(), triangle_frag_shader.get())
.set_input_topology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST)
.set_polygon_mode(VK_POLYGON_MODE_FILL)
.set_cull_mode(VK_CULL_MODE_NONE, VK_FRONT_FACE_CLOCKWISE)
.set_multisampling(
static_cast<VkSampleCountFlagBits>(m_vk.msaa_samples))
.enable_blending_alpha_blend()
.disable_depth_testing()
.set_color_attachment_format(
static_cast<VkFormat>(m_vk.draw_image.format))
.set_depth_format(
static_cast<VkFormat>(m_vk.depth_image.format));
});
m_vk.mesh_pipeline_culled m_vk.mesh_pipeline_culled
= builder.build_graphics([&](GraphicsPipelineBuilder &pipeline_builder) = builder.build_graphics([&](GraphicsPipelineBuilder &pipeline_builder)
-> GraphicsPipelineBuilder & { -> GraphicsPipelineBuilder & {
@@ -1254,7 +1506,7 @@ auto VulkanRenderer::imgui_init() -> void
{ VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1000 }, { VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1000 },
}; };
VkDescriptorPoolCreateInfo pool_info = {}; VkDescriptorPoolCreateInfo pool_info {};
pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT; pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
pool_info.maxSets = 1000; pool_info.maxSets = 1000;
@@ -1267,7 +1519,7 @@ auto VulkanRenderer::imgui_init() -> void
ImGui_ImplSDL3_InitForVulkan(m_window); ImGui_ImplSDL3_InitForVulkan(m_window);
ImGui_ImplVulkan_InitInfo init_info = {}; ImGui_ImplVulkan_InitInfo init_info {};
init_info.Instance = m_vkb.instance; init_info.Instance = m_vkb.instance;
init_info.PhysicalDevice = m_vkb.phys_dev.physical_device; init_info.PhysicalDevice = m_vkb.phys_dev.physical_device;
init_info.Device = m_vkb.dev.device; init_info.Device = m_vkb.dev.device;
@@ -1281,7 +1533,8 @@ auto VulkanRenderer::imgui_init() -> void
= VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO; = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
init_info.PipelineInfoMain.PipelineRenderingCreateInfo.colorAttachmentCount init_info.PipelineInfoMain.PipelineRenderingCreateInfo.colorAttachmentCount
= 1; = 1;
auto swapchain_format = static_cast<VkFormat>(m_vk.swapchain_image_format); auto swapchain_format { static_cast<VkFormat>(
m_vk.swapchain_image_format) };
init_info.PipelineInfoMain.PipelineRenderingCreateInfo init_info.PipelineInfoMain.PipelineRenderingCreateInfo
.pColorAttachmentFormats .pColorAttachmentFormats
= &swapchain_format; = &swapchain_format;
@@ -1344,21 +1597,21 @@ auto VulkanRenderer::default_data_init() -> void
{ {
// Solid color images // Solid color images
auto const white = smath::pack_unorm4x8(smath::Vec4 { 1, 1, 1, 1 }); auto const white { smath::pack_unorm4x8(smath::Vec4 { 1, 1, 1, 1 }) };
m_vk.white_image = create_image(&white, vk::Extent3D { 1, 1, 1 }, m_vk.white_image = create_image(&white, vk::Extent3D { 1, 1, 1 },
vk::Format::eR8G8B8A8Unorm, vk::ImageUsageFlagBits::eSampled); vk::Format::eR8G8B8A8Unorm, vk::ImageUsageFlagBits::eSampled);
auto const black = smath::pack_unorm4x8(smath::Vec4 { 0, 0, 0, 1 }); auto const black { smath::pack_unorm4x8(smath::Vec4 { 0, 0, 0, 1 }) };
m_vk.black_image = create_image(&black, vk::Extent3D { 1, 1, 1 }, m_vk.black_image = create_image(&black, vk::Extent3D { 1, 1, 1 },
vk::Format::eR8G8B8A8Unorm, vk::ImageUsageFlagBits::eSampled); vk::Format::eR8G8B8A8Unorm, vk::ImageUsageFlagBits::eSampled);
auto const gray auto const gray { smath::pack_unorm4x8(
= smath::pack_unorm4x8(smath::Vec4 { 0.6f, 0.6f, 0.6f, 1 }); smath::Vec4 { 0.6f, 0.6f, 0.6f, 1 }) };
m_vk.gray_image = create_image(&gray, vk::Extent3D { 1, 1, 1 }, m_vk.gray_image = create_image(&gray, vk::Extent3D { 1, 1, 1 },
vk::Format::eR8G8B8A8Unorm, vk::ImageUsageFlagBits::eSampled); vk::Format::eR8G8B8A8Unorm, vk::ImageUsageFlagBits::eSampled);
// Error checkerboard image // Error checkerboard image
auto const magenta = smath::pack_unorm4x8(smath::Vec4 { 1, 0, 1, 1 }); auto const magenta { smath::pack_unorm4x8(smath::Vec4 { 1, 0, 1, 1 }) };
std::array<uint32_t, 16 * 16> checkerboard; std::array<uint32_t, 16 * 16> checkerboard;
for (int x = 0; x < 16; x++) { for (int x = 0; x < 16; x++) {
for (int y = 0; y < 16; y++) { for (int y = 0; y < 16; y++) {
@@ -1402,7 +1655,7 @@ auto VulkanRenderer::render(std::function<void(GL &)> const &record) -> void
process_render_commands(); process_render_commands();
auto &frame = m_vk.get_current_frame(); auto &frame { m_vk.get_current_frame() };
VK_CHECK(m_logger, VK_CHECK(m_logger,
m_device.waitForFences(frame.render_fence.get(), true, 1'000'000'000)); m_device.waitForFences(frame.render_fence.get(), true, 1'000'000'000));
frame.deletion_queue.flush(); frame.deletion_queue.flush();
@@ -1412,17 +1665,21 @@ auto VulkanRenderer::render(std::function<void(GL &)> const &record) -> void
emit_tracy_frame_image(frame); emit_tracy_frame_image(frame);
#endif #endif
auto raw_fence = static_cast<VkFence>(frame.render_fence.get()); auto raw_fence { static_cast<VkFence>(frame.render_fence.get()) };
VK_CHECK(m_logger, vkResetFences(m_vkb.dev.device, 1, &raw_fence)); VK_CHECK(m_logger, vkResetFences(m_vkb.dev.device, 1, &raw_fence));
auto const acquire_result = m_device.acquireNextImageKHR( auto const acquire_result { m_device.acquireNextImageKHR(
m_vk.swapchain, 1'000'000'000, frame.swapchain_semaphore.get(), {}); m_vk.swapchain, 1'000'000'000, frame.swapchain_semaphore.get(), {}) };
if (acquire_result.result == vk::Result::eErrorOutOfDateKHR if (acquire_result.result == vk::Result::eErrorOutOfDateKHR
|| acquire_result.result == vk::Result::eSuboptimalKHR) { || acquire_result.result == vk::Result::eSuboptimalKHR) {
if (m_use_kms) {
recreate_swapchain(m_kms_extent.width, m_kms_extent.height);
} else {
int width {}, height {}; int width {}, height {};
SDL_GetWindowSize(m_window, &width, &height); SDL_GetWindowSize(m_window, &width, &height);
recreate_swapchain( recreate_swapchain(
static_cast<uint32_t>(width), static_cast<uint32_t>(height)); static_cast<uint32_t>(width), static_cast<uint32_t>(height));
}
return; return;
} }
VK_CHECK(m_logger, acquire_result.result); VK_CHECK(m_logger, acquire_result.result);
@@ -1478,7 +1735,10 @@ auto VulkanRenderer::render(std::function<void(GL &)> const &record) -> void
vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eTransferDstOptimal,
vk::ImageLayout::eColorAttachmentOptimal); vk::ImageLayout::eColorAttachmentOptimal);
draw_imgui(cmd, m_vk.swapchain_image_views.at(swapchain_image_idx).get()); if (m_imgui_enabled) {
draw_imgui(
cmd, m_vk.swapchain_image_views.at(swapchain_image_idx).get());
}
vkutil::transition_image(cmd, m_vk.swapchain_images.at(swapchain_image_idx), vkutil::transition_image(cmd, m_vk.swapchain_images.at(swapchain_image_idx),
vk::ImageLayout::eColorAttachmentOptimal, vk::ImageLayout::eColorAttachmentOptimal,
@@ -1569,8 +1829,9 @@ auto VulkanRenderer::render(std::function<void(GL &)> const &record) -> void
cmd.end(); cmd.end();
auto render_semaphore auto render_semaphore {
= m_vk.present_semaphores.at(swapchain_image_idx).get(); m_vk.present_semaphores.at(swapchain_image_idx).get()
};
vk::PipelineStageFlags2 wait_stage vk::PipelineStageFlags2 wait_stage
= vk::PipelineStageFlagBits2::eColorAttachmentOutput; = vk::PipelineStageFlagBits2::eColorAttachmentOutput;
auto wait_info { vkinit::semaphore_submit_info( auto wait_info { vkinit::semaphore_submit_info(
@@ -1589,18 +1850,103 @@ auto VulkanRenderer::render(std::function<void(GL &)> const &record) -> void
present_info.setWaitSemaphores(render_semaphore); present_info.setWaitSemaphores(render_semaphore);
present_info.setImageIndices(swapchain_image_idx); present_info.setImageIndices(swapchain_image_idx);
auto const present_result = m_vk.graphics_queue.presentKHR(present_info); auto const present_result { m_vk.graphics_queue.presentKHR(present_info) };
if (present_result == vk::Result::eErrorOutOfDateKHR if (present_result == vk::Result::eErrorOutOfDateKHR
|| present_result == vk::Result::eSuboptimalKHR) { || present_result == vk::Result::eSuboptimalKHR) {
if (m_use_kms) {
recreate_swapchain(m_kms_extent.width, m_kms_extent.height);
} else {
int width {}, height {}; int width {}, height {};
SDL_GetWindowSize(m_window, &width, &height); SDL_GetWindowSize(m_window, &width, &height);
recreate_swapchain( recreate_swapchain(
static_cast<uint32_t>(width), static_cast<uint32_t>(height)); static_cast<uint32_t>(width), static_cast<uint32_t>(height));
}
return; return;
} }
VK_CHECK(m_logger, present_result); VK_CHECK(m_logger, present_result);
} }
auto VulkanRenderer::render_to_image(vk::Image target_image,
vk::Extent2D target_extent, std::function<void(GL &)> const &record) -> void
{
defer(m_vk.frame_number++);
if (!target_image || target_extent.width == 0
|| target_extent.height == 0) {
return;
}
process_render_commands();
auto &frame { m_vk.get_current_frame() };
VK_CHECK(m_logger,
m_device.waitForFences(frame.render_fence.get(), true, 1'000'000'000));
frame.deletion_queue.flush();
frame.frame_descriptors.clear_pools(m_vkb.dev.device);
auto raw_fence { static_cast<VkFence>(frame.render_fence.get()) };
VK_CHECK(m_logger, vkResetFences(m_vkb.dev.device, 1, &raw_fence));
auto cmd { frame.main_command_buffer.get() };
cmd.reset();
m_vk.draw_extent.width = m_vk.draw_image.extent.width;
m_vk.draw_extent.height = m_vk.draw_image.extent.height;
vk::CommandBufferBeginInfo cmd_begin_info {};
cmd_begin_info.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit;
VK_CHECK(m_logger,
vkBeginCommandBuffer(static_cast<VkCommandBuffer>(cmd),
reinterpret_cast<VkCommandBufferBeginInfo *>(&cmd_begin_info)));
bool const msaa_enabled = m_vk.msaa_samples != vk::SampleCountFlagBits::e1;
vkutil::transition_image(cmd, m_vk.draw_image.image, m_vk.draw_image_layout,
vk::ImageLayout::eColorAttachmentOptimal);
m_vk.draw_image_layout = vk::ImageLayout::eColorAttachmentOptimal;
if (msaa_enabled) {
vkutil::transition_image(cmd, m_vk.msaa_color_image.image,
m_vk.msaa_color_image_layout,
vk::ImageLayout::eColorAttachmentOptimal);
m_vk.msaa_color_image_layout = vk::ImageLayout::eColorAttachmentOptimal;
}
vkutil::transition_image(cmd, m_vk.depth_image.image,
m_vk.depth_image_layout, vk::ImageLayout::eDepthAttachmentOptimal);
m_vk.depth_image_layout = vk::ImageLayout::eDepthAttachmentOptimal;
gl.begin_drawing(cmd, m_vk.draw_image, &m_vk.depth_image);
if (record) {
record(gl);
}
gl.end_drawing();
vkutil::transition_image(cmd, m_vk.draw_image.image, m_vk.draw_image_layout,
vk::ImageLayout::eTransferSrcOptimal);
m_vk.draw_image_layout = vk::ImageLayout::eTransferSrcOptimal;
vkutil::transition_image(cmd, target_image, vk::ImageLayout::eUndefined,
vk::ImageLayout::eTransferDstOptimal);
vkutil::copy_image_to_image(cmd, m_vk.draw_image.image, target_image,
m_vk.draw_extent, target_extent);
vkutil::transition_image(cmd, target_image,
vk::ImageLayout::eTransferDstOptimal,
vk::ImageLayout::eColorAttachmentOptimal);
cmd.end();
auto command_buffer_info { vkinit::command_buffer_submit_info(cmd) };
auto submit_info { vkinit::submit_info2(
&command_buffer_info, nullptr, nullptr) };
m_vk.graphics_queue.submit2(submit_info, frame.render_fence.get());
VK_CHECK(m_logger,
m_device.waitForFences(frame.render_fence.get(), true, 1'000'000'000));
}
auto VulkanRenderer::draw_imgui( auto VulkanRenderer::draw_imgui(
vk::CommandBuffer cmd, vk::ImageView target_image_view) -> void vk::CommandBuffer cmd, vk::ImageView target_image_view) -> void
{ {
@@ -1642,7 +1988,7 @@ auto VulkanRenderer::create_swapchain(uint32_t width, uint32_t height) -> void
m_vk.swapchain = m_vkb.swapchain.swapchain; m_vk.swapchain = m_vkb.swapchain.swapchain;
m_vk.swapchain_extent = vk::Extent2D { m_vkb.swapchain.extent.width, m_vk.swapchain_extent = vk::Extent2D { m_vkb.swapchain.extent.width,
m_vkb.swapchain.extent.height }; m_vkb.swapchain.extent.height };
auto images = m_vkb.swapchain.get_images().value(); auto images { m_vkb.swapchain.get_images().value() };
m_vk.swapchain_images.assign(images.begin(), images.end()); m_vk.swapchain_images.assign(images.begin(), images.end());
m_vk.swapchain_image_views.clear(); m_vk.swapchain_image_views.clear();
@@ -1890,7 +2236,7 @@ auto VulkanRenderer::emit_frame_screenshot(FrameData &frame) -> void
destination[i] = source[i + 2]; destination[i] = source[i + 2];
destination[i + 1] = source[i + 1]; destination[i + 1] = source[i + 1];
destination[i + 2] = source[i]; destination[i + 2] = source[i];
destination[i + 3] = source[i + 3]; destination[i + 3] = 0xff;
} }
auto const screenshot_flags { vk::ImageUsageFlagBits::eSampled }; auto const screenshot_flags { vk::ImageUsageFlagBits::eSampled };
@@ -2161,9 +2507,11 @@ auto VulkanRenderer::create_image(void const *data, vk::Extent3D size,
vk::SampleCountFlagBits::e1, mipmapped), vk::SampleCountFlagBits::e1, mipmapped),
}; };
immediate_submit([&](vk::CommandBuffer cmd) { immediate_submit(
[&](vk::CommandBuffer cmd) {
vkutil::transition_image(cmd, new_image.image, vkutil::transition_image(cmd, new_image.image,
vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal); vk::ImageLayout::eUndefined,
vk::ImageLayout::eTransferDstOptimal);
vk::BufferImageCopy copy_region {}; vk::BufferImageCopy copy_region {};
copy_region.imageSubresource.aspectMask copy_region.imageSubresource.aspectMask
@@ -2179,7 +2527,9 @@ auto VulkanRenderer::create_image(void const *data, vk::Extent3D size,
vkutil::transition_image(cmd, new_image.image, vkutil::transition_image(cmd, new_image.image,
vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eTransferDstOptimal,
vk::ImageLayout::eShaderReadOnlyOptimal); vk::ImageLayout::eShaderReadOnlyOptimal);
}); },
/*flush_frame_deletion_queue=*/false,
/*clear_frame_descriptors=*/false);
if (mapped_here) { if (mapped_here) {
vmaUnmapMemory(m_vk.allocator, upload_buffer.allocation); vmaUnmapMemory(m_vk.allocator, upload_buffer.allocation);
@@ -2258,7 +2608,8 @@ auto VulkanRenderer::create_cubemap(std::span<uint8_t const> pixels,
view_ci.subresourceRange.layerCount = 6; view_ci.subresourceRange.layerCount = 6;
new_image.image_view = m_device.createImageView(view_ci); new_image.image_view = m_device.createImageView(view_ci);
immediate_submit([&](vk::CommandBuffer cmd) { immediate_submit(
[&](vk::CommandBuffer cmd) {
vk::ImageMemoryBarrier to_transfer {}; vk::ImageMemoryBarrier to_transfer {};
to_transfer.srcAccessMask = vk::AccessFlagBits::eNone; to_transfer.srcAccessMask = vk::AccessFlagBits::eNone;
to_transfer.dstAccessMask = vk::AccessFlagBits::eTransferWrite; to_transfer.dstAccessMask = vk::AccessFlagBits::eTransferWrite;
@@ -2301,15 +2652,19 @@ auto VulkanRenderer::create_cubemap(std::span<uint8_t const> pixels,
to_read.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; to_read.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
to_read.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; to_read.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
to_read.image = new_image.image; to_read.image = new_image.image;
to_read.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; to_read.subresourceRange.aspectMask
= vk::ImageAspectFlagBits::eColor;
to_read.subresourceRange.baseMipLevel = 0; to_read.subresourceRange.baseMipLevel = 0;
to_read.subresourceRange.levelCount = 1; to_read.subresourceRange.levelCount = 1;
to_read.subresourceRange.baseArrayLayer = 0; to_read.subresourceRange.baseArrayLayer = 0;
to_read.subresourceRange.layerCount = 6; to_read.subresourceRange.layerCount = 6;
cmd.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, cmd.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {}, to_read); vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {},
}); to_read);
},
/*flush_frame_deletion_queue=*/false,
/*clear_frame_descriptors=*/false);
if (mapped_here) { if (mapped_here) {
vmaUnmapMemory(m_vk.allocator, upload_buffer.allocation); vmaUnmapMemory(m_vk.allocator, upload_buffer.allocation);
@@ -2328,6 +2683,12 @@ auto VulkanRenderer::destroy_image(AllocatedImage const &img) -> void
m_vk.allocator, static_cast<VkImage>(img.image), img.allocation); m_vk.allocator, static_cast<VkImage>(img.image), img.allocation);
} }
auto VulkanRenderer::destroy_image_later(AllocatedImage img) -> void
{
m_vk.get_current_frame().deletion_queue.emplace(
[this, img]() { destroy_image(img); });
}
auto VulkanRenderer::create_buffer(size_t alloc_size, auto VulkanRenderer::create_buffer(size_t alloc_size,
vk::BufferUsageFlags usage, VmaMemoryUsage memory_usage) -> AllocatedBuffer vk::BufferUsageFlags usage, VmaMemoryUsage memory_usage) -> AllocatedBuffer
{ {
@@ -2394,7 +2755,8 @@ auto VulkanRenderer::upload_mesh(
void *data = info.pMappedData; void *data = info.pMappedData;
bool mapped_here { false }; bool mapped_here { false };
if (!data) { if (!data) {
VkResult res = vmaMapMemory(m_vk.allocator, staging.allocation, &data); VkResult res { vmaMapMemory(
m_vk.allocator, staging.allocation, &data) };
assert(res == VK_SUCCESS); assert(res == VK_SUCCESS);
mapped_here = true; mapped_here = true;
} }

View File

@@ -7,6 +7,7 @@
#include <mutex> #include <mutex>
#include <optional> #include <optional>
#include <span> #include <span>
#include <string>
#include <variant> #include <variant>
#include <vector> #include <vector>
@@ -130,11 +131,21 @@ struct VulkanRenderer {
MSAA_8X, MSAA_8X,
}; };
VulkanRenderer(SDL_Window *window, Logger &logger); struct KmsSurfaceConfig { };
VulkanRenderer(SDL_Window *window, Logger &logger,
std::span<std::string const> instance_extensions = {},
std::span<std::string const> device_extensions = {});
VulkanRenderer(KmsSurfaceConfig config, Logger &logger,
std::span<std::string const> instance_extensions = {},
std::span<std::string const> device_extensions = {});
~VulkanRenderer(); ~VulkanRenderer();
auto render(std::function<void(GL &)> const &record = {}) -> void; auto render(std::function<void(GL &)> const &record = {}) -> void;
auto render_to_image(vk::Image target_image, vk::Extent2D target_extent,
std::function<void(GL &)> const &record = {}) -> void;
auto resize(uint32_t width, uint32_t height) -> void; auto resize(uint32_t width, uint32_t height) -> void;
auto set_offscreen_extent(vk::Extent2D extent) -> void;
auto set_antialiasing(AntiAliasingKind kind) -> void; auto set_antialiasing(AntiAliasingKind kind) -> void;
auto set_antialiasing_immediate(AntiAliasingKind kind) -> void; auto set_antialiasing_immediate(AntiAliasingKind kind) -> void;
auto antialiasing() const -> AntiAliasingKind auto antialiasing() const -> AntiAliasingKind
@@ -153,6 +164,7 @@ struct VulkanRenderer {
auto create_cubemap(std::span<uint8_t const> pixels, uint32_t face_size, auto create_cubemap(std::span<uint8_t const> pixels, uint32_t face_size,
vk::Format format, vk::ImageUsageFlags flags) -> AllocatedImage; vk::Format format, vk::ImageUsageFlags flags) -> AllocatedImage;
auto destroy_image(AllocatedImage const &img) -> void; auto destroy_image(AllocatedImage const &img) -> void;
auto destroy_image_later(AllocatedImage img) -> void;
auto rectangle_mesh() const -> GPUMeshBuffers const & auto rectangle_mesh() const -> GPUMeshBuffers const &
{ {
return m_vk.rectangle; return m_vk.rectangle;
@@ -175,8 +187,19 @@ struct VulkanRenderer {
} }
auto draw_extent() const -> vk::Extent2D { return m_vk.draw_extent; } auto draw_extent() const -> vk::Extent2D { return m_vk.draw_extent; }
auto mesh_pipeline() -> Pipeline & { return m_vk.mesh_pipeline; } auto mesh_pipeline() -> Pipeline & { return m_vk.mesh_pipeline; }
auto wayland_pipeline() -> Pipeline & { return m_vk.wayland_pipeline; }
auto triangle_pipeline() -> Pipeline & { return m_vk.triangle_pipeline; } auto triangle_pipeline() -> Pipeline & { return m_vk.triangle_pipeline; }
auto device() const -> vk::Device { return m_device; } auto device() const -> vk::Device { return m_device; }
auto instance() const -> vk::Instance { return m_instance; }
auto physical_device() const -> vk::PhysicalDevice
{
return m_physical_device;
}
auto graphics_queue() const -> vk::Queue { return m_vk.graphics_queue; }
auto graphics_queue_family() const -> uint32_t
{
return m_vk.graphics_queue_family;
}
auto draw_image_format() const -> vk::Format auto draw_image_format() const -> vk::Format
{ {
return m_vk.draw_image.format; return m_vk.draw_image.format;
@@ -232,6 +255,7 @@ private:
auto vk_init() -> void; auto vk_init() -> void;
auto swapchain_init() -> void; auto swapchain_init() -> void;
auto setup_kms_surface() -> void;
auto commands_init() -> void; auto commands_init() -> void;
auto sync_init() -> void; auto sync_init() -> void;
auto descriptors_init() -> void; auto descriptors_init() -> void;
@@ -337,6 +361,7 @@ private:
Pipeline triangle_pipeline_culled; Pipeline triangle_pipeline_culled;
Pipeline mesh_pipeline; Pipeline mesh_pipeline;
Pipeline mesh_pipeline_culled; Pipeline mesh_pipeline_culled;
Pipeline wayland_pipeline;
GPUMeshBuffers rectangle; GPUMeshBuffers rectangle;
@@ -359,10 +384,27 @@ private:
vk::UniqueSampler default_sampler_nearest; vk::UniqueSampler default_sampler_nearest;
} m_vk; } m_vk;
struct KmsState {
vk::DisplayKHR display {};
vk::DisplayModeKHR mode {};
vk::Extent2D extent {};
uint32_t plane_index { 0 };
uint32_t plane_stack_index { 0 };
std::string display_name {};
};
SDL_Window *m_window { nullptr }; SDL_Window *m_window { nullptr };
Logger &m_logger; Logger &m_logger;
std::mutex m_command_mutex; std::mutex m_command_mutex;
std::vector<RenderCommand> m_pending_render_commands; std::vector<RenderCommand> m_pending_render_commands;
bool m_use_kms { false };
bool m_imgui_enabled { true };
std::optional<KmsState> m_kms_state {};
vk::PhysicalDevice m_kms_physical_device {};
vk::Extent2D m_kms_extent {};
bool m_kms_physical_device_set { false };
std::vector<std::string> m_extra_instance_extensions {};
std::vector<std::string> m_extra_device_extensions {};
}; };
} // namespace Lunar } // namespace Lunar

View File

@@ -1,7 +1,3 @@
#include "src/Application.h" #include "src/Application.h"
auto main() -> int auto main() -> int { Lunar::Application::the().run(); }
{
Lunar::Application app {};
app.run();
}

113
src/wayland/Client.h Normal file
View File

@@ -0,0 +1,113 @@
#pragma once
#include <cassert>
#include <format>
#include <functional>
#include <optional>
#include <tuple>
#include <wayland-server-core.h>
#include "Display.h"
#include "List.h"
namespace Lunar::Wayland {
struct Client {
Client(wl_client *client)
: m_client { std::move(client) }
{
assert(m_client);
}
~Client() = default;
inline auto c_ptr() const -> wl_client * { return m_client; }
static auto from_link(wl_list *link) -> Client
{
return Client { wl_client_from_link(link) };
}
auto flush() { wl_client_flush(m_client); }
auto get_display() -> Display
{
return Display { wl_client_get_display(m_client) };
}
auto get_credentials() noexcept -> std::tuple<pid_t, uid_t, gid_t>
{
std::tuple<pid_t, uid_t, gid_t> ret {};
auto &[pid, uid, gid] { ret };
wl_client_get_credentials(m_client, &pid, &uid, &gid);
return ret;
}
auto get_fd() noexcept -> int { return wl_client_get_fd(m_client); }
auto get_object(uint32_t id) -> std::optional<wl_resource *>
{
if (auto *res { wl_client_get_object(m_client, id) }; res != NULL) {
return res;
} else {
return {};
}
}
auto post_implementation_error(std::string_view string)
{
wl_client_post_implementation_error(
m_client, "%.*s", static_cast<int>(string.size()), string.data());
}
template<typename... Args>
auto post_implementation_error(
std::format_string<Args...> fmt, Args &&...args)
{
post_implementation_error(
std::format(fmt, std::forward<Args>(args)...));
}
auto add_destroy_listener(wl_listener *listener)
{
wl_client_add_destroy_listener(m_client, listener);
}
auto add_destroy_late_listener(wl_listener *listener)
{
wl_client_add_destroy_late_listener(m_client, listener);
}
auto get_link() -> wl_list * { return wl_client_get_link(m_client); }
auto add_resource_created_listener(wl_listener *listener)
{
wl_client_add_resource_created_listener(m_client, listener);
}
auto for_each_resource(
std::function<wl_iterator_result(wl_resource *)> const &fn) -> void
{
wl_client_for_each_resource(
m_client,
(wl_client_for_each_resource_iterator_func_t)[](
wl_resource * res, void *user_data)
->wl_iterator_result {
auto *f { static_cast<
std::function<wl_iterator_result(wl_resource *)> *>(
user_data) };
return (*f)(res);
},
const_cast<void *>(static_cast<void const *>(&fn)));
}
auto set_max_buffer_size(size_t max_buffer_size)
{
wl_client_set_max_buffer_size(m_client, max_buffer_size);
}
private:
wl_client *m_client {};
};
} // namespace Lunar::Wayland

103
src/wayland/Display.h Normal file
View File

@@ -0,0 +1,103 @@
#pragma once
#include <cassert>
#include <format>
#include <stdexcept>
#include <vector>
#include <wayland-server-core.h>
namespace Lunar::Wayland {
struct Display {
Display()
: m_display(wl_display_create())
{
}
Display(wl_display *display)
: m_display { std::move(display) }
, m_should_cleanup { false }
{
}
~Display()
{
if (!m_should_cleanup)
return;
wl_display_destroy_clients(m_display);
wl_display_destroy(m_display);
}
inline auto c_ptr() const -> wl_display * { return m_display; }
auto set_global_filter(
wl_display_global_filter_func_t filter, void *data) noexcept
{
wl_display_set_global_filter(m_display, filter, data);
}
auto next_serial() noexcept -> uint32_t
{
return wl_display_next_serial(m_display);
}
auto set_default_max_buffer_size(size_t max_buffer_size) noexcept
{
wl_display_set_default_max_buffer_size(m_display, max_buffer_size);
}
auto add_socket_fd(int fd)
{
if (wl_display_add_socket_fd(m_display, fd) == -1) {
throw std::runtime_error(
"Failed to add socket fd to Wayland display");
}
}
auto add_socket(char const *name)
{
if (wl_display_add_socket(m_display, name) == -1) {
throw std::runtime_error(std::format(
"Failed to add socket `{}` to Wayland display", name));
}
}
auto add_protocol_logger(wl_protocol_logger_func_t func, void *user_data)
-> wl_protocol_logger *
{
if (auto *logger {
wl_display_add_protocol_logger(m_display, func, user_data) };
logger != NULL) {
return logger;
} else {
throw std::runtime_error(
"Failed to add protocol logger to Wayland display");
}
}
auto add_shm_format(uint32_t format) -> uint32_t *
{
if (auto *fmt { wl_display_add_shm_format(m_display, format) };
fmt != NULL) {
return fmt;
} else {
throw std::runtime_error(
"Failed to add SHM format to Wayland display");
}
}
auto get_client_list() -> std::vector<wl_client *>
{
std::vector<wl_client *> ret {};
auto const list { wl_display_get_client_list(m_display) };
assert(list);
wl_client *client {};
wl_client_for_each(client, list) { ret.push_back(client); }
return ret;
}
private:
wl_display *m_display {};
bool m_should_cleanup { true };
};
} // namespace Lunar::Wayland

71
src/wayland/Global.h Normal file
View File

@@ -0,0 +1,71 @@
#pragma once
#include <cassert>
#include <optional>
#include <utility>
#include <wayland-server-core.h>
#include "Client.h"
#include "Display.h"
namespace Lunar::Wayland {
struct Global {
Global() = delete;
explicit Global(Display &display, wl_interface const *interface,
int version, void *data, wl_global_bind_func_t bind)
: m_global {
wl_global_create(display.c_ptr(), interface, version, data, bind),
}
{
}
Global(wl_global *global)
: m_global { std::move(global) }
, m_should_cleanup { false }
{
assert(m_global);
}
~Global()
{
if (!m_should_cleanup)
return;
wl_global_destroy(m_global);
}
inline auto c_ptr() const -> wl_global * { return m_global; }
auto get_name(Client &client) const -> std::optional<uint32_t>
{
if (auto const ret { wl_global_get_name(m_global, client.c_ptr()) };
ret != 0) {
return ret;
} else {
return {};
}
}
inline auto get_version() const -> uint32_t
{
return wl_global_get_version(m_global);
}
inline auto get_display() const -> Display
{
return wl_global_get_display(m_global);
}
inline auto get_interface() const -> wl_interface const *
{
return wl_global_get_interface(m_global);
}
private:
wl_global *m_global {};
bool m_should_cleanup { true };
};
} // namespace Lunar::Wayland

287
src/wayland/List.h Normal file
View File

@@ -0,0 +1,287 @@
#pragma once
#include <cstddef>
#include <iterator>
#include <wayland-server-core.h>
namespace Lunar::Wayland {
namespace detail {
template<typename T, wl_list T::*Member>
constexpr std::ptrdiff_t member_offset() noexcept
{
return reinterpret_cast<std::ptrdiff_t>(
&(reinterpret_cast<T const volatile *>(0)->*Member));
}
template<typename T, wl_list T::*Member>
inline T *container_of(wl_list *node) noexcept
{
auto *p { reinterpret_cast<std::byte *>(node)
- member_offset<T, Member>() };
return reinterpret_cast<T *>(p);
}
template<typename T, wl_list T::*Member>
inline T const *container_of(wl_list const *node) noexcept
{
auto *p { reinterpret_cast<std::byte const *>(node)
- member_offset<T, Member>() };
return reinterpret_cast<T const *>(p);
}
} // namespace detail
template<typename T, wl_list T::*Member> struct List {
struct Iterator {
using iterator_category = std::bidirectional_iterator_tag;
using value_type = T;
using difference_type = std::ptrdiff_t;
using pointer = T *;
using reference = T &;
Iterator() = default;
Iterator(wl_list *cur, wl_list *head)
: m_cur(cur)
, m_head(head)
{
}
auto operator*() const -> reference
{
return *detail::container_of<T, Member>(m_cur);
}
auto operator->() const -> pointer
{
return detail::container_of<T, Member>(m_cur);
}
auto operator++() -> Iterator &
{
m_cur = m_cur->next;
return *this;
}
auto operator++(int) -> Iterator
{
auto t { *this };
++(*this);
return t;
}
auto operator--() -> Iterator &
{
m_cur = (m_cur == m_head) ? m_head->prev : m_cur->prev;
return *this;
}
auto operator--(int) -> Iterator
{
auto t { *this };
--(*this);
return t;
}
friend auto operator==(Iterator a, Iterator b) -> bool
{
return a.m_cur == b.m_cur;
}
friend auto operator!=(Iterator a, Iterator b) -> bool
{
return !(a == b);
}
private:
wl_list *m_cur { nullptr };
wl_list *m_head { nullptr };
};
struct ConstIterator {
using iterator_category = std::bidirectional_iterator_tag;
using value_type = T const;
using difference_type = std::ptrdiff_t;
using pointer = T const *;
using reference = T const &;
ConstIterator() = default;
ConstIterator(wl_list const *cur, wl_list const *head)
: m_cur(cur)
, m_head(head)
{
}
auto operator*() const -> reference
{
return *detail::container_of<T, Member>(m_cur);
}
auto operator->() const -> pointer
{
return detail::container_of<T, Member>(m_cur);
}
auto operator++() -> ConstIterator &
{
m_cur = m_cur->next;
return *this;
}
auto operator++(int) -> ConstIterator
{
auto t { *this };
++(*this);
return t;
}
auto operator--() -> ConstIterator &
{
m_cur = (m_cur == m_head) ? m_head->prev : m_cur->prev;
return *this;
}
auto operator--(int) -> ConstIterator
{
auto t { *this };
--(*this);
return t;
}
friend auto operator==(ConstIterator a, ConstIterator b) -> bool
{
return a.m_cur == b.m_cur;
}
friend auto operator!=(ConstIterator a, ConstIterator b) -> bool
{
return !(a == b);
}
private:
wl_list const *m_cur { nullptr };
wl_list const *m_head { nullptr };
};
List()
: m_head {}
, m_external_head { nullptr }
, m_should_cleanup { true }
{
wl_list_init(&m_head);
}
explicit List(
wl_list *existing_head, bool should_cleanup = false, bool init = false)
: m_head {}
, m_external_head { existing_head }
, m_should_cleanup { should_cleanup }
{
if (init && m_external_head)
wl_list_init(m_external_head);
}
~List()
{
if (!m_should_cleanup)
return;
clear();
if (auto *h { head_ptr() }; h)
wl_list_init(h);
}
List(List const &) = delete;
auto operator=(List const &) -> List & = delete;
List(List &&other) noexcept { move_from(other); }
auto operator=(List &&other) noexcept -> List &
{
if (this != &other) {
this->~List();
move_from(other);
}
return *this;
}
inline auto c_ptr() -> wl_list * { return head_ptr(); }
inline auto c_ptr() const -> wl_list const * { return head_ptr(); }
auto empty() const noexcept -> bool { return wl_list_empty(head_ptr()); }
auto length() const noexcept -> int { return wl_list_length(head_ptr()); }
auto push_front(T *elem) noexcept -> void
{
wl_list_insert(head_ptr(), &(elem->*Member));
}
auto push_back(T *elem) noexcept -> void
{
auto *h { head_ptr() };
wl_list_insert(h->prev, &(elem->*Member));
}
auto remove(T *elem) noexcept -> void
{
wl_list_remove(&(elem->*Member));
wl_list_init(&(elem->*Member));
}
auto clear() noexcept -> void
{
auto *h { head_ptr() };
while (!wl_list_empty(h)) {
auto *node { h->next };
wl_list_remove(node);
wl_list_init(node);
}
}
auto begin() noexcept -> Iterator
{
auto *h { head_ptr() };
return Iterator(h->next, h);
}
auto end() noexcept -> Iterator
{
auto *h { head_ptr() };
return Iterator(h, h);
}
auto begin() const noexcept -> ConstIterator
{
auto const *h { head_ptr() };
return ConstIterator(h->next, h);
}
auto end() const noexcept -> ConstIterator
{
auto const *h { head_ptr() };
return ConstIterator(h, h);
}
private:
auto head_ptr() noexcept -> wl_list *
{
return m_external_head ? m_external_head : &m_head;
}
auto head_ptr() const noexcept -> wl_list const *
{
return m_external_head ? m_external_head : &m_head;
}
auto move_from(List &other) noexcept -> void
{
m_head = other.m_head;
m_external_head = other.m_external_head;
m_should_cleanup = other.m_should_cleanup;
other.m_external_head = nullptr;
other.m_should_cleanup = false;
wl_list_init(&other.m_head);
}
private:
wl_list m_head {};
wl_list *m_external_head { nullptr };
bool m_should_cleanup { true };
};
} // namespace Lunar::Wayland

43
src/wayland/Region.h Normal file
View File

@@ -0,0 +1,43 @@
#pragma once
#include <cstdint>
#include <vector>
#include <wayland-server-core.h>
namespace Lunar::Wayland {
struct Region {
struct Box {
std::int32_t x {};
std::int32_t y {};
std::int32_t width {};
std::int32_t height {};
};
explicit Region(wl_resource *resource)
: m_resource(resource)
{
}
auto resource() const -> wl_resource * { return m_resource; }
auto add(std::int32_t x, std::int32_t y, std::int32_t width,
std::int32_t height) -> void
{
m_boxes.push_back(Box { x, y, width, height });
}
auto subtract(std::int32_t x, std::int32_t y, std::int32_t width,
std::int32_t height) -> void
{
m_subtract_boxes.push_back(Box { x, y, width, height });
}
private:
wl_resource *m_resource {};
std::vector<Box> m_boxes {};
std::vector<Box> m_subtract_boxes {};
};
} // namespace Lunar::Wayland

96
src/wayland/Shm.cpp Normal file
View File

@@ -0,0 +1,96 @@
#include "Shm.h"
#include <cerrno>
#include <cstring>
#include <sys/mman.h>
#include <unistd.h>
#include <utility>
namespace Lunar::Wayland {
ShmPool::ShmPool(int fd, std::size_t size, Logger &logger)
: m_logger(logger)
, m_fd(fd)
, m_size(size)
{
m_data = mmap(nullptr, m_size, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd, 0);
if (m_data == MAP_FAILED) {
m_data = nullptr;
m_logger.err("Failed to mmap shm pool: {}", std::strerror(errno));
}
}
ShmPool::~ShmPool()
{
if (m_data) {
munmap(m_data, m_size);
}
if (m_fd >= 0) {
close(m_fd);
}
}
auto ShmPool::resize(std::size_t new_size) -> bool
{
if (!m_data) {
return false;
}
void *new_data = mremap(m_data, m_size, new_size, MREMAP_MAYMOVE);
if (new_data == MAP_FAILED) {
m_logger.err("Failed to resize shm pool: {}", std::strerror(errno));
return false;
}
m_data = new_data;
m_size = new_size;
return true;
}
auto ShmPool::data() const -> std::byte *
{
return static_cast<std::byte *>(m_data);
}
ShmBuffer::ShmBuffer(std::shared_ptr<ShmPool> pool, wl_resource *resource,
std::int32_t offset, std::int32_t width, std::int32_t height,
std::int32_t stride, std::uint32_t format)
: pool(std::move(pool))
, resource(resource)
, offset(offset)
, width(width)
, height(height)
, stride(stride)
, format(format)
{
}
auto ShmBuffer::data() const -> std::byte *
{
if (!pool || !pool->data()) {
return nullptr;
}
return pool->data() + offset;
}
auto ShmBuffer::byte_size() const -> std::size_t
{
if (height <= 0 || stride <= 0) {
return 0;
}
return static_cast<std::size_t>(height) * static_cast<std::size_t>(stride);
}
auto shm_buffer_from_resource(wl_resource *resource)
-> std::shared_ptr<ShmBuffer>
{
if (!resource) {
return {};
}
auto *handle { static_cast<std::shared_ptr<ShmBuffer> *>(
wl_resource_get_user_data(resource)) };
if (!handle) {
return {};
}
return *handle;
}
} // namespace Lunar::Wayland

48
src/wayland/Shm.h Normal file
View File

@@ -0,0 +1,48 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <memory>
#include <wayland-server-core.h>
#include "../Logger.h"
namespace Lunar::Wayland {
struct ShmPool {
explicit ShmPool(int fd, std::size_t size, Logger &logger);
~ShmPool();
auto resize(std::size_t new_size) -> bool;
auto data() const -> std::byte *;
auto size() const -> std::size_t { return m_size; }
private:
Logger &m_logger;
int m_fd { -1 };
std::size_t m_size { 0 };
void *m_data { nullptr };
};
struct ShmBuffer {
ShmBuffer(std::shared_ptr<ShmPool> pool, wl_resource *resource,
std::int32_t offset, std::int32_t width, std::int32_t height,
std::int32_t stride, std::uint32_t format);
auto data() const -> std::byte *;
auto byte_size() const -> std::size_t;
std::shared_ptr<ShmPool> pool;
wl_resource *resource {};
std::int32_t offset {};
std::int32_t width {};
std::int32_t height {};
std::int32_t stride {};
std::uint32_t format {};
};
auto shm_buffer_from_resource(wl_resource *resource)
-> std::shared_ptr<ShmBuffer>;
} // namespace Lunar::Wayland

28
src/wayland/Signal.h Normal file
View File

@@ -0,0 +1,28 @@
#pragma once
#include <cassert>
#include <wayland-server-core.h>
namespace Lunar::Wayland {
struct Signal {
Signal(wl_signal *signal)
: m_signal { std::move(signal) }
{
assert(m_signal);
}
~Signal() = default;
inline auto c_ptr() const -> wl_signal * { return m_signal; }
template<typename T = void> auto flush(T *data)
{
wl_signal_emit_mutable(m_signal, (void *)data);
}
private:
wl_signal *m_signal {};
};
} // namespace Lunar::Wayland

98
src/wayland/Surface.cpp Normal file
View File

@@ -0,0 +1,98 @@
#include "Surface.h"
#include <algorithm>
#include <utility>
#include "WaylandServer.h"
#include "wayland-server-protocol.h"
namespace Lunar::Wayland {
Surface::Surface(WaylandServer &server, wl_resource *resource)
: m_server(server)
, m_resource(resource)
{
m_server.register_surface(this);
}
Surface::~Surface()
{
m_server.unregister_surface(this);
for (auto *callback : m_frame_callbacks) {
if (callback) {
wl_callback_send_done(callback, m_server.now_ms());
wl_resource_destroy(callback);
}
}
}
auto Surface::attach(
std::shared_ptr<ShmBuffer> buffer, std::int32_t x, std::int32_t y) -> void
{
m_pending_buffer = std::move(buffer);
m_pending_offset_x = x;
m_pending_offset_y = y;
}
auto Surface::damage(std::int32_t, std::int32_t, std::int32_t, std::int32_t)
-> void
{
}
auto Surface::damage_buffer(
std::int32_t, std::int32_t, std::int32_t, std::int32_t) -> void
{
}
auto Surface::frame(wl_resource *callback) -> void
{
if (!callback) {
return;
}
m_frame_callbacks.push_back(callback);
}
auto Surface::commit() -> void
{
auto previous { m_current_buffer };
m_current_buffer = m_pending_buffer;
m_pending_buffer.reset();
if (previous && previous != m_current_buffer && previous->resource) {
wl_buffer_send_release(previous->resource);
}
if (!m_frame_callbacks.empty()) {
auto callbacks { std::move(m_frame_callbacks) };
auto done_time { m_server.now_ms() };
for (auto *callback : callbacks) {
if (callback) {
wl_callback_send_done(callback, done_time);
wl_resource_destroy(callback);
}
}
}
}
auto Surface::set_opaque_region(std::shared_ptr<Region> region) -> void
{
m_opaque_region = std::move(region);
}
auto Surface::set_input_region(std::shared_ptr<Region> region) -> void
{
m_input_region = std::move(region);
}
auto Surface::set_buffer_transform(std::int32_t transform) -> void
{
m_buffer_transform = transform;
}
auto Surface::set_buffer_scale(std::int32_t scale) -> void
{
m_buffer_scale = std::max(1, scale);
}
} // namespace Lunar::Wayland

53
src/wayland/Surface.h Normal file
View File

@@ -0,0 +1,53 @@
#pragma once
#include <cstdint>
#include <memory>
#include <vector>
#include <wayland-server-core.h>
#include "Region.h"
#include "Shm.h"
namespace Lunar::Wayland {
struct WaylandServer;
struct Surface {
explicit Surface(WaylandServer &server, wl_resource *resource);
~Surface();
auto resource() const -> wl_resource * { return m_resource; }
auto current_buffer() const -> std::shared_ptr<ShmBuffer> const &
{
return m_current_buffer;
}
auto attach(std::shared_ptr<ShmBuffer> buffer, std::int32_t x,
std::int32_t y) -> void;
auto damage(std::int32_t x, std::int32_t y, std::int32_t width,
std::int32_t height) -> void;
auto damage_buffer(std::int32_t x, std::int32_t y, std::int32_t width,
std::int32_t height) -> void;
auto frame(wl_resource *callback) -> void;
auto commit() -> void;
auto set_opaque_region(std::shared_ptr<Region> region) -> void;
auto set_input_region(std::shared_ptr<Region> region) -> void;
auto set_buffer_transform(std::int32_t transform) -> void;
auto set_buffer_scale(std::int32_t scale) -> void;
private:
WaylandServer &m_server;
wl_resource *m_resource {};
std::shared_ptr<ShmBuffer> m_pending_buffer {};
std::shared_ptr<ShmBuffer> m_current_buffer {};
std::shared_ptr<Region> m_opaque_region {};
std::shared_ptr<Region> m_input_region {};
std::vector<wl_resource *> m_frame_callbacks {};
std::int32_t m_buffer_transform { 0 };
std::int32_t m_buffer_scale { 1 };
std::int32_t m_pending_offset_x { 0 };
std::int32_t m_pending_offset_y { 0 };
};
} // namespace Lunar::Wayland

View File

@@ -0,0 +1,81 @@
#include "WaylandServer.h"
#include <algorithm>
#include <chrono>
#include <span>
#include <stdexcept>
namespace Lunar::Wayland {
WaylandServer::WaylandServer(Logger &logger)
: m_logger(logger)
{
m_loop = wl_display_get_event_loop(m_display.c_ptr());
if (!m_loop) {
throw std::runtime_error("Failed to get Wayland event loop");
}
auto *socket_name { wl_display_add_socket_auto(m_display.c_ptr()) };
if (!socket_name) {
throw std::runtime_error("Failed to create Wayland socket");
}
if (socket_name) {
m_socket_name = socket_name;
}
m_logger.info("Wayland listening on {}", m_socket_name);
m_compositor_global = create_compositor_global();
m_shm_global = create_shm_global();
m_xdg_wm_base_global = create_xdg_wm_base_global();
}
WaylandServer::~WaylandServer() = default;
auto WaylandServer::dispatch() -> void
{
if (!m_loop) {
return;
}
wl_event_loop_dispatch(m_loop, 0);
}
auto WaylandServer::flush() -> void
{
wl_display_flush_clients(m_display.c_ptr());
}
auto WaylandServer::now_ms() const -> std::uint32_t
{
using Clock = std::chrono::steady_clock;
auto now { Clock::now().time_since_epoch() };
auto ms {
std::chrono::duration_cast<std::chrono::milliseconds>(now).count()
};
return static_cast<std::uint32_t>(ms);
}
auto WaylandServer::register_surface(Surface *surface) -> void
{
if (!surface) {
return;
}
m_surfaces.push_back(surface);
}
auto WaylandServer::unregister_surface(Surface *surface) -> void
{
if (!surface) {
return;
}
auto it { std::remove(m_surfaces.begin(), m_surfaces.end(), surface) };
m_surfaces.erase(it, m_surfaces.end());
}
auto WaylandServer::surfaces() const -> std::span<Surface *const>
{
return { m_surfaces.data(), m_surfaces.size() };
}
} // namespace Lunar::Wayland
#include "Surface.h"

View File

@@ -0,0 +1,50 @@
#pragma once
#include <cstdint>
#include <memory>
#include <span>
#include <string>
#include <string_view>
#include <vector>
#include <wayland-server-core.h>
#include "../Logger.h"
#include "Display.h"
#include "Global.h"
namespace Lunar::Wayland {
struct Surface;
struct WaylandServer {
explicit WaylandServer(Logger &logger);
~WaylandServer();
auto display() -> Display & { return m_display; }
auto logger() -> Logger & { return m_logger; }
auto socket_name() const -> std::string_view { return m_socket_name; }
auto dispatch() -> void;
auto flush() -> void;
auto now_ms() const -> std::uint32_t;
auto register_surface(Surface *surface) -> void;
auto unregister_surface(Surface *surface) -> void;
auto surfaces() const -> std::span<Surface *const>;
private:
auto create_compositor_global() -> std::unique_ptr<Global>;
auto create_shm_global() -> std::unique_ptr<Global>;
auto create_xdg_wm_base_global() -> std::unique_ptr<Global>;
Logger &m_logger;
Display m_display {};
wl_event_loop *m_loop { nullptr };
std::string m_socket_name {};
std::unique_ptr<Global> m_compositor_global {};
std::unique_ptr<Global> m_shm_global {};
std::unique_ptr<Global> m_xdg_wm_base_global {};
std::vector<Surface *> m_surfaces {};
};
} // namespace Lunar::Wayland

View File

@@ -0,0 +1,254 @@
#include <algorithm>
#include <cstdint>
#include <memory>
#include <utility>
#include <wayland-server-core.h>
#include "wayland-server-protocol.h"
#include "../Region.h"
#include "../Shm.h"
#include "../Surface.h"
#include "../WaylandServer.h"
namespace Lunar::Wayland {
namespace {
constexpr std::uint32_t COMPOSITOR_VERSION = 4;
auto resource_version(wl_resource *resource) -> std::uint32_t
{
return std::min<std::uint32_t>(
wl_resource_get_version(resource), COMPOSITOR_VERSION);
}
auto region_from_resource(wl_resource *resource) -> std::shared_ptr<Region>
{
if (!resource) {
return {};
}
auto *handle { static_cast<std::shared_ptr<Region> *>(
wl_resource_get_user_data(resource)) };
if (!handle) {
return {};
}
return *handle;
}
void region_destroy(wl_resource *resource)
{
auto *handle { static_cast<std::shared_ptr<Region> *>(
wl_resource_get_user_data(resource)) };
delete handle;
}
void region_handle_destroy(wl_client *, wl_resource *resource)
{
wl_resource_destroy(resource);
}
void region_handle_add(wl_client *, wl_resource *resource, std::int32_t x,
std::int32_t y, std::int32_t width, std::int32_t height)
{
if (auto region { region_from_resource(resource) }) {
region->add(x, y, width, height);
}
}
void region_handle_subtract(wl_client *, wl_resource *resource, std::int32_t x,
std::int32_t y, std::int32_t width, std::int32_t height)
{
if (auto region { region_from_resource(resource) }) {
region->subtract(x, y, width, height);
}
}
struct wl_region_interface const REGION_INTERFACE = {
.destroy = region_handle_destroy,
.add = region_handle_add,
.subtract = region_handle_subtract,
};
auto surface_from_resource(wl_resource *resource) -> Surface *
{
return static_cast<Surface *>(wl_resource_get_user_data(resource));
}
void surface_destroy_resource(wl_resource *resource)
{
auto *surface { surface_from_resource(resource) };
delete surface;
}
void surface_handle_destroy(wl_client *, wl_resource *resource)
{
wl_resource_destroy(resource);
}
void surface_handle_attach(wl_client *, wl_resource *resource,
wl_resource *buffer_resource, std::int32_t x, std::int32_t y)
{
auto *surface { surface_from_resource(resource) };
if (!surface) {
return;
}
surface->attach(shm_buffer_from_resource(buffer_resource), x, y);
}
void surface_handle_damage(wl_client *, wl_resource *resource, std::int32_t x,
std::int32_t y, std::int32_t width, std::int32_t height)
{
if (auto *surface { surface_from_resource(resource) }) {
surface->damage(x, y, width, height);
}
}
void surface_handle_frame(
wl_client *client, wl_resource *resource, std::uint32_t callback_id)
{
auto *surface { surface_from_resource(resource) };
if (!surface) {
return;
}
auto version { wl_resource_get_version(resource) };
auto *callback_resource { wl_resource_create(
client, &wl_callback_interface, version, callback_id) };
if (!callback_resource) {
return;
}
wl_resource_set_implementation(
callback_resource, nullptr, nullptr, nullptr);
surface->frame(callback_resource);
}
void surface_handle_set_opaque_region(
wl_client *, wl_resource *resource, wl_resource *region_resource)
{
if (auto *surface { surface_from_resource(resource) }) {
surface->set_opaque_region(region_from_resource(region_resource));
}
}
void surface_handle_set_input_region(
wl_client *, wl_resource *resource, wl_resource *region_resource)
{
if (auto *surface { surface_from_resource(resource) }) {
surface->set_input_region(region_from_resource(region_resource));
}
}
void surface_handle_commit(wl_client *, wl_resource *resource)
{
if (auto *surface { surface_from_resource(resource) }) {
surface->commit();
}
}
void surface_handle_set_buffer_transform(
wl_client *, wl_resource *resource, std::int32_t transform)
{
if (auto *surface { surface_from_resource(resource) }) {
surface->set_buffer_transform(transform);
}
}
void surface_handle_set_buffer_scale(
wl_client *, wl_resource *resource, std::int32_t scale)
{
if (auto *surface { surface_from_resource(resource) }) {
surface->set_buffer_scale(scale);
}
}
void surface_handle_damage_buffer(wl_client *, wl_resource *resource,
std::int32_t x, std::int32_t y, std::int32_t width, std::int32_t height)
{
if (auto *surface { surface_from_resource(resource) }) {
surface->damage_buffer(x, y, width, height);
}
}
void surface_handle_offset(
wl_client *, wl_resource *resource, std::int32_t x, std::int32_t y)
{
(void)resource;
(void)x;
(void)y;
}
struct wl_surface_interface const SURFACE_INTERFACE = {
.destroy = surface_handle_destroy,
.attach = surface_handle_attach,
.damage = surface_handle_damage,
.frame = surface_handle_frame,
.set_opaque_region = surface_handle_set_opaque_region,
.set_input_region = surface_handle_set_input_region,
.commit = surface_handle_commit,
.set_buffer_transform = surface_handle_set_buffer_transform,
.set_buffer_scale = surface_handle_set_buffer_scale,
.damage_buffer = surface_handle_damage_buffer,
.offset = surface_handle_offset,
};
void compositor_handle_create_surface(
wl_client *client, wl_resource *resource, std::uint32_t id)
{
auto *server { static_cast<WaylandServer *>(
wl_resource_get_user_data(resource)) };
if (!server) {
return;
}
auto version { resource_version(resource) };
auto *surface_resource { wl_resource_create(
client, &wl_surface_interface, version, id) };
if (!surface_resource) {
return;
}
auto *surface { new Surface(*server, surface_resource) };
wl_resource_set_implementation(surface_resource, &SURFACE_INTERFACE,
surface, surface_destroy_resource);
}
void compositor_handle_create_region(
wl_client *client, wl_resource *resource, std::uint32_t id)
{
auto version { wl_resource_get_version(resource) };
auto *region_resource { wl_resource_create(
client, &wl_region_interface, version, id) };
if (!region_resource) {
return;
}
auto region { std::make_shared<Region>(region_resource) };
auto *handle { new std::shared_ptr<Region>(std::move(region)) };
wl_resource_set_implementation(
region_resource, &REGION_INTERFACE, handle, region_destroy);
}
struct wl_compositor_interface const COMPOSITOR_INTERFACE = {
.create_surface = compositor_handle_create_surface,
.create_region = compositor_handle_create_region,
};
void bind_compositor(
wl_client *client, void *data, std::uint32_t version, std::uint32_t id)
{
auto *server { static_cast<WaylandServer *>(data) };
auto *resource { wl_resource_create(client, &wl_compositor_interface,
std::min(version, COMPOSITOR_VERSION), id) };
if (!resource) {
return;
}
wl_resource_set_implementation(
resource, &COMPOSITOR_INTERFACE, server, nullptr);
}
} // namespace
auto WaylandServer::create_compositor_global() -> std::unique_ptr<Global>
{
return std::make_unique<Global>(display(), &wl_compositor_interface,
COMPOSITOR_VERSION, this, bind_compositor);
}
} // namespace Lunar::Wayland

View File

@@ -0,0 +1,171 @@
#include <algorithm>
#include <cstdint>
#include <memory>
#include <unistd.h>
#include <utility>
#include <wayland-server-core.h>
#include "wayland-server-protocol.h"
#include "../Shm.h"
#include "../WaylandServer.h"
namespace Lunar::Wayland {
namespace {
constexpr std::uint32_t SHM_VERSION = 2;
auto shm_pool_from_resource(wl_resource *resource) -> std::shared_ptr<ShmPool>
{
auto *handle { static_cast<std::shared_ptr<ShmPool> *>(
wl_resource_get_user_data(resource)) };
if (!handle) {
return {};
}
return *handle;
}
void shm_pool_destroy_resource(wl_resource *resource)
{
auto *handle { static_cast<std::shared_ptr<ShmPool> *>(
wl_resource_get_user_data(resource)) };
delete handle;
}
void shm_buffer_destroy_resource(wl_resource *resource)
{
auto *handle { static_cast<std::shared_ptr<ShmBuffer> *>(
wl_resource_get_user_data(resource)) };
delete handle;
}
void shm_pool_handle_destroy(wl_client *, wl_resource *resource)
{
wl_resource_destroy(resource);
}
void shm_pool_handle_resize(
wl_client *, wl_resource *resource, std::int32_t size)
{
auto pool { shm_pool_from_resource(resource) };
if (!pool) {
return;
}
if (size <= 0) {
return;
}
pool->resize(static_cast<std::size_t>(size));
}
void shm_handle_release(wl_client *, wl_resource *) { }
void shm_pool_handle_create_buffer(wl_client *client, wl_resource *resource,
std::uint32_t id, std::int32_t offset, std::int32_t width,
std::int32_t height, std::int32_t stride, std::uint32_t format)
{
auto pool { shm_pool_from_resource(resource) };
if (!pool) {
return;
}
if (width <= 0 || height <= 0 || stride <= 0 || offset < 0) {
wl_resource_post_error(resource, WL_SHM_ERROR_INVALID_STRIDE,
"Invalid shm buffer geometry");
return;
}
if (format != WL_SHM_FORMAT_XRGB8888 && format != WL_SHM_FORMAT_ARGB8888) {
wl_resource_post_error(
resource, WL_SHM_ERROR_INVALID_FORMAT, "Unsupported shm format");
return;
}
auto required { static_cast<std::size_t>(offset)
+ static_cast<std::size_t>(height) * static_cast<std::size_t>(stride) };
if (required > pool->size()) {
wl_resource_post_error(resource, WL_SHM_ERROR_INVALID_STRIDE,
"Shm buffer size out of bounds");
return;
}
auto *buffer_resource { wl_resource_create(
client, &wl_buffer_interface, 1, id) };
if (!buffer_resource) {
return;
}
auto buffer { std::make_shared<ShmBuffer>(
pool, buffer_resource, offset, width, height, stride, format) };
auto *handle { new std::shared_ptr<ShmBuffer>(std::move(buffer)) };
wl_resource_set_implementation(
buffer_resource, nullptr, handle, shm_buffer_destroy_resource);
}
struct wl_shm_pool_interface const SHM_POOL_INTERFACE = {
.create_buffer = shm_pool_handle_create_buffer,
.destroy = shm_pool_handle_destroy,
.resize = shm_pool_handle_resize,
};
void shm_handle_create_pool(wl_client *client, wl_resource *resource,
std::uint32_t id, int fd, std::int32_t size)
{
auto *server
= static_cast<WaylandServer *>(wl_resource_get_user_data(resource));
if (!server) {
return;
}
if (size <= 0) {
wl_resource_post_error(
resource, WL_SHM_ERROR_INVALID_STRIDE, "Invalid shm pool size");
close(fd);
return;
}
auto pool { std::make_shared<ShmPool>(
fd, static_cast<std::size_t>(size), server->logger()) };
if (!pool->data()) {
wl_resource_post_error(
resource, WL_SHM_ERROR_INVALID_FD, "Failed to mmap shm pool");
return;
}
auto *pool_resource
= wl_resource_create(client, &wl_shm_pool_interface, 1, id);
if (!pool_resource) {
return;
}
auto *handle { new std::shared_ptr<ShmPool>(std::move(pool)) };
wl_resource_set_implementation(
pool_resource, &SHM_POOL_INTERFACE, handle, shm_pool_destroy_resource);
}
struct wl_shm_interface const SHM_INTERFACE = {
.create_pool = shm_handle_create_pool,
.release = shm_handle_release,
};
void bind_shm(
wl_client *client, void *data, std::uint32_t version, std::uint32_t id)
{
auto *server { static_cast<WaylandServer *>(data) };
auto *resource { wl_resource_create(
client, &wl_shm_interface, std::min(version, SHM_VERSION), id) };
if (!resource) {
return;
}
wl_resource_set_implementation(resource, &SHM_INTERFACE, server, nullptr);
wl_shm_send_format(resource, WL_SHM_FORMAT_XRGB8888);
wl_shm_send_format(resource, WL_SHM_FORMAT_ARGB8888);
}
} // namespace
auto WaylandServer::create_shm_global() -> std::unique_ptr<Global>
{
return std::make_unique<Global>(
display(), &wl_shm_interface, SHM_VERSION, this, bind_shm);
}
} // namespace Lunar::Wayland

View File

@@ -0,0 +1,327 @@
#include <algorithm>
#include <cstdint>
#include <wayland-server-core.h>
#include "xdg-shell-server-protocol.h"
#include "../WaylandServer.h"
namespace Lunar::Wayland {
namespace {
constexpr std::uint32_t XDG_WM_BASE_VERSION = 7;
struct XdgSurface {
WaylandServer &server;
wl_resource *resource {};
wl_resource *surface_resource {};
wl_resource *toplevel_resource {};
std::uint32_t last_serial { 0 };
};
struct XdgToplevel {
WaylandServer &server;
wl_resource *resource {};
XdgSurface *surface {};
};
extern struct xdg_toplevel_interface const XDG_TOPLEVEL_INTERFACE_IMPL;
auto xdg_surface_from_resource(wl_resource *resource) -> XdgSurface *
{
return static_cast<XdgSurface *>(wl_resource_get_user_data(resource));
}
auto xdg_toplevel_from_resource(wl_resource *resource) -> XdgToplevel *
{
return static_cast<XdgToplevel *>(wl_resource_get_user_data(resource));
}
auto send_configure(XdgSurface &surface) -> void
{
std::uint32_t serial = surface.server.display().next_serial();
if (surface.toplevel_resource) {
wl_array states;
wl_array_init(&states);
xdg_toplevel_send_configure(surface.toplevel_resource, 0, 0, &states);
wl_array_release(&states);
}
surface.last_serial = serial;
xdg_surface_send_configure(surface.resource, serial);
}
auto xdg_positioner_handle_destroy(wl_client *, wl_resource *resource) -> void
{
wl_resource_destroy(resource);
}
auto xdg_positioner_handle_set_size(
wl_client *, wl_resource *, std::int32_t, std::int32_t) -> void
{
}
auto xdg_positioner_handle_set_anchor_rect(wl_client *, wl_resource *,
std::int32_t, std::int32_t, std::int32_t, std::int32_t) -> void
{
}
auto xdg_positioner_handle_set_anchor(wl_client *, wl_resource *, std::uint32_t)
-> void
{
}
auto xdg_positioner_handle_set_gravity(
wl_client *, wl_resource *, std::uint32_t) -> void
{
}
auto xdg_positioner_handle_set_constraint_adjustment(
wl_client *, wl_resource *, std::uint32_t) -> void
{
}
auto xdg_positioner_handle_set_offset(
wl_client *, wl_resource *, std::int32_t, std::int32_t) -> void
{
}
auto xdg_positioner_handle_set_reactive(wl_client *, wl_resource *) -> void { }
auto xdg_positioner_handle_set_parent_size(
wl_client *, wl_resource *, std::int32_t, std::int32_t) -> void
{
}
auto xdg_positioner_handle_set_parent_configure(
wl_client *, wl_resource *, std::uint32_t) -> void
{
}
struct xdg_positioner_interface const XDG_POSITIONER_INTERFACE_IMPL = {
.destroy = xdg_positioner_handle_destroy,
.set_size = xdg_positioner_handle_set_size,
.set_anchor_rect = xdg_positioner_handle_set_anchor_rect,
.set_anchor = xdg_positioner_handle_set_anchor,
.set_gravity = xdg_positioner_handle_set_gravity,
.set_constraint_adjustment
= xdg_positioner_handle_set_constraint_adjustment,
.set_offset = xdg_positioner_handle_set_offset,
.set_reactive = xdg_positioner_handle_set_reactive,
.set_parent_size = xdg_positioner_handle_set_parent_size,
.set_parent_configure = xdg_positioner_handle_set_parent_configure,
};
auto xdg_surface_destroy_resource(wl_resource *resource) -> void
{
auto *surface { xdg_surface_from_resource(resource) };
delete surface;
}
auto xdg_surface_handle_destroy(wl_client *, wl_resource *resource) -> void
{
wl_resource_destroy(resource);
}
auto xdg_surface_handle_get_toplevel(
wl_client *client, wl_resource *resource, std::uint32_t id) -> void
{
auto *surface { xdg_surface_from_resource(resource) };
if (!surface) {
return;
}
auto version { wl_resource_get_version(resource) };
auto *toplevel_resource { wl_resource_create(
client, &::xdg_toplevel_interface, version, id) };
if (!toplevel_resource) {
return;
}
auto *toplevel { new XdgToplevel {
surface->server, toplevel_resource, surface } };
surface->toplevel_resource = toplevel_resource;
wl_resource_set_implementation(toplevel_resource,
&XDG_TOPLEVEL_INTERFACE_IMPL, toplevel, [](wl_resource *res) {
auto *tl { xdg_toplevel_from_resource(res) };
if (tl && tl->surface && tl->surface->toplevel_resource == res) {
tl->surface->toplevel_resource = nullptr;
}
delete tl;
});
send_configure(*surface);
}
auto xdg_surface_handle_get_popup(wl_client *, wl_resource *, std::uint32_t,
wl_resource *, wl_resource *) -> void
{
}
auto xdg_surface_handle_set_window_geometry(wl_client *, wl_resource *,
std::int32_t, std::int32_t, std::int32_t, std::int32_t) -> void
{
}
auto xdg_surface_handle_ack_configure(
wl_client *, wl_resource *resource, std::uint32_t serial) -> void
{
if (auto *surface { xdg_surface_from_resource(resource) }) {
surface->last_serial = serial;
}
}
struct xdg_surface_interface const XDG_SURFACE_INTERFACE_IMPL = {
.destroy = xdg_surface_handle_destroy,
.get_toplevel = xdg_surface_handle_get_toplevel,
.get_popup = xdg_surface_handle_get_popup,
.set_window_geometry = xdg_surface_handle_set_window_geometry,
.ack_configure = xdg_surface_handle_ack_configure,
};
auto xdg_toplevel_handle_destroy(wl_client *, wl_resource *resource) -> void
{
wl_resource_destroy(resource);
}
auto xdg_toplevel_handle_set_parent(wl_client *, wl_resource *, wl_resource *)
-> void
{
}
auto xdg_toplevel_handle_set_title(wl_client *, wl_resource *, char const *)
-> void
{
}
auto xdg_toplevel_handle_set_app_id(wl_client *, wl_resource *, char const *)
-> void
{
}
auto xdg_toplevel_handle_show_window_menu(wl_client *, wl_resource *,
wl_resource *, std::uint32_t, std::int32_t, std::int32_t) -> void
{
}
auto xdg_toplevel_handle_move(
wl_client *, wl_resource *, wl_resource *, std::uint32_t) -> void
{
}
auto xdg_toplevel_handle_resize(wl_client *, wl_resource *, wl_resource *,
std::uint32_t, std::uint32_t) -> void
{
}
auto xdg_toplevel_handle_set_max_size(
wl_client *, wl_resource *, std::int32_t, std::int32_t) -> void
{
}
auto xdg_toplevel_handle_set_min_size(
wl_client *, wl_resource *, std::int32_t, std::int32_t) -> void
{
}
auto xdg_toplevel_handle_set_maximized(wl_client *, wl_resource *) -> void { }
auto xdg_toplevel_handle_unset_maximized(wl_client *, wl_resource *) -> void { }
auto xdg_toplevel_handle_set_fullscreen(
wl_client *, wl_resource *, wl_resource *) -> void
{
}
auto xdg_toplevel_handle_unset_fullscreen(wl_client *, wl_resource *) -> void {
}
auto xdg_toplevel_handle_set_minimized(wl_client *, wl_resource *) -> void { }
struct xdg_toplevel_interface const XDG_TOPLEVEL_INTERFACE_IMPL = {
.destroy = xdg_toplevel_handle_destroy,
.set_parent = xdg_toplevel_handle_set_parent,
.set_title = xdg_toplevel_handle_set_title,
.set_app_id = xdg_toplevel_handle_set_app_id,
.show_window_menu = xdg_toplevel_handle_show_window_menu,
.move = xdg_toplevel_handle_move,
.resize = xdg_toplevel_handle_resize,
.set_max_size = xdg_toplevel_handle_set_max_size,
.set_min_size = xdg_toplevel_handle_set_min_size,
.set_maximized = xdg_toplevel_handle_set_maximized,
.unset_maximized = xdg_toplevel_handle_unset_maximized,
.set_fullscreen = xdg_toplevel_handle_set_fullscreen,
.unset_fullscreen = xdg_toplevel_handle_unset_fullscreen,
.set_minimized = xdg_toplevel_handle_set_minimized,
};
auto xdg_wm_base_handle_destroy(wl_client *, wl_resource *resource) -> void
{
wl_resource_destroy(resource);
}
auto xdg_wm_base_handle_create_positioner(
wl_client *client, wl_resource *resource, std::uint32_t id) -> void
{
auto version { wl_resource_get_version(resource) };
auto *positioner { wl_resource_create(
client, &::xdg_positioner_interface, version, id) };
if (!positioner) {
return;
}
wl_resource_set_implementation(
positioner, &XDG_POSITIONER_INTERFACE_IMPL, nullptr, nullptr);
}
auto xdg_wm_base_handle_get_xdg_surface(wl_client *client,
wl_resource *resource, std::uint32_t id, wl_resource *surface_resource)
-> void
{
auto *server
= static_cast<WaylandServer *>(wl_resource_get_user_data(resource));
if (!server) {
return;
}
auto version { wl_resource_get_version(resource) };
auto *xdg_surface_resource { wl_resource_create(
client, &::xdg_surface_interface, version, id) };
if (!xdg_surface_resource) {
return;
}
auto *surface { new XdgSurface {
*server, xdg_surface_resource, surface_resource, nullptr, 0 } };
wl_resource_set_implementation(xdg_surface_resource,
&XDG_SURFACE_INTERFACE_IMPL, surface, xdg_surface_destroy_resource);
}
auto xdg_wm_base_handle_pong(wl_client *, wl_resource *, std::uint32_t) -> void
{
}
struct xdg_wm_base_interface const XDG_WM_BASE_INTERFACE_IMPL = {
.destroy = xdg_wm_base_handle_destroy,
.create_positioner = xdg_wm_base_handle_create_positioner,
.get_xdg_surface = xdg_wm_base_handle_get_xdg_surface,
.pong = xdg_wm_base_handle_pong,
};
auto bind_xdg_wm_base(wl_client *client, void *data, std::uint32_t version,
std::uint32_t id) -> void
{
auto *server { static_cast<WaylandServer *>(data) };
auto *resource { wl_resource_create(client, &::xdg_wm_base_interface,
std::min(version, XDG_WM_BASE_VERSION), id) };
if (!resource) {
return;
}
wl_resource_set_implementation(
resource, &XDG_WM_BASE_INTERFACE_IMPL, server, nullptr);
}
} // namespace
auto WaylandServer::create_xdg_wm_base_global() -> std::unique_ptr<Global>
{
return std::make_unique<Global>(display(), &::xdg_wm_base_interface,
XDG_WM_BASE_VERSION, this, bind_xdg_wm_base);
}
} // namespace Lunar::Wayland

417
tools/shm_life.cpp Normal file
View File

@@ -0,0 +1,417 @@
#define _GNU_SOURCE
#include <algorithm>
#include <chrono>
#include <cmath>
#include <csignal>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <fcntl.h>
#include <iostream>
#include <print>
#include <random>
#include <sys/mman.h>
#include <sys/stat.h>
#include <thread>
#include <unistd.h>
#include <vector>
#include "xdg-shell-client-protocol.h"
#include <wayland-client.h>
static wl_compositor *g_compositor;
static wl_shm *g_shm;
static xdg_wm_base *g_xdg_wm_base;
static xdg_surface *g_xdg_surface;
static xdg_toplevel *g_xdg_toplevel;
static bool g_configured;
static std::sig_atomic_t volatile g_running { 1 };
static auto handle_signal(int) -> void { g_running = 0; }
static auto registry_global(void *data, wl_registry *registry, uint32_t name,
char const *interface, uint32_t version) -> void
{
(void)data;
if (std::strcmp(interface, "wl_compositor") == 0) {
g_compositor = static_cast<wl_compositor *>(wl_registry_bind(registry,
name, &wl_compositor_interface, version < 4 ? version : 4));
} else if (std::strcmp(interface, "wl_shm") == 0) {
g_shm = static_cast<wl_shm *>(wl_registry_bind(
registry, name, &wl_shm_interface, version < 1 ? version : 1));
} else if (std::strcmp(interface, "xdg_wm_base") == 0) {
g_xdg_wm_base = static_cast<xdg_wm_base *>(wl_registry_bind(
registry, name, &xdg_wm_base_interface, version < 7 ? version : 7));
}
}
static auto registry_global_remove(
void *data, wl_registry *registry, uint32_t name) -> void
{
(void)data;
(void)registry;
(void)name;
}
static wl_registry_listener const registry_listener = {
.global = registry_global,
.global_remove = registry_global_remove,
};
static auto xdg_wm_base_handle_ping(
void *, xdg_wm_base *wm_base, uint32_t serial) -> void
{
xdg_wm_base_pong(wm_base, serial);
}
static xdg_wm_base_listener const xdg_wm_base_listener = {
.ping = xdg_wm_base_handle_ping,
};
static auto xdg_surface_handle_configure(
void *, xdg_surface *surface, uint32_t serial) -> void
{
xdg_surface_ack_configure(surface, serial);
g_configured = true;
}
static xdg_surface_listener const xdg_surface_listener = {
.configure = xdg_surface_handle_configure,
};
static auto xdg_toplevel_handle_configure(
void *, xdg_toplevel *, int32_t, int32_t, wl_array *) -> void
{
}
static auto xdg_toplevel_handle_close(void *, xdg_toplevel *) -> void { }
static auto xdg_toplevel_handle_configure_bounds(
void *, xdg_toplevel *, int32_t, int32_t) -> void
{
}
static auto xdg_toplevel_handle_wm_capabilities(
void *, xdg_toplevel *, wl_array *) -> void
{
}
static xdg_toplevel_listener const xdg_toplevel_listener = {
.configure = xdg_toplevel_handle_configure,
.close = xdg_toplevel_handle_close,
.configure_bounds = xdg_toplevel_handle_configure_bounds,
.wm_capabilities = xdg_toplevel_handle_wm_capabilities,
};
static auto create_shm_file(size_t size) -> int
{
int fd { memfd_create("shm_life", 0) };
if (fd < 0) {
return -1;
}
if (ftruncate(fd, static_cast<off_t>(size)) != 0) {
close(fd);
return -1;
}
return fd;
}
auto main(int argc, char **argv) -> int
{
auto *display { wl_display_connect(nullptr) };
if (!display) {
std::println(std::cerr, "Failed to connect to Wayland display.");
return 1;
}
auto *registry { wl_display_get_registry(display) };
if (!registry) {
std::println(std::cerr, "Failed to fetch Wayland registry.");
wl_display_disconnect(display);
return 1;
}
if (wl_registry_add_listener(registry, &registry_listener, nullptr) < 0) {
std::println(std::cerr, "Failed to add registry listener.");
wl_display_disconnect(display);
return 1;
}
if (wl_display_roundtrip(display) < 0) {
std::println(std::cerr, "Failed to sync Wayland registry.");
wl_display_disconnect(display);
return 1;
}
if (!g_compositor || !g_shm || !g_xdg_wm_base) {
std::println(std::cerr, "Missing compositor or xdg globals.");
wl_display_disconnect(display);
return 1;
}
std::signal(SIGINT, handle_signal);
std::signal(SIGTERM, handle_signal);
if (xdg_wm_base_add_listener(g_xdg_wm_base, &xdg_wm_base_listener, nullptr)
< 0) {
std::println(std::cerr, "Failed to add xdg_wm_base listener.");
wl_display_disconnect(display);
return 1;
}
constexpr int width { 640 };
constexpr int height { 480 };
int cell_size { 5 };
if (argc > 1) {
long requested { std::strtol(argv[1], nullptr, 10) };
if (requested >= 2 && requested <= 80) {
cell_size = static_cast<int>(requested);
}
}
int grid_width { width / cell_size };
int grid_height { height / cell_size };
constexpr int stride { width * 4 };
constexpr size_t size { static_cast<size_t>(stride) * height };
int fd { create_shm_file(size) };
if (fd < 0) {
std::println(std::cerr, "Failed to create shm file.");
wl_display_disconnect(display);
return 1;
}
auto *data { static_cast<uint32_t *>(
mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) };
if (data == MAP_FAILED) {
std::println(std::cerr, "Failed to mmap shm.");
close(fd);
wl_display_disconnect(display);
return 1;
}
std::vector<uint8_t> grid(static_cast<size_t>(grid_width * grid_height), 0);
std::vector<uint8_t> next(static_cast<size_t>(grid_width * grid_height), 0);
std::minstd_rand rng(std::random_device {}());
std::bernoulli_distribution alive_dist(0.45);
std::vector<uint64_t> history;
float hue { 0.0f };
float hue_step { 1.5f };
auto hue_to_rgb { [](float hue_degrees) -> uint32_t {
float h { std::fmod(hue_degrees, 360.0f) / 360.0f };
float s { 1.0f };
float l { 0.5f };
auto hue_to_channel { [](float p, float q, float t) -> float {
if (t < 0.0f) {
t += 1.0f;
}
if (t > 1.0f) {
t -= 1.0f;
}
if (t < 1.0f / 6.0f) {
return p + (q - p) * 6.0f * t;
}
if (t < 1.0f / 2.0f) {
return q;
}
if (t < 2.0f / 3.0f) {
return p + (q - p) * (2.0f / 3.0f - t) * 6.0f;
}
return p;
} };
float r { l };
float g { l };
float b { l };
if (s > 0.0f) {
float q { l < 0.5f ? l * (1.0f + s) : l + s - l * s };
float p { 2.0f * l - q };
r = hue_to_channel(p, q, h + 1.0f / 3.0f);
g = hue_to_channel(p, q, h);
b = hue_to_channel(p, q, h - 1.0f / 3.0f);
}
uint8_t rr { static_cast<uint8_t>(r * 255.0f) };
uint8_t gg { static_cast<uint8_t>(g * 255.0f) };
uint8_t bb { static_cast<uint8_t>(b * 255.0f) };
return 0xff000000u | (uint32_t)rr << 16 | (uint32_t)gg << 8
| (uint32_t)bb;
} };
auto randomize_grid { [&]() -> void {
for (int y = 0; y < grid_height; ++y) {
for (int x = 0; x < grid_width; ++x) {
grid[y * grid_width + x] = alive_dist(rng) ? 1 : 0;
}
}
std::fill(next.begin(), next.end(), 0);
history.clear();
} };
auto hash_grid { [&]() -> uint64_t {
uint64_t hash { 1469598103934665603ull };
for (uint8_t cell : grid) {
hash ^= static_cast<uint64_t>(cell);
hash *= 1099511628211ull;
}
return hash;
} };
randomize_grid();
auto *pool { wl_shm_create_pool(g_shm, fd, static_cast<int>(size)) };
auto *buffer { wl_shm_pool_create_buffer(
pool, 0, width, height, stride, WL_SHM_FORMAT_ARGB8888) };
auto *surface { wl_compositor_create_surface(g_compositor) };
if (!pool || !buffer || !surface) {
std::println(std::cerr, "Failed to create shm objects.");
if (surface) {
wl_surface_destroy(surface);
}
if (buffer) {
wl_buffer_destroy(buffer);
}
if (pool) {
wl_shm_pool_destroy(pool);
}
munmap(data, size);
close(fd);
wl_display_disconnect(display);
return 1;
}
g_xdg_surface = xdg_wm_base_get_xdg_surface(g_xdg_wm_base, surface);
if (!g_xdg_surface) {
std::println(std::cerr, "Failed to create xdg_surface.");
wl_surface_destroy(surface);
wl_buffer_destroy(buffer);
wl_shm_pool_destroy(pool);
munmap(data, size);
close(fd);
wl_display_disconnect(display);
return 1;
}
if (xdg_surface_add_listener(g_xdg_surface, &xdg_surface_listener, nullptr)
< 0) {
std::println(std::cerr, "Failed to add xdg_surface listener.");
xdg_surface_destroy(g_xdg_surface);
wl_surface_destroy(surface);
wl_buffer_destroy(buffer);
wl_shm_pool_destroy(pool);
munmap(data, size);
close(fd);
wl_display_disconnect(display);
return 1;
}
g_xdg_toplevel = xdg_surface_get_toplevel(g_xdg_surface);
if (g_xdg_toplevel) {
xdg_toplevel_add_listener(
g_xdg_toplevel, &xdg_toplevel_listener, nullptr);
xdg_toplevel_set_title(g_xdg_toplevel, "life");
xdg_toplevel_set_app_id(g_xdg_toplevel, "lunar.life");
}
wl_surface_commit(surface);
if (wl_display_roundtrip(display) < 0 || !g_configured) {
std::println(std::cerr, "Failed to receive initial configure.");
if (g_xdg_toplevel) {
xdg_toplevel_destroy(g_xdg_toplevel);
}
xdg_surface_destroy(g_xdg_surface);
wl_surface_destroy(surface);
wl_buffer_destroy(buffer);
wl_shm_pool_destroy(pool);
munmap(data, size);
close(fd);
wl_display_disconnect(display);
return 1;
}
wl_surface_attach(surface, buffer, 0, 0);
wl_surface_commit(surface);
wl_display_flush(display);
auto last_tick { std::chrono::steady_clock::now() };
while (g_running) {
wl_display_dispatch_pending(display);
auto now { std::chrono::steady_clock::now() };
auto dt { now - last_tick };
if (dt < std::chrono::milliseconds(16)) {
std::this_thread::sleep_for(std::chrono::milliseconds(4));
continue;
}
last_tick = now;
for (int y = 0; y < grid_height; ++y) {
for (int x = 0; x < grid_width; ++x) {
int neighbors { 0 };
for (int oy = -1; oy <= 1; ++oy) {
for (int ox = -1; ox <= 1; ++ox) {
if (ox == 0 && oy == 0) {
continue;
}
int ny { (y + oy + grid_height) % grid_height };
int nx { (x + ox + grid_width) % grid_width };
neighbors += grid[ny * grid_width + nx];
}
}
uint8_t alive { grid[y * grid_width + x] };
if (alive) {
next[y * grid_width + x]
= (neighbors == 2 || neighbors == 3) ? 1 : 0;
} else {
next[y * grid_width + x] = (neighbors == 3) ? 1 : 0;
}
}
}
grid.swap(next);
std::fill(next.begin(), next.end(), 0);
uint64_t current_hash { hash_grid() };
bool repeating { std::find(history.begin(), history.end(), current_hash)
!= history.end() };
if (repeating) {
randomize_grid();
current_hash = hash_grid();
}
history.push_back(current_hash);
if (history.size() > 5) {
history.erase(history.begin());
}
hue += hue_step;
if (hue >= 360.0f) {
hue -= 360.0f;
}
uint32_t alive_color { hue_to_rgb(hue) };
for (int y = 0; y < height; ++y) {
int gy { y / cell_size };
for (int x = 0; x < width; ++x) {
int gx { x / cell_size };
uint8_t alive { grid[gy * grid_width + gx] };
if (alive) {
data[y * width + x] = alive_color;
} else {
data[y * width + x] = 0x00000000u;
}
}
}
wl_surface_attach(surface, buffer, 0, 0);
wl_surface_damage_buffer(surface, 0, 0, width, height);
wl_surface_commit(surface);
wl_display_flush(display);
}
if (g_xdg_toplevel) {
xdg_toplevel_destroy(g_xdg_toplevel);
}
if (g_xdg_surface) {
xdg_surface_destroy(g_xdg_surface);
}
wl_buffer_destroy(buffer);
wl_shm_pool_destroy(pool);
wl_surface_destroy(surface);
munmap(data, size);
close(fd);
wl_display_disconnect(display);
}