new input system
This commit is contained in:
parent
b3db40ebec
commit
ac2eb7fb0a
31 changed files with 3292 additions and 781 deletions
42
interfaces/Crafter.Graphics-Clipboard.cppm
Normal file
42
interfaces/Crafter.Graphics-Clipboard.cppm
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
Crafter®.Graphics
|
||||
Copyright (C) 2026 Catcrafts®
|
||||
Catcrafts.net
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 3.0 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
export module Crafter.Graphics:Clipboard;
|
||||
import std;
|
||||
|
||||
// Native system-clipboard writes. No popen, no helper binaries — just
|
||||
// the platform's own clipboard API. Implementation lives next to the
|
||||
// other window-backend code (Wayland data_device on Linux, Win32 in
|
||||
// the Windows build); callers don't pick a backend.
|
||||
//
|
||||
// `Get` is intentionally not exposed yet: paste-from-clipboard isn't
|
||||
// a feature the game's UI wants right now, and the read path needs
|
||||
// more lifecycle plumbing (mime negotiation, fd reads on the Wayland
|
||||
// event loop) than the simple write path. Easy to add later.
|
||||
export namespace Crafter::Clipboard {
|
||||
|
||||
// Place `text` on the system clipboard as UTF-8 plain text. Returns
|
||||
// true if the platform accepted the request — false means the
|
||||
// backend isn't initialised, no input event has been seen yet
|
||||
// (Wayland needs a recent serial), or the OS API failed. On
|
||||
// success the ownership of the clipboard contents is held until
|
||||
// either another app replaces the selection or the application
|
||||
// exits; the caller doesn't need to keep `text` alive.
|
||||
bool SetText(std::string_view text);
|
||||
}
|
||||
133
interfaces/Crafter.Graphics-Decompress.cppm
Normal file
133
interfaces/Crafter.Graphics-Decompress.cppm
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
Crafter®.Graphics
|
||||
Copyright (C) 2026 Catcrafts®
|
||||
catcrafts.net
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License version 3.0 as published by the Free Software Foundation;
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
module;
|
||||
#include "vulkan/vulkan.h"
|
||||
|
||||
export module Crafter.Graphics:Decompress;
|
||||
import :Device;
|
||||
import std;
|
||||
|
||||
export namespace Crafter::Decompress {
|
||||
// GDeflate 1.0 requires each VkDecompressMemoryRegionEXT to cover one
|
||||
// 64 KiB tile. CompressedBlob stores one stream per RegionMeta — this
|
||||
// helper walks the stream's tile-stream header and emits per-tile
|
||||
// regions ready for vkCmdDecompressMemoryEXT.
|
||||
//
|
||||
// streamBytes is the raw stream bytes (CompressedBlob::bytes.subspan(
|
||||
// r.srcOffset, r.compressedSize)). srcBase is its GPU device address.
|
||||
// dstBase is the GPU device address of the first decompressed byte.
|
||||
inline void ExpandStreamToTileRegions(
|
||||
std::span<const std::byte> streamBytes,
|
||||
VkDeviceAddress srcBase,
|
||||
VkDeviceAddress dstBase,
|
||||
std::vector<VkDecompressMemoryRegionEXT>& out)
|
||||
{
|
||||
if (streamBytes.empty()) return;
|
||||
|
||||
constexpr std::size_t kTileSize = 64 * 1024;
|
||||
constexpr std::size_t kHeaderSize = 8;
|
||||
|
||||
// TileStream wire layout (see vendored TileStream.h):
|
||||
// u8 id; u8 magic; u16 numTiles;
|
||||
// { u2 tileSizeIdx; u18 lastTileSize; u12 reserved; } packed into u32
|
||||
const std::uint8_t* p = reinterpret_cast<const std::uint8_t*>(streamBytes.data());
|
||||
std::uint8_t id = p[0];
|
||||
std::uint8_t magic = p[1];
|
||||
if (id != (magic ^ 0xff)) {
|
||||
throw std::runtime_error("GDeflate tile-stream: bad id/magic");
|
||||
}
|
||||
std::uint16_t numTiles =
|
||||
static_cast<std::uint16_t>(p[2])
|
||||
| static_cast<std::uint16_t>(p[3]) << 8;
|
||||
std::uint32_t packed =
|
||||
static_cast<std::uint32_t>(p[4])
|
||||
| static_cast<std::uint32_t>(p[5]) << 8
|
||||
| static_cast<std::uint32_t>(p[6]) << 16
|
||||
| static_cast<std::uint32_t>(p[7]) << 24;
|
||||
std::uint32_t lastTileSize = (packed >> 2) & 0x3FFFFu;
|
||||
|
||||
const std::uint32_t* tileOffsets = reinterpret_cast<const std::uint32_t*>(
|
||||
streamBytes.data() + kHeaderSize);
|
||||
const std::size_t dataStart = kHeaderSize + std::size_t(numTiles) * sizeof(std::uint32_t);
|
||||
|
||||
out.reserve(out.size() + numTiles);
|
||||
for (std::uint32_t k = 0; k < numTiles; ++k) {
|
||||
std::size_t tileOff = (k > 0) ? tileOffsets[k] : 0;
|
||||
std::size_t compressedSize = (k + 1 < numTiles)
|
||||
? std::size_t(tileOffsets[k + 1]) - tileOff
|
||||
: std::size_t(tileOffsets[0]);
|
||||
std::size_t decompressedSize = (k + 1 < numTiles)
|
||||
? kTileSize
|
||||
: (lastTileSize != 0 ? std::size_t(lastTileSize) : kTileSize);
|
||||
out.push_back(VkDecompressMemoryRegionEXT {
|
||||
.srcAddress = srcBase + dataStart + tileOff,
|
||||
.dstAddress = dstBase + std::size_t(k) * kTileSize,
|
||||
.compressedSize = compressedSize,
|
||||
.decompressedSize = decompressedSize,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Records vkCmdDecompressMemoryEXT into `cmd` for the supplied regions
|
||||
// (caller pre-resolves srcAddress/dstAddress) and follows it with a
|
||||
// synchronization2 memory barrier so the consumer at (dstStage, dstAccess)
|
||||
// observes the decompressed bytes.
|
||||
//
|
||||
// Method is fixed at GDeflate 1.0 — Crafter.Asset's compressed file format
|
||||
// emits GDeflate streams. Caller must guarantee Device::memoryDecompressionSupported.
|
||||
inline void DecompressOnGPU(
|
||||
VkCommandBuffer cmd,
|
||||
std::span<const VkDecompressMemoryRegionEXT> regions,
|
||||
VkPipelineStageFlags2 dstStage,
|
||||
VkAccessFlags2 dstAccess)
|
||||
{
|
||||
if (regions.empty()) return;
|
||||
|
||||
VkDecompressMemoryInfoEXT info {
|
||||
.sType = VK_STRUCTURE_TYPE_DECOMPRESS_MEMORY_INFO_EXT,
|
||||
.pNext = nullptr,
|
||||
.decompressionMethod = VK_MEMORY_DECOMPRESSION_METHOD_GDEFLATE_1_0_BIT_EXT,
|
||||
.regionCount = static_cast<std::uint32_t>(regions.size()),
|
||||
.pRegions = regions.data(),
|
||||
};
|
||||
Device::vkCmdDecompressMemoryEXT(cmd, &info);
|
||||
|
||||
VkMemoryBarrier2 barrier {
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2,
|
||||
.pNext = nullptr,
|
||||
.srcStageMask = VK_PIPELINE_STAGE_2_MEMORY_DECOMPRESSION_BIT_EXT,
|
||||
.srcAccessMask = VK_ACCESS_2_MEMORY_DECOMPRESSION_WRITE_BIT_EXT,
|
||||
.dstStageMask = dstStage,
|
||||
.dstAccessMask = dstAccess,
|
||||
};
|
||||
VkDependencyInfo dep {
|
||||
.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
|
||||
.pNext = nullptr,
|
||||
.dependencyFlags = 0,
|
||||
.memoryBarrierCount = 1,
|
||||
.pMemoryBarriers = &barrier,
|
||||
.bufferMemoryBarrierCount = 0,
|
||||
.pBufferMemoryBarriers = nullptr,
|
||||
.imageMemoryBarrierCount = 0,
|
||||
.pImageMemoryBarriers = nullptr,
|
||||
};
|
||||
vkCmdPipelineBarrier2(cmd, &dep);
|
||||
}
|
||||
}
|
||||
|
|
@ -30,7 +30,7 @@ module;
|
|||
#endif
|
||||
export module Crafter.Graphics:Device;
|
||||
import std;
|
||||
import :Types; // CrafterKeys for keyboard repeat state
|
||||
import :Keys; // KeyCode for keyboard repeat state
|
||||
|
||||
export namespace Crafter {
|
||||
struct Window;
|
||||
|
|
@ -43,7 +43,7 @@ export namespace Crafter {
|
|||
int rate = 25; // chars/sec
|
||||
int delay = 500; // ms before first repeat
|
||||
bool active = false;
|
||||
CrafterKeys key{};
|
||||
KeyCode key = 0;
|
||||
std::string utf8; // UTF-8 to re-emit as onTextInput, if any
|
||||
std::chrono::time_point<std::chrono::steady_clock> pressTime;
|
||||
std::chrono::time_point<std::chrono::steady_clock> lastFireTime;
|
||||
|
|
@ -68,6 +68,13 @@ export namespace Crafter {
|
|||
inline static xkb_state* xkb_state;
|
||||
inline static std::vector<Window*> windows;
|
||||
inline static wl_pointer* wlPointer;
|
||||
// wl_data_device_manager + wl_data_device drive copy/paste. Bound
|
||||
// lazily in handle_global; the data device is created once both
|
||||
// the manager and the seat are present (registry binding order
|
||||
// isn't guaranteed). nullptr on compositors that don't expose
|
||||
// the manager — Clipboard::SetText silently no-ops there.
|
||||
inline static wl_data_device_manager* dataDeviceManager = nullptr;
|
||||
inline static wl_data_device* dataDevice = nullptr;
|
||||
|
||||
static void seat_handle_capabilities(void* data, wl_seat* seat, uint32_t capabilities);
|
||||
static void xdg_surface_handle_preferred_scale(void* data, wp_fractional_scale_v1*, std::uint32_t scale);
|
||||
|
|
@ -137,6 +144,12 @@ export namespace Crafter {
|
|||
inline static PFN_vkGetPhysicalDeviceDescriptorSizeEXT vkGetPhysicalDeviceDescriptorSizeEXT;
|
||||
inline static PFN_vkGetDeviceFaultInfoEXT vkGetDeviceFaultInfoEXT;
|
||||
|
||||
// VK_EXT_memory_decompression — opt-in. When the driver advertises it
|
||||
// and exposes the GDeflate 1.0 method, GPU asset decompression is
|
||||
// available; otherwise consumers fall back to CPU decode.
|
||||
inline static bool memoryDecompressionSupported = false;
|
||||
inline static PFN_vkCmdDecompressMemoryEXT vkCmdDecompressMemoryEXT = nullptr;
|
||||
|
||||
inline static VkPhysicalDeviceMemoryProperties memoryProperties;
|
||||
|
||||
inline static VkPhysicalDeviceDescriptorHeapPropertiesEXT descriptorHeapProperties = {
|
||||
|
|
@ -146,16 +159,19 @@ export namespace Crafter {
|
|||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_PROPERTIES_KHR,
|
||||
.pNext = &descriptorHeapProperties
|
||||
};
|
||||
inline static VkPhysicalDeviceMemoryDecompressionPropertiesEXT memoryDecompressionProperties = {
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_DECOMPRESSION_PROPERTIES_EXT
|
||||
};
|
||||
|
||||
static void CheckVkResult(VkResult result);
|
||||
static std::uint32_t GetMemoryType(std::uint32_t typeBits, VkMemoryPropertyFlags properties);
|
||||
|
||||
// ─── Wayland key repeat ────────────────────────────────────────
|
||||
// TickKeyRepeats walks the held-key state and fires onKeyDown /
|
||||
// onTextInput accordingly. Called once per frame from
|
||||
// Window::Render. KeyRepeatState lives at namespace scope so its
|
||||
// member initializers don't trip C++'s "complete-type-needed"
|
||||
// rule for the inline static below.
|
||||
// TickKeyRepeats fires onRawKeyDown / onRawKeyHold / onTextInput on
|
||||
// the focused window for whichever key is currently repeating.
|
||||
// Called once per frame from Window::Render. KeyRepeatState lives
|
||||
// at namespace scope so its member initializers don't trip C++'s
|
||||
// "complete-type-needed" rule for the inline static below.
|
||||
#ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND
|
||||
inline static KeyRepeatState keyRepeat;
|
||||
static void TickKeyRepeats();
|
||||
|
|
|
|||
99
interfaces/Crafter.Graphics-Gamepad.cppm
Normal file
99
interfaces/Crafter.Graphics-Gamepad.cppm
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
Crafter®.Graphics
|
||||
Copyright (C) 2026 Catcrafts®
|
||||
Catcrafts.net
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 3.0 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
export module Crafter.Graphics:Gamepad;
|
||||
import std;
|
||||
import Crafter.Event;
|
||||
|
||||
// Raw gamepad device API. Platform-split implementation:
|
||||
// - Linux: libudev (enumerate + hot-plug) + libevdev (read events,
|
||||
// calibrate axes). Open every /dev/input/event* node the kernel
|
||||
// classifies as a gamepad; ignore the rest.
|
||||
// - Windows: Windows.Gaming.Input via the C ABI (COBJMACROS pattern,
|
||||
// same as SDL). Added/Removed events fire on a thread pool and are
|
||||
// marshaled into a queue drained from Tick() on the main thread.
|
||||
//
|
||||
// Button names follow the standardized kernel BTN_SOUTH / BTN_EAST /
|
||||
// BTN_WEST / BTN_NORTH convention — physical position, not label. An
|
||||
// Xbox "A" and a PlayStation "Cross" both arrive as `South`.
|
||||
//
|
||||
// Stick axes are -1..1 (post-deadzone calibration, raw float). Trigger
|
||||
// axes are 0..1. The `LeftTrigger` / `RightTrigger` entries in `Button`
|
||||
// and `Axis` are both populated — pick whichever fits your use case
|
||||
// (digital threshold vs. analog reading).
|
||||
|
||||
export namespace Crafter::Gamepad {
|
||||
enum class Button : std::uint8_t {
|
||||
South, East, West, North, // BTN_SOUTH/EAST/WEST/NORTH
|
||||
Select, Start, Home,
|
||||
LeftStickClick, RightStickClick,
|
||||
LeftBumper, RightBumper,
|
||||
DPadUp, DPadDown, DPadLeft, DPadRight,
|
||||
LeftTrigger, RightTrigger, // digital threshold; analog also on Axis
|
||||
Max
|
||||
};
|
||||
|
||||
enum class Axis : std::uint8_t {
|
||||
LeftStickX, LeftStickY,
|
||||
RightStickX, RightStickY,
|
||||
LeftTrigger, RightTrigger,
|
||||
Max
|
||||
};
|
||||
|
||||
enum class Stick : std::uint8_t { Left, Right };
|
||||
|
||||
struct Device {
|
||||
std::uint32_t id; // stable across the device's lifetime
|
||||
std::string name;
|
||||
bool buttons[(std::size_t)Button::Max] = {};
|
||||
float axes[(std::size_t)Axis::Max] = {}; // sticks: -1..1, triggers: 0..1
|
||||
Event<Button> onButtonDown;
|
||||
Event<Button> onButtonUp;
|
||||
Event<Axis> onAxisChanged;
|
||||
};
|
||||
|
||||
// Process-level state — one input subsystem per app. Indices into
|
||||
// `connected` are NOT stable across hot-plug; use `Device::id` when
|
||||
// serializing bindings. The list is unsorted; iterate to find a
|
||||
// specific id.
|
||||
inline std::vector<std::unique_ptr<Device>> connected;
|
||||
inline Event<Device*> onConnected;
|
||||
inline Event<Device*> onDisconnected;
|
||||
|
||||
// Drains pending events from the OS, fires the relevant onButton* /
|
||||
// onAxisChanged / onConnected / onDisconnected events synchronously,
|
||||
// and updates the polled `buttons[]` / `axes[]` state on each Device.
|
||||
// Must be called from the main thread once per frame — Window's
|
||||
// event loop does this automatically. Safe to call when no gamepads
|
||||
// are connected (early-out path).
|
||||
void Tick();
|
||||
|
||||
// Optional rumble. `low` drives the heavy/low-frequency motor,
|
||||
// `high` drives the light/high-frequency motor (Xbox naming). Both
|
||||
// are clamped to 0..1. `duration` clamps to the backend's max; pass
|
||||
// 0ms to stop any active rumble. No-op if the device doesn't
|
||||
// advertise force-feedback.
|
||||
void Rumble(Device& dev, float low, float high,
|
||||
std::chrono::milliseconds duration);
|
||||
|
||||
// Find a connected device by its stable id. Returns nullptr if the
|
||||
// device isn't currently connected — typical use after deserializing
|
||||
// a saved binding.
|
||||
Device* FindById(std::uint32_t id);
|
||||
}
|
||||
|
|
@ -23,6 +23,8 @@ module;
|
|||
|
||||
export module Crafter.Graphics:ImageVulkan;
|
||||
import std;
|
||||
import Crafter.Asset;
|
||||
import :Decompress;
|
||||
import :VulkanBuffer;
|
||||
|
||||
export namespace Crafter {
|
||||
|
|
@ -35,6 +37,10 @@ export namespace Crafter {
|
|||
VkImage image;
|
||||
VkDeviceMemory imageMemory;
|
||||
VulkanBuffer<PixelType, true> buffer;
|
||||
// Lives until the compressed Update path's cmd buffer completes.
|
||||
// Same lifetime contract as Mesh::compressedStaging — caller must
|
||||
// not destroy / re-Update before the submit fence is signaled.
|
||||
VulkanBuffer<std::byte, true> compressedStaging;
|
||||
VkImageView imageView;
|
||||
VkDescriptorImageInfo descriptor;
|
||||
|
||||
|
|
@ -153,6 +159,114 @@ export namespace Crafter {
|
|||
}
|
||||
}
|
||||
|
||||
// GPU compressed-asset Update: stage compressed bytes, decompress
|
||||
// into `buffer` via VK_EXT_memory_decompression, then copy buffer→image
|
||||
// and transition to `layout`. Falls back to CPU decode + the existing
|
||||
// Update path when Device::memoryDecompressionSupported is false.
|
||||
// Caller is responsible for the dimensions matching: asset.sizeX/sizeY
|
||||
// must equal this->width/height (set by Create), and asset.pixelStride
|
||||
// must equal sizeof(PixelType).
|
||||
void Update(const CompressedTextureAsset& asset, VkCommandBuffer cmd, VkImageLayout layout) {
|
||||
if (asset.pixelStride != sizeof(PixelType)) {
|
||||
throw std::runtime_error("ImageVulkan::Update(compressed): pixel stride mismatch");
|
||||
}
|
||||
if (!Device::memoryDecompressionSupported) {
|
||||
std::span<PixelType> dst{ buffer.value, static_cast<std::size_t>(width) * height };
|
||||
std::array<std::span<std::byte>, 1> outputs = {
|
||||
std::as_writable_bytes(dst),
|
||||
};
|
||||
Compression::DecompressCPU(asset.blob, outputs);
|
||||
Update(cmd, layout);
|
||||
return;
|
||||
}
|
||||
|
||||
// Re-create the staging-into-image buffer with MEMORY_DECOMPRESSION
|
||||
// permission so the GPU codec can write into it. Keeps it
|
||||
// HOST_VISIBLE (matches the existing path) — on UMA / ReBAR that's
|
||||
// a fast path, on older systems the decompress writes traverse
|
||||
// PCIe but correctness is unchanged.
|
||||
buffer.Resize(
|
||||
VK_BUFFER_USAGE_TRANSFER_SRC_BIT
|
||||
| VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT
|
||||
| VK_BUFFER_USAGE_2_MEMORY_DECOMPRESSION_BIT_EXT,
|
||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT,
|
||||
static_cast<std::uint32_t>(width) * height);
|
||||
|
||||
compressedStaging.Resize(
|
||||
VK_BUFFER_USAGE_TRANSFER_SRC_BIT
|
||||
| VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT
|
||||
| VK_BUFFER_USAGE_2_MEMORY_DECOMPRESSION_BIT_EXT,
|
||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT,
|
||||
static_cast<std::uint32_t>(asset.blob.bytes.size()));
|
||||
std::memcpy(compressedStaging.value, asset.blob.bytes.data(), asset.blob.bytes.size());
|
||||
compressedStaging.FlushDevice();
|
||||
|
||||
std::vector<VkDecompressMemoryRegionEXT> regions;
|
||||
for (const Compression::RegionMeta& r : asset.blob.regions) {
|
||||
if (r.decompressedSize == 0) continue;
|
||||
std::span<const std::byte> streamBytes(
|
||||
asset.blob.bytes.data() + r.srcOffset,
|
||||
static_cast<std::size_t>(r.compressedSize));
|
||||
Decompress::ExpandStreamToTileRegions(
|
||||
streamBytes,
|
||||
compressedStaging.address + r.srcOffset,
|
||||
buffer.address,
|
||||
regions);
|
||||
}
|
||||
Decompress::DecompressOnGPU(
|
||||
cmd,
|
||||
regions,
|
||||
VK_PIPELINE_STAGE_2_COPY_BIT,
|
||||
VK_ACCESS_2_TRANSFER_READ_BIT);
|
||||
|
||||
// Continue with the existing buffer→image upload + layout transitions.
|
||||
// We've already inserted the decompress→transfer-read barrier,
|
||||
// so we skip the FlushDevice host-write barrier the regular Update
|
||||
// would emit (no host write happened).
|
||||
TransitionImageLayout(cmd, image, layout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, 0, mipLevels);
|
||||
|
||||
VkBufferImageCopy region{};
|
||||
region.bufferOffset = 0;
|
||||
region.bufferRowLength = 0;
|
||||
region.bufferImageHeight = 0;
|
||||
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
region.imageSubresource.mipLevel = 0;
|
||||
region.imageSubresource.baseArrayLayer = 0;
|
||||
region.imageSubresource.layerCount = 1;
|
||||
region.imageOffset = {0, 0, 0};
|
||||
region.imageExtent = { width, height, 1 };
|
||||
vkCmdCopyBufferToImage(cmd, buffer.buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
|
||||
|
||||
if (mipLevels > 1) {
|
||||
TransitionImageLayout(cmd, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT, 0, 1);
|
||||
for (std::uint16_t i = 1; i < mipLevels; ++i) {
|
||||
std::uint16_t mipWidth = width >> i;
|
||||
std::uint16_t mipHeight = height >> i;
|
||||
std::uint16_t previousMipWidth = width >> (i - std::uint16_t(1));
|
||||
std::uint16_t previousMipHeight = height >> (i - std::uint16_t(1));
|
||||
|
||||
VkImageBlit blit = {};
|
||||
blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
blit.srcSubresource.mipLevel = i - 1;
|
||||
blit.srcSubresource.baseArrayLayer = 0;
|
||||
blit.srcSubresource.layerCount = 1;
|
||||
blit.srcOffsets[0] = { 0, 0, 0 };
|
||||
blit.srcOffsets[1] = { (int32_t)previousMipWidth, (int32_t)previousMipHeight, 1 };
|
||||
blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
blit.dstSubresource.mipLevel = i;
|
||||
blit.dstSubresource.baseArrayLayer = 0;
|
||||
blit.dstSubresource.layerCount = 1;
|
||||
blit.dstOffsets[0] = { 0, 0, 0 };
|
||||
blit.dstOffsets[1] = { (int32_t)mipWidth, (int32_t)mipHeight, 1 };
|
||||
vkCmdBlitImage(cmd, image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &blit, VK_FILTER_LINEAR);
|
||||
TransitionImageLayout(cmd, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT, i, 1);
|
||||
}
|
||||
TransitionImageLayout(cmd, image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, layout, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR, VK_ACCESS_TRANSFER_READ_BIT, VK_ACCESS_SHADER_READ_BIT, 0, mipLevels);
|
||||
} else {
|
||||
TransitionImageLayout(cmd, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, layout, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, 0, mipLevels);
|
||||
}
|
||||
}
|
||||
|
||||
void Destroy() {
|
||||
vkDestroyImageView(Device::device, imageView, nullptr);
|
||||
vkDestroyImage(Device::device, image, nullptr);
|
||||
|
|
|
|||
180
interfaces/Crafter.Graphics-Input.cppm
Normal file
180
interfaces/Crafter.Graphics-Input.cppm
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
/*
|
||||
Crafter®.Graphics
|
||||
Copyright (C) 2026 Catcrafts®
|
||||
Catcrafts.net
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 3.0 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
export module Crafter.Graphics:Input;
|
||||
import std;
|
||||
import Crafter.Math;
|
||||
import Crafter.Event;
|
||||
import :Keys;
|
||||
import :Gamepad;
|
||||
import :Window;
|
||||
|
||||
// Named-action input mapping with rebindable bindings. Game code declares
|
||||
// actions ("Jump", "Move", "Fire"), attaches binding sources (keys, mouse
|
||||
// buttons, gamepad sticks, WASD-as-Vector2, etc.), and subscribes to
|
||||
// `onPerformed` / `onCanceled` / `onValueChanged` events. Bindings can be
|
||||
// rebound at runtime via `StartRebind` — the next qualifying input
|
||||
// becomes the new binding.
|
||||
//
|
||||
// Polling-based evaluation: `Map::Tick()` (called once per frame from
|
||||
// game code, AFTER `Gamepad::Tick()`) reads the polled state of the
|
||||
// attached window + connected gamepads and diffs against the action's
|
||||
// previous state. No per-device event listeners — works regardless of
|
||||
// gamepad connect/disconnect timing.
|
||||
//
|
||||
// Bindings store raw platform key codes (the same `KeyCode` domain as
|
||||
// `Window::onRawKeyDown`). Source code uses `Key(CrafterKeys::Space)`
|
||||
// from :Keys to obtain cross-platform default codes at compile time.
|
||||
// Binding files serialized with `BindingToString` are platform-specific
|
||||
// (a Win32 save won't load on Wayland) — document this for callers.
|
||||
|
||||
export namespace Crafter::Input {
|
||||
|
||||
enum class ActionType : std::uint8_t {
|
||||
Button, // digital — pressed/released
|
||||
Axis1D, // -1..1 (or 0..1 for triggers / scroll)
|
||||
Vector2, // 2D — sticks, WASD, mouse delta
|
||||
};
|
||||
|
||||
// ─── Binding alternatives ───────────────────────────────────────
|
||||
struct KeyBind { KeyCode code; };
|
||||
struct MouseButtonBind { std::uint8_t button; }; // 0 = left, 1 = right
|
||||
struct MouseScrollBind { }; // Axis1D, accumulator drained per tick
|
||||
struct MouseDeltaBind { float scale = 1.0f; }; // Vector2
|
||||
struct GamepadButtonBind { std::uint32_t gamepadId; Gamepad::Button button; };
|
||||
struct GamepadAxisBind { std::uint32_t gamepadId; Gamepad::Axis axis; bool invert = false; };
|
||||
struct GamepadStickBind { std::uint32_t gamepadId; Gamepad::Stick stick; }; // Vector2
|
||||
struct WASDBind {
|
||||
KeyCode up;
|
||||
KeyCode down;
|
||||
KeyCode left;
|
||||
KeyCode right;
|
||||
};
|
||||
|
||||
using Binding = std::variant<
|
||||
KeyBind, MouseButtonBind, MouseScrollBind, MouseDeltaBind,
|
||||
GamepadButtonBind, GamepadAxisBind, GamepadStickBind, WASDBind
|
||||
>;
|
||||
|
||||
struct Action {
|
||||
std::string name;
|
||||
ActionType type;
|
||||
std::vector<Binding> bindings;
|
||||
// Analog deadzone — bindings below this magnitude read as zero.
|
||||
// Applied per-axis to GamepadAxisBind, radially to
|
||||
// GamepadStickBind. Doesn't apply to Button (digital).
|
||||
float deadzone = 0.15f;
|
||||
|
||||
// Polled state. Read freely from the listener callback or
|
||||
// anywhere else; updated by `Map::Tick()`.
|
||||
bool pressed = false;
|
||||
float value = 0.0f;
|
||||
Vector<float, 2> vector2{};
|
||||
|
||||
// Events fired during `Map::Tick()` when the polled state crosses
|
||||
// an edge or changes value.
|
||||
Event<void> onPerformed; // pressed (Button) / non-zero edge (axis)
|
||||
Event<void> onCanceled; // released (Button) / back to zero
|
||||
Event<float> onValueChanged; // Axis1D — value
|
||||
Event<Vector<float,2>> onVector2Changed; // Vector2 — vector2
|
||||
};
|
||||
|
||||
enum class CaptureMask : std::uint8_t {
|
||||
Keyboard = 1,
|
||||
Mouse = 2,
|
||||
Gamepad = 4,
|
||||
Any = 0xFF,
|
||||
};
|
||||
constexpr CaptureMask operator|(CaptureMask a, CaptureMask b) {
|
||||
return (CaptureMask)((std::uint8_t)a | (std::uint8_t)b);
|
||||
}
|
||||
constexpr bool HasFlag(CaptureMask m, CaptureMask f) {
|
||||
return ((std::uint8_t)m & (std::uint8_t)f) != 0;
|
||||
}
|
||||
|
||||
struct Map {
|
||||
std::vector<std::unique_ptr<Action>> actions;
|
||||
|
||||
Action& AddAction(std::string name, ActionType type);
|
||||
Action* Find(std::string_view name);
|
||||
|
||||
// Subscribe internally to the window's mouse-scroll event so a
|
||||
// single scroll between ticks isn't lost. All other state is
|
||||
// polled from `Window` / `Gamepad`. Detaching releases that one
|
||||
// listener; the polled-state reads automatically stop.
|
||||
void Attach(Window& window);
|
||||
void Detach();
|
||||
|
||||
// Apply current input state to all actions. Fire edge / value
|
||||
// events. Call once per frame AFTER `Gamepad::Tick()`. Safe to
|
||||
// call when not attached (no-op).
|
||||
void Tick();
|
||||
|
||||
// Capture the next qualifying input as a binding. While capture
|
||||
// is active, that input does NOT dispatch to actions — it only
|
||||
// routes to `onCaptured`. Capturing replaces nothing in the
|
||||
// action's bindings; the callback decides whether to assign,
|
||||
// append, or discard. Cancel via `StopRebind`.
|
||||
void StartRebind(Action& action, CaptureMask mask,
|
||||
std::function<void(Binding)> onCaptured);
|
||||
void StopRebind();
|
||||
bool IsRebinding() const;
|
||||
|
||||
// ─── Internals (exposed because we're a POD-ish struct) ──────
|
||||
Window* window = nullptr;
|
||||
// Mouse scroll is the one event-driven source; everything else
|
||||
// is polled. The listener feeds into `scrollAccumulator`,
|
||||
// drained on the next Tick.
|
||||
std::unique_ptr<EventListener<std::uint32_t>> scrollListener;
|
||||
std::int32_t scrollAccumulator = 0;
|
||||
|
||||
// Mouse delta is computed from window.currentMousePos vs the
|
||||
// position observed last tick. nullopt before first tick.
|
||||
std::optional<Vector<float, 2>> lastMousePos;
|
||||
|
||||
// Rebind state.
|
||||
struct RebindState {
|
||||
Action* action;
|
||||
CaptureMask mask;
|
||||
std::function<void(Binding)> onCaptured;
|
||||
// Snapshot of all input at rebind-start, so we only fire on
|
||||
// a fresh down-edge (not on a button held since before).
|
||||
std::unordered_set<KeyCode> keysHeldAtStart;
|
||||
bool mouseLeftHeldAtStart = false;
|
||||
bool mouseRightHeldAtStart = false;
|
||||
std::unordered_map<std::uint32_t, std::array<bool, (std::size_t)Gamepad::Button::Max>> gamepadButtonsAtStart;
|
||||
};
|
||||
std::optional<RebindState> rebind;
|
||||
};
|
||||
|
||||
// ─── Serialization ──────────────────────────────────────────────
|
||||
// Single-binding text round-trip. Caller composes binding files
|
||||
// however they like (JSON / INI / line-per-binding). Formats:
|
||||
// "key:<hex>" — KeyBind
|
||||
// "mb:<0|1>" — MouseButtonBind
|
||||
// "mscroll" — MouseScrollBind
|
||||
// "mdelta:<scale>" — MouseDeltaBind
|
||||
// "gpb:<id>:<button>" — GamepadButtonBind
|
||||
// "gpa:<id>:<axis>:<inv>" — GamepadAxisBind (inv = 0/1)
|
||||
// "gps:<id>:<stick>" — GamepadStickBind
|
||||
// "wasd:<u>:<d>:<l>:<r>" — WASDBind, four hex KeyCodes
|
||||
// KeyCode hex values are platform-specific; see :Keys.
|
||||
std::string BindingToString(const Binding&);
|
||||
std::optional<Binding> BindingFromString(std::string_view);
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@ module;
|
|||
export module Crafter.Graphics:InputField;
|
||||
import std;
|
||||
import :Types;
|
||||
import :Keys;
|
||||
import :Font;
|
||||
import :UI;
|
||||
import :UIComponents;
|
||||
|
|
@ -81,8 +82,12 @@ export namespace Crafter {
|
|||
void InputField_OnText(InputField&, std::string_view utf8);
|
||||
|
||||
// Edit-control keys: Backspace, Delete, Left, Right, Home, End. Anything
|
||||
// else is ignored. Safe to feed every key the host receives.
|
||||
void InputField_OnKey(InputField&, CrafterKeys);
|
||||
// else is ignored. Safe to feed every key the host receives. The KeyCode
|
||||
// is the raw platform key (Win32 PS/2 scancode + extended bit; Wayland
|
||||
// kernel keycode) — typically the value delivered by
|
||||
// `Window::onRawKeyDown`. Internal comparisons use `Key(CrafterKeys::X)`
|
||||
// from :Keys so the code stays cross-platform.
|
||||
void InputField_OnKey(InputField&, KeyCode);
|
||||
|
||||
// Map a click x-coord (in window pixels) to a cursor position. `rect` is
|
||||
// the field's current draw rect; `colors.paddingX` is consulted for the
|
||||
|
|
|
|||
331
interfaces/Crafter.Graphics-Keys.cppm
Normal file
331
interfaces/Crafter.Graphics-Keys.cppm
Normal file
|
|
@ -0,0 +1,331 @@
|
|||
/*
|
||||
Crafter®.Graphics
|
||||
Copyright (C) 2026 Catcrafts®
|
||||
Catcrafts.net
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 3.0 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
module;
|
||||
#ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND
|
||||
#include <linux/input-event-codes.h>
|
||||
#endif
|
||||
|
||||
export module Crafter.Graphics:Keys;
|
||||
import std;
|
||||
import :Types;
|
||||
|
||||
// Compile-time translation from the abstract `CrafterKeys` enum to the raw
|
||||
// platform key code that the runtime actually stores and compares. The whole
|
||||
// point: bindings, events, and InputField never carry the abstract enum at
|
||||
// runtime — they carry the raw integer the platform delivers. `Key(...)` is
|
||||
// `consteval` so every call from source folds to an immediate operand; user
|
||||
// code stays cross-platform (`Key(CrafterKeys::Space)`) without any `#ifdef`
|
||||
// at the call site.
|
||||
//
|
||||
// Domain of the returned KeyCode:
|
||||
// Win32: bits 0..7 = PS/2 set-1 scancode byte. Bit 8 = extended-key flag
|
||||
// (the 0xE0-prefixed variants — RightCtrl, RightAlt, the keypad's
|
||||
// Enter and /, the cursor cluster's arrows/Home/End/etc., the
|
||||
// Windows keys). At runtime, WndProc OR-s in 0x100 when lParam
|
||||
// bit 24 is set, so a saved binding for "RightCtrl" (0x11D) survives
|
||||
// round-trip with the actual hardware event.
|
||||
// Wayland/Linux: kernel input-event-codes (KEY_*) directly. The +8 X11
|
||||
// offset is stripped at the wl_keyboard.key boundary so the
|
||||
// runtime values match the table here.
|
||||
//
|
||||
// Keys with no clean scancode encoding (Pause, some keys without standard
|
||||
// physical position) return 0. 0 is documented as "unmapped"; binding to it
|
||||
// will never match a real keypress.
|
||||
|
||||
export namespace Crafter {
|
||||
using KeyCode = std::uint32_t;
|
||||
|
||||
consteval KeyCode Key(CrafterKeys k) {
|
||||
#ifdef CRAFTER_GRAPHICS_WINDOW_WIN32
|
||||
// PS/2 set-1 scancodes. Extended keys carry 0x100 so the runtime
|
||||
// path can disambiguate by OR-ing in 0x100 when lParam bit 24 is set.
|
||||
switch (k) {
|
||||
// Alphabetic
|
||||
case CrafterKeys::A: return 0x1E;
|
||||
case CrafterKeys::B: return 0x30;
|
||||
case CrafterKeys::C: return 0x2E;
|
||||
case CrafterKeys::D: return 0x20;
|
||||
case CrafterKeys::E: return 0x12;
|
||||
case CrafterKeys::F: return 0x21;
|
||||
case CrafterKeys::G: return 0x22;
|
||||
case CrafterKeys::H: return 0x23;
|
||||
case CrafterKeys::I: return 0x17;
|
||||
case CrafterKeys::J: return 0x24;
|
||||
case CrafterKeys::K: return 0x25;
|
||||
case CrafterKeys::L: return 0x26;
|
||||
case CrafterKeys::M: return 0x32;
|
||||
case CrafterKeys::N: return 0x31;
|
||||
case CrafterKeys::O: return 0x18;
|
||||
case CrafterKeys::P: return 0x19;
|
||||
case CrafterKeys::Q: return 0x10;
|
||||
case CrafterKeys::R: return 0x13;
|
||||
case CrafterKeys::S: return 0x1F;
|
||||
case CrafterKeys::T: return 0x14;
|
||||
case CrafterKeys::U: return 0x16;
|
||||
case CrafterKeys::V: return 0x2F;
|
||||
case CrafterKeys::W: return 0x11;
|
||||
case CrafterKeys::X: return 0x2D;
|
||||
case CrafterKeys::Y: return 0x15;
|
||||
case CrafterKeys::Z: return 0x2C;
|
||||
// Numeric (top row)
|
||||
case CrafterKeys::_1: return 0x02;
|
||||
case CrafterKeys::_2: return 0x03;
|
||||
case CrafterKeys::_3: return 0x04;
|
||||
case CrafterKeys::_4: return 0x05;
|
||||
case CrafterKeys::_5: return 0x06;
|
||||
case CrafterKeys::_6: return 0x07;
|
||||
case CrafterKeys::_7: return 0x08;
|
||||
case CrafterKeys::_8: return 0x09;
|
||||
case CrafterKeys::_9: return 0x0A;
|
||||
case CrafterKeys::_0: return 0x0B;
|
||||
// Function keys
|
||||
case CrafterKeys::F1: return 0x3B;
|
||||
case CrafterKeys::F2: return 0x3C;
|
||||
case CrafterKeys::F3: return 0x3D;
|
||||
case CrafterKeys::F4: return 0x3E;
|
||||
case CrafterKeys::F5: return 0x3F;
|
||||
case CrafterKeys::F6: return 0x40;
|
||||
case CrafterKeys::F7: return 0x41;
|
||||
case CrafterKeys::F8: return 0x42;
|
||||
case CrafterKeys::F9: return 0x43;
|
||||
case CrafterKeys::F10: return 0x44;
|
||||
case CrafterKeys::F11: return 0x57;
|
||||
case CrafterKeys::F12: return 0x58;
|
||||
// Control keys
|
||||
case CrafterKeys::Escape: return 0x01;
|
||||
case CrafterKeys::Tab: return 0x0F;
|
||||
case CrafterKeys::Enter: return 0x1C;
|
||||
case CrafterKeys::Space: return 0x39;
|
||||
case CrafterKeys::Backspace: return 0x0E;
|
||||
case CrafterKeys::Delete: return 0x153; // extended
|
||||
case CrafterKeys::Insert: return 0x152; // extended
|
||||
case CrafterKeys::Home: return 0x147; // extended
|
||||
case CrafterKeys::End: return 0x14F; // extended
|
||||
case CrafterKeys::PageUp: return 0x149; // extended
|
||||
case CrafterKeys::PageDown: return 0x151; // extended
|
||||
case CrafterKeys::CapsLock: return 0x3A;
|
||||
case CrafterKeys::NumLock: return 0x45;
|
||||
case CrafterKeys::ScrollLock: return 0x46;
|
||||
// Modifiers
|
||||
case CrafterKeys::LeftShift: return 0x2A;
|
||||
case CrafterKeys::RightShift: return 0x36;
|
||||
case CrafterKeys::LeftCtrl: return 0x1D;
|
||||
case CrafterKeys::RightCtrl: return 0x11D; // extended
|
||||
case CrafterKeys::LeftAlt: return 0x38;
|
||||
case CrafterKeys::RightAlt: return 0x138; // extended
|
||||
case CrafterKeys::LeftSuper: return 0x15B; // extended
|
||||
case CrafterKeys::RightSuper: return 0x15C; // extended
|
||||
// Arrows (all extended on Win32 — the cursor cluster, not the keypad)
|
||||
case CrafterKeys::Up: return 0x148;
|
||||
case CrafterKeys::Down: return 0x150;
|
||||
case CrafterKeys::Left: return 0x14B;
|
||||
case CrafterKeys::Right: return 0x14D;
|
||||
// Keypad
|
||||
case CrafterKeys::keypad_0: return 0x52;
|
||||
case CrafterKeys::keypad_1: return 0x4F;
|
||||
case CrafterKeys::keypad_2: return 0x50;
|
||||
case CrafterKeys::keypad_3: return 0x51;
|
||||
case CrafterKeys::keypad_4: return 0x4B;
|
||||
case CrafterKeys::keypad_5: return 0x4C;
|
||||
case CrafterKeys::keypad_6: return 0x4D;
|
||||
case CrafterKeys::keypad_7: return 0x47;
|
||||
case CrafterKeys::keypad_8: return 0x48;
|
||||
case CrafterKeys::keypad_9: return 0x49;
|
||||
case CrafterKeys::keypad_enter: return 0x11C; // extended
|
||||
case CrafterKeys::keypad_plus: return 0x4E;
|
||||
case CrafterKeys::keypad_minus: return 0x4A;
|
||||
case CrafterKeys::keypad_multiply: return 0x37;
|
||||
case CrafterKeys::keypad_divide: return 0x135; // extended
|
||||
case CrafterKeys::keypad_decimal: return 0x53;
|
||||
// Punctuation
|
||||
case CrafterKeys::grave: return 0x29;
|
||||
case CrafterKeys::minus: return 0x0C;
|
||||
case CrafterKeys::equal: return 0x0D;
|
||||
case CrafterKeys::bracket_left: return 0x1A;
|
||||
case CrafterKeys::bracket_right: return 0x1B;
|
||||
case CrafterKeys::backslash: return 0x2B;
|
||||
case CrafterKeys::semicolon: return 0x27;
|
||||
case CrafterKeys::quote: return 0x28;
|
||||
case CrafterKeys::comma: return 0x33;
|
||||
case CrafterKeys::period: return 0x34;
|
||||
case CrafterKeys::slash: return 0x35;
|
||||
case CrafterKeys::print_screen: return 0x137; // extended
|
||||
case CrafterKeys::pause: return 0; // unmapped — multi-byte sequence
|
||||
case CrafterKeys::menu: return 0x15D; // extended (App key)
|
||||
// Multimedia / browser / launch keys (all extended on Win32)
|
||||
case CrafterKeys::volume_up: return 0x130;
|
||||
case CrafterKeys::volume_down: return 0x12E;
|
||||
case CrafterKeys::volume_mute: return 0x120;
|
||||
case CrafterKeys::media_play: return 0x122;
|
||||
case CrafterKeys::media_stop: return 0x124;
|
||||
case CrafterKeys::media_prev: return 0x110;
|
||||
case CrafterKeys::media_next: return 0x119;
|
||||
case CrafterKeys::browser_back: return 0x16A;
|
||||
case CrafterKeys::browser_forward: return 0x169;
|
||||
case CrafterKeys::browser_refresh: return 0x167;
|
||||
case CrafterKeys::browser_stop: return 0x168;
|
||||
case CrafterKeys::browser_search: return 0x165;
|
||||
case CrafterKeys::browser_home: return 0x132;
|
||||
case CrafterKeys::launch_mail: return 0x16C;
|
||||
case CrafterKeys::launch_calculator: return 0x121;
|
||||
case CrafterKeys::launch_media_player: return 0x16D;
|
||||
case CrafterKeys::CrafterKeysMax: return 0;
|
||||
}
|
||||
#endif
|
||||
#ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND
|
||||
// Linux kernel input-event-codes. The Wayland keyboard handler strips
|
||||
// the +8 X11 offset before delivery so these match wl_keyboard.key
|
||||
// values directly.
|
||||
switch (k) {
|
||||
// Alphabetic
|
||||
case CrafterKeys::A: return KEY_A;
|
||||
case CrafterKeys::B: return KEY_B;
|
||||
case CrafterKeys::C: return KEY_C;
|
||||
case CrafterKeys::D: return KEY_D;
|
||||
case CrafterKeys::E: return KEY_E;
|
||||
case CrafterKeys::F: return KEY_F;
|
||||
case CrafterKeys::G: return KEY_G;
|
||||
case CrafterKeys::H: return KEY_H;
|
||||
case CrafterKeys::I: return KEY_I;
|
||||
case CrafterKeys::J: return KEY_J;
|
||||
case CrafterKeys::K: return KEY_K;
|
||||
case CrafterKeys::L: return KEY_L;
|
||||
case CrafterKeys::M: return KEY_M;
|
||||
case CrafterKeys::N: return KEY_N;
|
||||
case CrafterKeys::O: return KEY_O;
|
||||
case CrafterKeys::P: return KEY_P;
|
||||
case CrafterKeys::Q: return KEY_Q;
|
||||
case CrafterKeys::R: return KEY_R;
|
||||
case CrafterKeys::S: return KEY_S;
|
||||
case CrafterKeys::T: return KEY_T;
|
||||
case CrafterKeys::U: return KEY_U;
|
||||
case CrafterKeys::V: return KEY_V;
|
||||
case CrafterKeys::W: return KEY_W;
|
||||
case CrafterKeys::X: return KEY_X;
|
||||
case CrafterKeys::Y: return KEY_Y;
|
||||
case CrafterKeys::Z: return KEY_Z;
|
||||
// Numeric
|
||||
case CrafterKeys::_0: return KEY_0;
|
||||
case CrafterKeys::_1: return KEY_1;
|
||||
case CrafterKeys::_2: return KEY_2;
|
||||
case CrafterKeys::_3: return KEY_3;
|
||||
case CrafterKeys::_4: return KEY_4;
|
||||
case CrafterKeys::_5: return KEY_5;
|
||||
case CrafterKeys::_6: return KEY_6;
|
||||
case CrafterKeys::_7: return KEY_7;
|
||||
case CrafterKeys::_8: return KEY_8;
|
||||
case CrafterKeys::_9: return KEY_9;
|
||||
// Function keys
|
||||
case CrafterKeys::F1: return KEY_F1;
|
||||
case CrafterKeys::F2: return KEY_F2;
|
||||
case CrafterKeys::F3: return KEY_F3;
|
||||
case CrafterKeys::F4: return KEY_F4;
|
||||
case CrafterKeys::F5: return KEY_F5;
|
||||
case CrafterKeys::F6: return KEY_F6;
|
||||
case CrafterKeys::F7: return KEY_F7;
|
||||
case CrafterKeys::F8: return KEY_F8;
|
||||
case CrafterKeys::F9: return KEY_F9;
|
||||
case CrafterKeys::F10: return KEY_F10;
|
||||
case CrafterKeys::F11: return KEY_F11;
|
||||
case CrafterKeys::F12: return KEY_F12;
|
||||
// Control keys
|
||||
case CrafterKeys::Escape: return KEY_ESC;
|
||||
case CrafterKeys::Tab: return KEY_TAB;
|
||||
case CrafterKeys::Enter: return KEY_ENTER;
|
||||
case CrafterKeys::Space: return KEY_SPACE;
|
||||
case CrafterKeys::Backspace: return KEY_BACKSPACE;
|
||||
case CrafterKeys::Delete: return KEY_DELETE;
|
||||
case CrafterKeys::Insert: return KEY_INSERT;
|
||||
case CrafterKeys::Home: return KEY_HOME;
|
||||
case CrafterKeys::End: return KEY_END;
|
||||
case CrafterKeys::PageUp: return KEY_PAGEUP;
|
||||
case CrafterKeys::PageDown: return KEY_PAGEDOWN;
|
||||
case CrafterKeys::CapsLock: return KEY_CAPSLOCK;
|
||||
case CrafterKeys::NumLock: return KEY_NUMLOCK;
|
||||
case CrafterKeys::ScrollLock: return KEY_SCROLLLOCK;
|
||||
// Modifiers
|
||||
case CrafterKeys::LeftShift: return KEY_LEFTSHIFT;
|
||||
case CrafterKeys::RightShift: return KEY_RIGHTSHIFT;
|
||||
case CrafterKeys::LeftCtrl: return KEY_LEFTCTRL;
|
||||
case CrafterKeys::RightCtrl: return KEY_RIGHTCTRL;
|
||||
case CrafterKeys::LeftAlt: return KEY_LEFTALT;
|
||||
case CrafterKeys::RightAlt: return KEY_RIGHTALT;
|
||||
case CrafterKeys::LeftSuper: return KEY_LEFTMETA;
|
||||
case CrafterKeys::RightSuper: return KEY_RIGHTMETA;
|
||||
// Arrows
|
||||
case CrafterKeys::Up: return KEY_UP;
|
||||
case CrafterKeys::Down: return KEY_DOWN;
|
||||
case CrafterKeys::Left: return KEY_LEFT;
|
||||
case CrafterKeys::Right: return KEY_RIGHT;
|
||||
// Keypad
|
||||
case CrafterKeys::keypad_0: return KEY_KP0;
|
||||
case CrafterKeys::keypad_1: return KEY_KP1;
|
||||
case CrafterKeys::keypad_2: return KEY_KP2;
|
||||
case CrafterKeys::keypad_3: return KEY_KP3;
|
||||
case CrafterKeys::keypad_4: return KEY_KP4;
|
||||
case CrafterKeys::keypad_5: return KEY_KP5;
|
||||
case CrafterKeys::keypad_6: return KEY_KP6;
|
||||
case CrafterKeys::keypad_7: return KEY_KP7;
|
||||
case CrafterKeys::keypad_8: return KEY_KP8;
|
||||
case CrafterKeys::keypad_9: return KEY_KP9;
|
||||
case CrafterKeys::keypad_enter: return KEY_KPENTER;
|
||||
case CrafterKeys::keypad_plus: return KEY_KPPLUS;
|
||||
case CrafterKeys::keypad_minus: return KEY_KPMINUS;
|
||||
case CrafterKeys::keypad_multiply: return KEY_KPASTERISK;
|
||||
case CrafterKeys::keypad_divide: return KEY_KPSLASH;
|
||||
case CrafterKeys::keypad_decimal: return KEY_KPDOT;
|
||||
// Punctuation
|
||||
case CrafterKeys::grave: return KEY_GRAVE;
|
||||
case CrafterKeys::minus: return KEY_MINUS;
|
||||
case CrafterKeys::equal: return KEY_EQUAL;
|
||||
case CrafterKeys::bracket_left: return KEY_LEFTBRACE;
|
||||
case CrafterKeys::bracket_right: return KEY_RIGHTBRACE;
|
||||
case CrafterKeys::backslash: return KEY_BACKSLASH;
|
||||
case CrafterKeys::semicolon: return KEY_SEMICOLON;
|
||||
case CrafterKeys::quote: return KEY_APOSTROPHE;
|
||||
case CrafterKeys::comma: return KEY_COMMA;
|
||||
case CrafterKeys::period: return KEY_DOT;
|
||||
case CrafterKeys::slash: return KEY_SLASH;
|
||||
case CrafterKeys::print_screen: return KEY_SYSRQ;
|
||||
case CrafterKeys::pause: return KEY_PAUSE;
|
||||
case CrafterKeys::menu: return KEY_COMPOSE;
|
||||
// Multimedia / browser / launch keys
|
||||
case CrafterKeys::volume_up: return KEY_VOLUMEUP;
|
||||
case CrafterKeys::volume_down: return KEY_VOLUMEDOWN;
|
||||
case CrafterKeys::volume_mute: return KEY_MUTE;
|
||||
case CrafterKeys::media_play: return KEY_PLAYPAUSE;
|
||||
case CrafterKeys::media_stop: return KEY_STOPCD;
|
||||
case CrafterKeys::media_prev: return KEY_PREVIOUSSONG;
|
||||
case CrafterKeys::media_next: return KEY_NEXTSONG;
|
||||
case CrafterKeys::browser_back: return KEY_BACK;
|
||||
case CrafterKeys::browser_forward: return KEY_FORWARD;
|
||||
case CrafterKeys::browser_refresh: return KEY_REFRESH;
|
||||
case CrafterKeys::browser_stop: return KEY_STOP;
|
||||
case CrafterKeys::browser_search: return KEY_SEARCH;
|
||||
case CrafterKeys::browser_home: return KEY_HOMEPAGE;
|
||||
case CrafterKeys::launch_mail: return KEY_MAIL;
|
||||
case CrafterKeys::launch_calculator: return KEY_CALC;
|
||||
case CrafterKeys::launch_media_player: return KEY_MEDIA;
|
||||
case CrafterKeys::CrafterKeysMax: return 0;
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -24,6 +24,7 @@ module;
|
|||
export module Crafter.Graphics:Mesh;
|
||||
import std;
|
||||
import Crafter.Math;
|
||||
import Crafter.Asset;
|
||||
import :VulkanBuffer;
|
||||
|
||||
export namespace Crafter {
|
||||
|
|
@ -33,11 +34,25 @@ export namespace Crafter {
|
|||
VulkanBuffer<char, false> blasBuffer;
|
||||
VulkanBuffer<Vector<float, 3, 3>, true> vertexBuffer;
|
||||
VulkanBuffer<std::uint32_t, true> indexBuffer;
|
||||
// Lives until the cmd buffer issued by the compressed Build path
|
||||
// completes execution. Kept as a member so the recorded
|
||||
// vkCmdDecompressMemoryEXT references valid memory until the queue
|
||||
// submit signals — caller must not re-Build or destroy the Mesh
|
||||
// before that submit's fence is signaled (same contract as the
|
||||
// existing uncompressed path).
|
||||
VulkanBuffer<std::byte, true> compressedStaging;
|
||||
VkAccelerationStructureGeometryTrianglesDataKHR blasData;
|
||||
VkAccelerationStructureGeometryKHR blas;
|
||||
VkAccelerationStructureKHR accelerationStructure;
|
||||
VkDeviceAddress blasAddr;
|
||||
bool opaque;
|
||||
void Build(std::span<Vector<float, 3, 3>> verticies, std::span<std::uint32_t> indicies, VkCommandBuffer cmd);
|
||||
// GPU path: decompresses vertex (region 0) and index (region 1) streams
|
||||
// from asset.blob into vertexBuffer / indexBuffer using
|
||||
// VK_EXT_memory_decompression. Falls back to CPU decode + the
|
||||
// uncompressed Build if Device::memoryDecompressionSupported is false.
|
||||
// Region 2 (data) is not consumed here — the caller decompresses it
|
||||
// into their own buffer if needed (or uses Compression::DecompressCPU).
|
||||
void Build(const ::Crafter::CompressedMeshAsset& asset, VkCommandBuffer cmd);
|
||||
};
|
||||
}
|
||||
|
|
@ -274,6 +274,12 @@ export namespace Crafter {
|
|||
|
||||
bool firstDispatchThisFrame_ = true;
|
||||
|
||||
// Subscription to window.onResize. Each resize destroys the old
|
||||
// swapchain images, so the per-frame heap entries we wrote at
|
||||
// outImageSlot_ now reference dangling VkImage handles. The
|
||||
// listener re-writes them and flushes the descriptor heaps.
|
||||
Crafter::EventListener<void> resizeSub_;
|
||||
|
||||
void WriteSwapchainDescriptors();
|
||||
void WriteFontAtlasDescriptor();
|
||||
|
||||
|
|
|
|||
|
|
@ -52,12 +52,20 @@ namespace Crafter {
|
|||
class VulkanBuffer : public VulkanBufferBase, public VulkanBufferMappedConditional<T, Mapped> {
|
||||
public:
|
||||
VulkanBuffer() = default;
|
||||
void Create(VkBufferUsageFlags usageFlags, VkMemoryPropertyFlags memoryPropertyFlags, std::uint32_t count) {
|
||||
void Create(VkBufferUsageFlags2 usageFlags, VkMemoryPropertyFlags memoryPropertyFlags, std::uint32_t count) {
|
||||
size = count * sizeof(T);
|
||||
|
||||
// Carry usage in the maintenance5 flags2 chain so 64-bit bits
|
||||
// (e.g. VK_BUFFER_USAGE_2_MEMORY_DECOMPRESSION_BIT_EXT, bit 35)
|
||||
// are not truncated.
|
||||
VkBufferUsageFlags2CreateInfo usageFlags2 {
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_USAGE_FLAGS_2_CREATE_INFO,
|
||||
.usage = usageFlags,
|
||||
};
|
||||
VkBufferCreateInfo bufferCreateInfo {};
|
||||
bufferCreateInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
||||
bufferCreateInfo.usage = usageFlags;
|
||||
bufferCreateInfo.pNext = &usageFlags2;
|
||||
bufferCreateInfo.usage = 0;
|
||||
bufferCreateInfo.size = sizeof(T)*count;
|
||||
Device::CheckVkResult(vkCreateBuffer(Device::device, &bufferCreateInfo, nullptr, &buffer));
|
||||
|
||||
|
|
@ -98,7 +106,7 @@ namespace Crafter {
|
|||
buffer = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
void Resize(VkBufferUsageFlags usageFlags, VkMemoryPropertyFlags memoryPropertyFlags, std::uint32_t count) {
|
||||
void Resize(VkBufferUsageFlags2 usageFlags, VkMemoryPropertyFlags memoryPropertyFlags, std::uint32_t count) {
|
||||
if(buffer != VK_NULL_HANDLE) {
|
||||
Clear();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ module;
|
|||
export module Crafter.Graphics:Window;
|
||||
import std;
|
||||
import :Types;
|
||||
import :Keys;
|
||||
import Crafter.Event;
|
||||
|
||||
export namespace Crafter {
|
||||
|
|
@ -66,15 +67,21 @@ export namespace Crafter {
|
|||
Event<void> onClose;
|
||||
Event<void> onBeforeUpdate;
|
||||
Event<FrameTime> onUpdate;
|
||||
// Fires when the swapchain has been recreated for a new size.
|
||||
// width/height already reflect the new size. Passes that hold
|
||||
// descriptors referring to window.imageViews[] must re-write them
|
||||
// here (the old VkImage handles have been destroyed).
|
||||
Event<void> onResize;
|
||||
bool open = true;
|
||||
bool updating = false;
|
||||
bool heldkeys[static_cast<std::uint32_t>(CrafterKeys::CrafterKeysMax)] = {};
|
||||
Event<void> onKeyDown[static_cast<std::uint32_t>(CrafterKeys::CrafterKeysMax)];
|
||||
Event<void> onKeyHold[static_cast<std::uint32_t>(CrafterKeys::CrafterKeysMax)];
|
||||
Event<void> onKeyUp[static_cast<std::uint32_t>(CrafterKeys::CrafterKeysMax)];
|
||||
Event<CrafterKeys> onAnyKeyDown;
|
||||
Event<CrafterKeys> onAnyKeyHold;
|
||||
Event<CrafterKeys> onAnyKeyUp;
|
||||
// Currently-pressed raw key codes. The runtime stores raw platform
|
||||
// codes only (Win32 PS/2 scancode + extended bit; Wayland kernel
|
||||
// keycode). Cross-platform default bindings use `Key(CrafterKeys::X)`
|
||||
// from :Keys to obtain the right code at compile time.
|
||||
std::unordered_set<KeyCode> heldKeys;
|
||||
Event<KeyCode> onRawKeyDown;
|
||||
Event<KeyCode> onRawKeyHold;
|
||||
Event<KeyCode> onRawKeyUp;
|
||||
Event<const std::string_view> onTextInput;
|
||||
Event<void> onMouseRightClick;
|
||||
Event<void> onMouseLeftClick;
|
||||
|
|
@ -141,6 +148,11 @@ export namespace Crafter {
|
|||
#ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND
|
||||
float scale = 1.0f;
|
||||
bool configured = false;
|
||||
// Pending size from the most recent xdg_toplevel.configure, in
|
||||
// surface-local (logical DP) units. Applied on xdg_surface.configure
|
||||
// via Resize(); 0 means "compositor has no preference, keep current".
|
||||
std::int32_t pendingLogicalWidth = 0;
|
||||
std::int32_t pendingLogicalHeight = 0;
|
||||
xdg_toplevel* xdgToplevel = nullptr;
|
||||
wp_viewport* wpViewport = nullptr;
|
||||
wl_surface* surface = nullptr;
|
||||
|
|
@ -186,6 +198,12 @@ export namespace Crafter {
|
|||
VkCommandBuffer GetCmd();
|
||||
void EndCmd(VkCommandBuffer cmd);
|
||||
void CreateSwapchain();
|
||||
// Tear-and-rebuild helper used by Resize() and the OUT_OF_DATE
|
||||
// recovery in Render(). Calls CreateSwapchain() and re-issues the
|
||||
// initial PRESENT_SRC_KHR layout transition for the new images so
|
||||
// Render()'s barriers (which assume oldLayout = PRESENT_SRC_KHR)
|
||||
// stay valid. Does NOT fire onResize — callers do that.
|
||||
void RecreateSwapchainAndImages();
|
||||
|
||||
// Save the current swapchain image (state after Render() returns) to
|
||||
// a PNG file. Allocates a one-shot staging buffer + command buffer,
|
||||
|
|
|
|||
|
|
@ -20,8 +20,11 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|||
|
||||
export module Crafter.Graphics;
|
||||
|
||||
export import :Window;
|
||||
export import :Types;
|
||||
export import :Keys;
|
||||
export import :Gamepad;
|
||||
export import :Window;
|
||||
export import :Input;
|
||||
export import :Device;
|
||||
export import :Font;
|
||||
export import :Animation;
|
||||
|
|
@ -42,4 +45,6 @@ export import :FontAtlas;
|
|||
export import :ComputeShader;
|
||||
export import :UI;
|
||||
export import :UIComponents;
|
||||
export import :InputField;
|
||||
export import :InputField;
|
||||
export import :Clipboard;
|
||||
export import :Decompress;
|
||||
Loading…
Add table
Add a link
Reference in a new issue