From 348daff9f24a3edb3263e9a9dd72b65a5f333839 Mon Sep 17 00:00:00 2001 From: agent Date: Thu, 29 Oct 2026 05:24:06 +0300 Subject: [PATCH 1/5] GraphicApplication: platform-independent input layer Starts the GraphicApplication module, which replaces Graphics. As in Sketch2D, each library lives in src// with its own public/ and private/; only public/ is exported and headers are included by name. input_layer/ is the boundary between platform backends and input consumers. InputEvent is the one declaration of every event: a std::variant of small structs (pointer sample, move, explicit PointerLeft, button, scroll, scroll end, key, text, focus, gesture). InputSink::post() is the only entry point, so the coalescer, the per-frame state and recordings all derive from the same type instead of re-listing event kinds. Keys and pointer buttons are separate enums (Key, PointerButton) and separate events; KeyAction covers press, release and repeat. InputCoalescer batches posted events per frame under delivery policies and forwards every raw event to an optional tap. InputQueue collects delivered events, releases held buttons and keys when the pointer or focus is lost, and produces a read-only InputState: the ordered event log plus the pointer, button, key, scroll and pressure state derived from it. InputView is a consumer's view with its own pointer origin and optionally hidden buttons, so callers never mutate shared state. InputRecording serializes every event kind (text, key repeat and gestures included) through one format table per variant alternative, and replays into any InputSink. Two-finger touch gestures are tracked here too. The layer depends on no window system and is unit-tested directly. --- .../input_layer/private/InputCoalescer.cpp | 85 ++++ .../input_layer/private/InputRecording.cpp | 413 ++++++++++++++++++ .../src/input_layer/private/InputState.cpp | 134 ++++++ .../src/input_layer/private/TouchGesture.cpp | 122 ++++++ .../src/input_layer/public/InputCoalescer.hpp | 52 +++ .../src/input_layer/public/InputCodes.hpp | 137 ++++++ .../src/input_layer/public/InputEvent.hpp | 93 ++++ .../src/input_layer/public/InputRecording.hpp | 54 +++ .../src/input_layer/public/InputSink.hpp | 18 + .../src/input_layer/public/InputState.hpp | 118 +++++ .../src/input_layer/public/PointerSample.hpp | 25 ++ .../src/input_layer/public/TouchGesture.hpp | 58 +++ .../tests/InputCoalescerTests.cpp | 265 +++++++++++ .../tests/InputRecordingTests.cpp | 135 ++++++ GraphicApplication/tests/InputStateTests.cpp | 128 ++++++ GraphicApplication/tests/Main.cpp | 3 + .../tests/TouchGestureTests.cpp | 174 ++++++++ 17 files changed, 2014 insertions(+) create mode 100644 GraphicApplication/src/input_layer/private/InputCoalescer.cpp create mode 100644 GraphicApplication/src/input_layer/private/InputRecording.cpp create mode 100644 GraphicApplication/src/input_layer/private/InputState.cpp create mode 100644 GraphicApplication/src/input_layer/private/TouchGesture.cpp create mode 100644 GraphicApplication/src/input_layer/public/InputCoalescer.hpp create mode 100644 GraphicApplication/src/input_layer/public/InputCodes.hpp create mode 100644 GraphicApplication/src/input_layer/public/InputEvent.hpp create mode 100644 GraphicApplication/src/input_layer/public/InputRecording.hpp create mode 100644 GraphicApplication/src/input_layer/public/InputSink.hpp create mode 100644 GraphicApplication/src/input_layer/public/InputState.hpp create mode 100644 GraphicApplication/src/input_layer/public/PointerSample.hpp create mode 100644 GraphicApplication/src/input_layer/public/TouchGesture.hpp create mode 100644 GraphicApplication/tests/InputCoalescerTests.cpp create mode 100644 GraphicApplication/tests/InputRecordingTests.cpp create mode 100644 GraphicApplication/tests/InputStateTests.cpp create mode 100644 GraphicApplication/tests/Main.cpp create mode 100644 GraphicApplication/tests/TouchGestureTests.cpp diff --git a/GraphicApplication/src/input_layer/private/InputCoalescer.cpp b/GraphicApplication/src/input_layer/private/InputCoalescer.cpp new file mode 100644 index 00000000..a931af2d --- /dev/null +++ b/GraphicApplication/src/input_layer/private/InputCoalescer.cpp @@ -0,0 +1,85 @@ +#include "InputCoalescer.hpp" + +using namespace tp; + +namespace { + bool isGestureUpdate(const InputEvent& event) { + const auto* changed = std::get_if(&event); + return changed && changed->gesture.phase == GesturePhase::Update; + } +} + +void InputCoalescer::post(const InputEvent& event) { + if (mTap) { + mTap->post(event); + } + mStatistics.received++; + + if (const auto* sampled = std::get_if(&event)) { + mSample = sampled->sample; + return; + } + if (merge(event)) { + return; + } + mEvents.push_back({ event, mSample }); +} + +bool InputCoalescer::merge(const InputEvent& event) { + if (mEvents.empty()) { + return false; + } + Pending& previous = mEvents.back(); + + if (const auto* moved = std::get_if(&event)) { + auto* last = std::get_if(&previous.event); + if (!last || mPolicies.pointerMotion == DeliveryPolicy::Every) { + return false; + } + last->position = + mPolicies.pointerMotion == DeliveryPolicy::Latest ? moved->position : last->position + moved->position; + previous.sample = mSample; + return true; + } + + if (const auto* scrolled = std::get_if(&event)) { + auto* last = std::get_if(&previous.event); + if (!last || last->source != scrolled->source || mPolicies.scroll == DeliveryPolicy::Every) { + return false; + } + last->delta = mPolicies.scroll == DeliveryPolicy::Latest ? scrolled->delta : last->delta + scrolled->delta; + return true; + } + + if (isGestureUpdate(event) && isGestureUpdate(previous.event)) { + accumulate(std::get(previous.event).gesture, std::get(event).gesture); + return true; + } + return false; +} + +bool InputCoalescer::carriesSample(const InputEvent& event) { + return std::holds_alternative(event) || std::holds_alternative(event); +} + +bool InputCoalescer::isEmptyScroll(const InputEvent& event) { + const auto* scrolled = std::get_if(&event); + return scrolled && scrolled->delta == Vec2F{ 0, 0 }; +} + +void InputCoalescer::deliver(InputSink& sink) { + std::vector events; + events.swap(mEvents); + + for (const Pending& pending : events) { + if (isEmptyScroll(pending.event)) { + continue; + } + if (carriesSample(pending.event) && !(pending.sample == mDeliveredSample)) { + mDeliveredSample = pending.sample; + sink.post(PointerSampled{ pending.sample }); + } + mStatistics.delivered++; + sink.post(pending.event); + } +} diff --git a/GraphicApplication/src/input_layer/private/InputRecording.cpp b/GraphicApplication/src/input_layer/private/InputRecording.cpp new file mode 100644 index 00000000..6db8ca08 --- /dev/null +++ b/GraphicApplication/src/input_layer/private/InputRecording.cpp @@ -0,0 +1,413 @@ +#include "InputRecording.hpp" + +#include +#include +#include +#include +#include +#include + +namespace tp { + + namespace { + const char* const kDeviceNames[] = { "mouse", "finger", "stylus" }; + const char* const kTipNames[] = { "pen", "eraser", "brush", "pencil", "airbrush" }; + const char* const kButtonNames[] = { "left", "right", "middle", "back", "forward" }; + const char* const kScrollSourceNames[] = { "wheel", "touchpad" }; + const char* const kKeyActionNames[] = { "press", "release", "repeat" }; + const char* const kGesturePhaseNames[] = { "begin", "update", "end" }; + + template + void writeName(std::ostream& out, const char* const (&names)[Count], Enum value) { + out << ' ' << names[(size_t) value]; + } + + template + bool readName(std::istream& in, const char* const (&names)[Count], Enum& value) { + std::string name; + in >> name; + for (size_t index = 0; index < Count; index++) { + if (name == names[index]) { + value = (Enum) index; + return true; + } + } + return false; + } + + void writeVector(std::ostream& out, Vec2F value) { out << ' ' << value.x << ' ' << value.y; } + + void readVector(std::istream& in, Vec2F& value) { in >> value.x >> value.y; } + + std::string escapedText(const std::string& text) { + std::string escaped; + for (const unsigned char byte : text) { + if (byte <= ' ' || byte == '%' || byte == 0x7f) { + char code[4]; + std::snprintf(code, sizeof(code), "%%%02X", byte); + escaped += code; + } else { + escaped += (char) byte; + } + } + return escaped; + } + + std::optional unescapedText(const std::string& escaped) { + std::string text; + for (size_t index = 0; index < escaped.size(); index++) { + if (escaped[index] != '%') { + text += escaped[index]; + continue; + } + if (index + 2 >= escaped.size()) { + return std::nullopt; + } + text += (char) std::stoi(escaped.substr(index + 1, 2), nullptr, 16); + index += 2; + } + return text; + } + + // Recording format of each InputEvent alternative. A missing specialization fails to compile. + template + struct RecordedFormat; + + template <> + struct RecordedFormat { + static constexpr const char* kName = "sample"; + + static void write(std::ostream& out, const PointerSampled& event) { + const PointerSample& sample = event.sample; + writeName(out, kDeviceNames, sample.device); + writeName(out, kTipNames, sample.tip); + out << ' ' << sample.pressure; + writeVector(out, sample.tilt); + out << ' ' << sample.rotation << ' ' << sample.distance << ' ' << sample.timeMs; + } + + static bool read(std::istream& in, PointerSampled& event) { + PointerSample& sample = event.sample; + if (!readName(in, kDeviceNames, sample.device) || !readName(in, kTipNames, sample.tip)) { + return false; + } + in >> sample.pressure; + readVector(in, sample.tilt); + in >> sample.rotation >> sample.distance >> sample.timeMs; + return true; + } + }; + + template <> + struct RecordedFormat { + static constexpr const char* kName = "move"; + + static void write(std::ostream& out, const PointerMoved& event) { writeVector(out, event.position); } + + static bool read(std::istream& in, PointerMoved& event) { + readVector(in, event.position); + return true; + } + }; + + template <> + struct RecordedFormat { + static constexpr const char* kName = "leave"; + + static void write(std::ostream&, const PointerLeft&) {} + + static bool read(std::istream&, PointerLeft&) { return true; } + }; + + template <> + struct RecordedFormat { + static constexpr const char* kName = "button"; + + static void write(std::ostream& out, const PointerButtonChanged& event) { + writeName(out, kButtonNames, event.button); + out << ' ' << (int) event.pressed; + } + + static bool read(std::istream& in, PointerButtonChanged& event) { + int pressed = 0; + if (!readName(in, kButtonNames, event.button)) { + return false; + } + in >> pressed; + event.pressed = pressed != 0; + return true; + } + }; + + template <> + struct RecordedFormat { + static constexpr const char* kName = "scroll"; + + static void write(std::ostream& out, const Scrolled& event) { + writeName(out, kScrollSourceNames, event.source); + writeVector(out, event.delta); + } + + static bool read(std::istream& in, Scrolled& event) { + if (!readName(in, kScrollSourceNames, event.source)) { + return false; + } + readVector(in, event.delta); + return true; + } + }; + + template <> + struct RecordedFormat { + static constexpr const char* kName = "scroll-end"; + + static void write(std::ostream&, const ScrollEnded&) {} + + static bool read(std::istream&, ScrollEnded&) { return true; } + }; + + template <> + struct RecordedFormat { + static constexpr const char* kName = "key"; + + static void write(std::ostream& out, const KeyChanged& event) { + out << ' ' << (unsigned) event.key; + writeName(out, kKeyActionNames, event.action); + } + + static bool read(std::istream& in, KeyChanged& event) { + unsigned code = 0; + in >> code; + if (code >= kKeyCodeLimit) { + return false; + } + event.key = (Key) code; + return readName(in, kKeyActionNames, event.action); + } + }; + + template <> + struct RecordedFormat { + static constexpr const char* kName = "text"; + + static void write(std::ostream& out, const TextEntered& event) { out << ' ' << escapedText(event.utf8); } + + static bool read(std::istream& in, TextEntered& event) { + std::string escaped; + in >> escaped; + const std::optional text = unescapedText(escaped); + if (!text) { + return false; + } + event.utf8 = *text; + return true; + } + }; + + template <> + struct RecordedFormat { + static constexpr const char* kName = "focus"; + + static void write(std::ostream& out, const KeyboardFocusChanged& event) { out << ' ' << (int) event.focused; } + + static bool read(std::istream& in, KeyboardFocusChanged& event) { + int focused = 0; + in >> focused; + event.focused = focused != 0; + return true; + } + }; + + template <> + struct RecordedFormat { + static constexpr const char* kName = "gesture"; + + static void write(std::ostream& out, const GestureChanged& event) { + const TouchGesture& gesture = event.gesture; + writeName(out, kGesturePhaseNames, gesture.phase); + writeVector(out, gesture.center); + writeVector(out, gesture.pan); + out << ' ' << gesture.scale << ' ' << gesture.rotation << ' ' << gesture.tapFingers; + } + + static bool read(std::istream& in, GestureChanged& event) { + TouchGesture& gesture = event.gesture; + if (!readName(in, kGesturePhaseNames, gesture.phase)) { + return false; + } + readVector(in, gesture.center); + readVector(in, gesture.pan); + in >> gesture.scale >> gesture.rotation >> gesture.tapFingers; + return true; + } + }; + + template + std::optional readEventOf(const std::string& name, std::istream& in, std::index_sequence) { + std::optional result; + const auto tryAlternative = [&]() { + using Event = std::variant_alternative_t; + if (result || name != RecordedFormat::kName) { + return; + } + Event event; + if (RecordedFormat::read(in, event)) { + result = event; + } + }; + (tryAlternative.template operator()(), ...); + return result; + } + + std::optional readEvent(const std::string& name, std::istream& in) { + return readEventOf(name, in, std::make_index_sequence>{}); + } + + void writeEvent(std::ostream& out, const InputEvent& event) { + std::visit( + [&out](const Event& alternative) { + out << RecordedFormat::kName; + RecordedFormat::write(out, alternative); + }, + event + ); + } + + bool isPointerActivity(const InputEvent& event) { + return std::holds_alternative(event) || std::holds_alternative(event) || + std::holds_alternative(event) || std::holds_alternative(event); + } + + double steadyMs() { + const auto now = std::chrono::steady_clock::now().time_since_epoch(); + return std::chrono::duration(now).count(); + } + } + + std::string InputRecording::serialize() const { + std::ostringstream text; + text << std::setprecision(9); + text << kHeader << "\n"; + text << "window " << windowSize.x << ' ' << windowSize.y << "\n"; + + for (const RecordedInput& recorded : events) { + text << std::fixed << std::setprecision(3) << recorded.timeMs << ' ' << std::defaultfloat << std::setprecision(9); + writeEvent(text, recorded.event); + text << "\n"; + } + return text.str(); + } + + std::optional InputRecording::parse(const std::string& text) { + std::istringstream input(text); + std::string line; + if (!std::getline(input, line) || line != kHeader) { + return std::nullopt; + } + + InputRecording recording; + while (std::getline(input, line)) { + if (line.empty()) { + continue; + } + + std::istringstream fields(line); + std::string first; + fields >> first; + if (first == "window") { + readVector(fields, recording.windowSize); + continue; + } + + std::string name; + fields >> name; + const std::optional event = readEvent(name, fields); + if (!event || fields.fail()) { + return std::nullopt; + } + recording.events.push_back({ std::stod(first), *event }); + } + return recording; + } + + bool InputRecording::save(const std::string& path) const { + std::ofstream file(path); + if (!file) { + return false; + } + file << serialize(); + return (bool) file; + } + + std::optional InputRecording::load(const std::string& path) { + std::ifstream file(path); + if (!file) { + return std::nullopt; + } + std::stringstream text; + text << file.rdbuf(); + return parse(text.str()); + } + + void InputRecording::replayInto(InputSink& sink) const { + for (const RecordedInput& recorded : events) { + sink.post(recorded.event); + } + } + + InputRecording InputRecording::translated(Vec2F offset) const { + InputRecording moved = *this; + for (RecordedInput& recorded : moved.events) { + if (auto* pointer = std::get_if(&recorded.event)) { + pointer->position += offset; + } + if (auto* gesture = std::get_if(&recorded.event)) { + gesture->gesture.center += offset; + } + } + return moved; + } + + InputRecording InputRecording::slicedToPointerActivity() const { + InputRecording sliced; + sliced.windowSize = windowSize; + + size_t first = events.size(); + size_t last = 0; + for (size_t index = 0; index < events.size(); index++) { + if (!std::holds_alternative(events[index].event)) { + continue; + } + first = std::min(first, index); + last = index; + } + if (first >= events.size()) { + return sliced; + } + + while (first > 0 && isPointerActivity(events[first - 1].event) && + !std::holds_alternative(events[first - 1].event)) { + first--; + } + + const double origin = events[first].timeMs; + for (size_t index = first; index <= last; index++) { + if (!isPointerActivity(events[index].event)) { + continue; + } + sliced.events.push_back({ events[index].timeMs - origin, events[index].event }); + } + return sliced; + } + + InputRecorder::InputRecorder(Clock clock) : + mClock(clock ? std::move(clock) : Clock(steadyMs)) {} + + void InputRecorder::post(const InputEvent& event) { + const double now = mClock(); + if (mStartMs < 0) { + mStartMs = now; + } + mRecording.events.push_back({ now - mStartMs, event }); + } +} diff --git a/GraphicApplication/src/input_layer/private/InputState.cpp b/GraphicApplication/src/input_layer/private/InputState.cpp new file mode 100644 index 00000000..51e052c8 --- /dev/null +++ b/GraphicApplication/src/input_layer/private/InputState.cpp @@ -0,0 +1,134 @@ +#include "InputState.hpp" + +using namespace tp; + +namespace { + void press(ButtonState& state) { + if (!state.down) { + state.pressed = true; + } + state.down = true; + } + + void release(ButtonState& state) { + if (state.down) { + state.released = true; + } + state.down = false; + } +} + +halnf InputState::pointerPressure() const { + if (mPointerSample.isStylus()) { + return mPointerSample.pressure; + } + return button(PointerButton::Left).down ? 1.0f : 0.0f; +} + +void InputState::beginFrame(std::vector events) { + mPreviousPointer = mPointer; + mScrollDelta = { 0, 0 }; + mContinuousScroll = false; + mScrollEnded = false; + + for (ButtonState& state : mPointerButtons) { + state.pressed = false; + state.released = false; + } + for (ButtonState& state : mKeys) { + state.pressed = false; + state.released = false; + } + + mFrameEvents = std::move(events); + for (const InputEvent& event : mFrameEvents) { + apply(event); + } +} + +void InputState::apply(const InputEvent& event) { + std::visit( + InputEventVisitor{ + [this](const PointerSampled& sampled) { mPointerSample = sampled.sample; }, + [this](const PointerMoved& moved) { + mPointer = moved.position; + mPointerInside = true; + }, + [this](const PointerLeft&) { mPointerInside = false; }, + [this](const PointerButtonChanged& changed) { + ButtonState& state = mPointerButtons[(size_t) changed.button]; + changed.pressed ? press(state) : release(state); + }, + [this](const Scrolled& scrolled) { + mScrollDelta += scrolled.delta; + mContinuousScroll = scrolled.source == ScrollSource::Touchpad; + }, + [this](const ScrollEnded&) { mScrollEnded = true; }, + [this](const KeyChanged& changed) { + ButtonState& state = mKeys[(size_t) changed.key]; + switch (changed.action) { + case KeyAction::Press: press(state); break; + case KeyAction::Release: release(state); break; + case KeyAction::Repeat: break; + } + }, + [](const TextEntered&) {}, + [](const KeyboardFocusChanged&) {}, + [](const GestureChanged&) {}, + }, + event + ); +} + +void InputQueue::post(const InputEvent& event) { + if (std::holds_alternative(event)) { + releaseHeldPointerButtons(); + } + if (const auto* focus = std::get_if(&event); focus && !focus->focused) { + releaseHeldKeys(); + } + if (!tracksHeldInput(event)) { + return; + } + mPendingEvents.push_back(event); +} + +bool InputQueue::tracksHeldInput(const InputEvent& event) { + if (const auto* changed = std::get_if(&event)) { + if (changed->pressed) { + mHeldPointerButtons.insert(changed->button); + return true; + } + return mHeldPointerButtons.erase(changed->button) > 0; + } + if (const auto* changed = std::get_if(&event)) { + if (changed->action == KeyAction::Press) { + mHeldKeys.insert(changed->key); + } else if (changed->action == KeyAction::Release) { + mHeldKeys.erase(changed->key); + } + } + return true; +} + +void InputQueue::releaseHeldPointerButtons() { + const std::set held = std::move(mHeldPointerButtons); + mHeldPointerButtons.clear(); + for (PointerButton button : held) { + mPendingEvents.push_back(PointerButtonChanged{ button, false }); + } +} + +void InputQueue::releaseHeldKeys() { + const std::set held = std::move(mHeldKeys); + mHeldKeys.clear(); + for (Key key : held) { + mPendingEvents.push_back(KeyChanged{ key, KeyAction::Release }); + } +} + +void InputQueue::applyPendingEvents() { + std::vector events; + events.swap(mPendingEvents); + mState.beginFrame(std::move(events)); +} diff --git a/GraphicApplication/src/input_layer/private/TouchGesture.cpp b/GraphicApplication/src/input_layer/private/TouchGesture.cpp new file mode 100644 index 00000000..fc69ec84 --- /dev/null +++ b/GraphicApplication/src/input_layer/private/TouchGesture.cpp @@ -0,0 +1,122 @@ +#include "TouchGesture.hpp" + +#include +#include +#include + +namespace tp { + + namespace { + constexpr halnf kMinimumSpan = 1.0f; + + } + + void TouchGestureTracker::track(const TouchPoint& first, const TouchPoint& second) { + const Vec2F offset = second.position - first.position; + mFirst = first.id; + mSecond = second.id; + mCenter = (first.position + second.position) * 0.5f; + mDistance = std::sqrt(offset.x * offset.x + offset.y * offset.y); + mAngle = std::atan2(offset.y, offset.x); + } + + void TouchGestureTracker::followTap(const std::vector& touches, uint32_t timeMs) { + if (mTapStarts.empty() && !touches.empty()) { + mTapStartMs = timeMs; + mTapStillPossible = true; + } + for (const TouchPoint& touch : touches) { + auto start = std::find_if(mTapStarts.begin(), mTapStarts.end(), [&](const TouchPoint& known) { + return known.id == touch.id; + }); + if (start == mTapStarts.end()) { + mTapStarts.push_back(touch); + continue; + } + const Vec2F travel = touch.position - start->position; + if (travel.x * travel.x + travel.y * travel.y > kTapMaximumTravel * kTapMaximumTravel) { + mTapStillPossible = false; + } + } + } + + int32_t TouchGestureTracker::finishedTapFingers(uint32_t timeMs) const { + const bool quick = timeMs - mTapStartMs <= kTapMaximumMs; + return mTapStillPossible && quick && mTapStarts.size() >= 2 ? (int32_t) mTapStarts.size() : 0; + } + + void TouchGestureTracker::update( + const std::vector& touches, std::vector& gestures, uint32_t timeMs + ) { + followTap(touches, timeMs); + if (touches.empty()) { + const int32_t tapFingers = finishedTapFingers(timeMs); + mTapStarts.clear(); + cancel(gestures); + if (!gestures.empty() && gestures.back().phase == GesturePhase::End) { + gestures.back().tapFingers = tapFingers; + } + return; + } + if (touches.size() < 2) { + if (finishedTapFingers(timeMs) == 0) { + cancel(gestures); + } + return; + } + + const TouchPoint& first = touches[0]; + const TouchPoint& second = touches[1]; + + if (!mActive) { + track(first, second); + mActive = true; + gestures.push_back({ GesturePhase::Begin, mCenter }); + return; + } + + if (first.id != mFirst || second.id != mSecond) { + track(first, second); + return; + } + + const Vec2F previousCenter = mCenter; + const halnf previousDistance = mDistance; + const halnf previousAngle = mAngle; + track(first, second); + + TouchGesture gesture; + gesture.phase = GesturePhase::Update; + gesture.center = mCenter; + gesture.pan = mCenter - previousCenter; + gesture.scale = previousDistance >= kMinimumSpan && mDistance >= kMinimumSpan ? mDistance / previousDistance : 1; + gesture.rotation = wrapAngle(mAngle - previousAngle); + gestures.push_back(gesture); + } + + void TouchGestureTracker::cancel(std::vector& gestures) { + if (!mActive) { + return; + } + mActive = false; + gestures.push_back({ GesturePhase::End, mCenter }); + } + + halnf wrapAngle(halnf radians) { + constexpr halnf pi = std::numbers::pi_v; + while (radians > pi) { + radians -= 2 * pi; + } + while (radians < -pi) { + radians += 2 * pi; + } + return radians; + } + + void accumulate(TouchGesture& into, const TouchGesture& next) { + into.center = next.center; + into.pan += next.pan; + into.scale *= next.scale; + into.rotation = wrapAngle(into.rotation + next.rotation); + } +} diff --git a/GraphicApplication/src/input_layer/public/InputCoalescer.hpp b/GraphicApplication/src/input_layer/public/InputCoalescer.hpp new file mode 100644 index 00000000..44640e4d --- /dev/null +++ b/GraphicApplication/src/input_layer/public/InputCoalescer.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include "InputSink.hpp" + +#include +#include + +namespace tp { + + enum class DeliveryPolicy { Every, Latest, Accumulate }; + + struct InputDeliveryPolicies { + DeliveryPolicy pointerMotion = DeliveryPolicy::Latest; + DeliveryPolicy scroll = DeliveryPolicy::Accumulate; + }; + + struct CoalescingStatistics { + uint64_t received = 0; + uint64_t delivered = 0; + }; + + // Backends post raw events here; they are batched and delivered once per frame. + class InputCoalescer final : public InputSink { + public: + void post(const InputEvent& event) override; + + void setTap(InputSink* tap) { mTap = tap; } + void setPolicies(const InputDeliveryPolicies& policies) { mPolicies = policies; } + + [[nodiscard]] bool isEmpty() const { return mEvents.empty(); } + void deliver(InputSink& sink); + + [[nodiscard]] const CoalescingStatistics& statistics() const { return mStatistics; } + + private: + struct Pending { + InputEvent event; + PointerSample sample; + }; + + [[nodiscard]] bool merge(const InputEvent& event); + [[nodiscard]] static bool carriesSample(const InputEvent& event); + [[nodiscard]] static bool isEmptyScroll(const InputEvent& event); + + InputSink* mTap = nullptr; + InputDeliveryPolicies mPolicies; + std::vector mEvents; + PointerSample mSample; + PointerSample mDeliveredSample; + CoalescingStatistics mStatistics; + }; +} diff --git a/GraphicApplication/src/input_layer/public/InputCodes.hpp b/GraphicApplication/src/input_layer/public/InputCodes.hpp new file mode 100644 index 00000000..372baf57 --- /dev/null +++ b/GraphicApplication/src/input_layer/public/InputCodes.hpp @@ -0,0 +1,137 @@ +#pragma once + +#include +#include + +namespace tp { + + enum class PointerButton : uint8_t { Left, Right, Middle, Back, Forward }; + + inline constexpr size_t kPointerButtonCount = 5; + + enum class Key : uint16_t { + Space = 32, + Apostrophe = 39, + Comma = 44, + Minus = 45, + Period = 46, + Slash = 47, + Digit0 = 48, + Digit1 = 49, + Digit2 = 50, + Digit3 = 51, + Digit4 = 52, + Digit5 = 53, + Digit6 = 54, + Digit7 = 55, + Digit8 = 56, + Digit9 = 57, + Semicolon = 59, + Equal = 61, + A = 65, + B = 66, + C = 67, + D = 68, + E = 69, + F = 70, + G = 71, + H = 72, + I = 73, + J = 74, + K = 75, + L = 76, + M = 77, + N = 78, + O = 79, + P = 80, + Q = 81, + R = 82, + S = 83, + T = 84, + U = 85, + V = 86, + W = 87, + X = 88, + Y = 89, + Z = 90, + LeftBracket = 91, + Backslash = 92, + RightBracket = 93, + GraveAccent = 96, + World1 = 161, + World2 = 162, + + Escape = 256, + Enter = 257, + Tab = 258, + Backspace = 259, + Insert = 260, + Delete = 261, + Right = 262, + Left = 263, + Down = 264, + Up = 265, + PageUp = 266, + PageDown = 267, + Home = 268, + End = 269, + CapsLock = 280, + ScrollLock = 281, + NumLock = 282, + PrintScreen = 283, + Pause = 284, + F1 = 290, + F2 = 291, + F3 = 292, + F4 = 293, + F5 = 294, + F6 = 295, + F7 = 296, + F8 = 297, + F9 = 298, + F10 = 299, + F11 = 300, + F12 = 301, + F13 = 302, + F14 = 303, + F15 = 304, + F16 = 305, + F17 = 306, + F18 = 307, + F19 = 308, + F20 = 309, + F21 = 310, + F22 = 311, + F23 = 312, + F24 = 313, + F25 = 314, + Keypad0 = 320, + Keypad1 = 321, + Keypad2 = 322, + Keypad3 = 323, + Keypad4 = 324, + Keypad5 = 325, + Keypad6 = 326, + Keypad7 = 327, + Keypad8 = 328, + Keypad9 = 329, + KeypadDecimal = 330, + KeypadDivide = 331, + KeypadMultiply = 332, + KeypadSubtract = 333, + KeypadAdd = 334, + KeypadEnter = 335, + KeypadEqual = 336, + LeftShift = 340, + LeftControl = 341, + LeftAlt = 342, + LeftSuper = 343, + RightShift = 344, + RightControl = 345, + RightAlt = 346, + RightSuper = 347, + Menu = 348, + }; + + inline constexpr size_t kKeyCodeLimit = 349; +} diff --git a/GraphicApplication/src/input_layer/public/InputEvent.hpp b/GraphicApplication/src/input_layer/public/InputEvent.hpp new file mode 100644 index 00000000..6ba8a4d2 --- /dev/null +++ b/GraphicApplication/src/input_layer/public/InputEvent.hpp @@ -0,0 +1,93 @@ +#pragma once + +#include "InputCodes.hpp" +#include "PointerSample.hpp" +#include "TouchGesture.hpp" +#include "Vec.hpp" + +#include +#include + +namespace tp { + + enum class ScrollSource { Wheel, Touchpad }; + + enum class KeyAction { Press, Release, Repeat }; + + struct PointerSampled { + PointerSample sample; + + [[nodiscard]] bool operator==(const PointerSampled& other) const = default; + }; + + struct PointerMoved { + Vec2F position = { 0, 0 }; + + [[nodiscard]] bool operator==(const PointerMoved& other) const = default; + }; + + struct PointerLeft { + [[nodiscard]] bool operator==(const PointerLeft& other) const = default; + }; + + struct PointerButtonChanged { + PointerButton button = PointerButton::Left; + bool pressed = false; + + [[nodiscard]] bool operator==(const PointerButtonChanged& other) const = default; + }; + + struct Scrolled { + Vec2F delta = { 0, 0 }; + ScrollSource source = ScrollSource::Wheel; + + [[nodiscard]] bool operator==(const Scrolled& other) const = default; + }; + + struct ScrollEnded { + [[nodiscard]] bool operator==(const ScrollEnded& other) const = default; + }; + + struct KeyChanged { + Key key = Key::Space; + KeyAction action = KeyAction::Press; + + [[nodiscard]] bool operator==(const KeyChanged& other) const = default; + }; + + struct TextEntered { + std::string utf8; + + [[nodiscard]] bool operator==(const TextEntered& other) const = default; + }; + + struct KeyboardFocusChanged { + bool focused = false; + + [[nodiscard]] bool operator==(const KeyboardFocusChanged& other) const = default; + }; + + struct GestureChanged { + TouchGesture gesture; + + [[nodiscard]] bool operator==(const GestureChanged& other) const = default; + }; + + // The one declaration of every input event; sinks, the coalescer, input state and recordings derive from it. + using InputEvent = std::variant< + PointerSampled, + PointerMoved, + PointerLeft, + PointerButtonChanged, + Scrolled, + ScrollEnded, + KeyChanged, + TextEntered, + KeyboardFocusChanged, + GestureChanged>; + + template + struct InputEventVisitor : Handlers... { + using Handlers::operator()...; + }; +} diff --git a/GraphicApplication/src/input_layer/public/InputRecording.hpp b/GraphicApplication/src/input_layer/public/InputRecording.hpp new file mode 100644 index 00000000..15aacd5b --- /dev/null +++ b/GraphicApplication/src/input_layer/public/InputRecording.hpp @@ -0,0 +1,54 @@ +#pragma once + +#include "InputSink.hpp" + +#include +#include +#include +#include + +namespace tp { + + struct RecordedInput { + double timeMs = 0; + InputEvent event; + + [[nodiscard]] bool operator==(const RecordedInput& other) const = default; + }; + + struct InputRecording { + static constexpr const char* kHeader = "tp-input-recording 2"; + + Vec2F windowSize = { 0, 0 }; + std::vector events; + + [[nodiscard]] double durationMs() const { return events.empty() ? 0.0 : events.back().timeMs; } + [[nodiscard]] std::string serialize() const; + [[nodiscard]] static std::optional parse(const std::string& text); + + bool save(const std::string& path) const; + [[nodiscard]] static std::optional load(const std::string& path); + + void replayInto(InputSink& sink) const; + [[nodiscard]] InputRecording translated(Vec2F offset) const; + [[nodiscard]] InputRecording slicedToPointerActivity() const; + }; + + class InputRecorder final : public InputSink { + public: + using Clock = std::function; + + explicit InputRecorder(Clock clock = {}); + + void setWindowSize(Vec2F size) { mRecording.windowSize = size; } + void startClockNow() { mStartMs = mClock(); } + [[nodiscard]] const InputRecording& recording() const { return mRecording; } + + void post(const InputEvent& event) override; + + private: + Clock mClock; + double mStartMs = -1; + InputRecording mRecording; + }; +} diff --git a/GraphicApplication/src/input_layer/public/InputSink.hpp b/GraphicApplication/src/input_layer/public/InputSink.hpp new file mode 100644 index 00000000..23f04b3d --- /dev/null +++ b/GraphicApplication/src/input_layer/public/InputSink.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include "InputEvent.hpp" + +namespace tp { + + class InputSink { + public: + virtual ~InputSink() = default; + + virtual void post(const InputEvent& event) = 0; + }; + + class DiscardingInputSink final : public InputSink { + public: + void post(const InputEvent&) override {} + }; +} diff --git a/GraphicApplication/src/input_layer/public/InputState.hpp b/GraphicApplication/src/input_layer/public/InputState.hpp new file mode 100644 index 00000000..e58f6310 --- /dev/null +++ b/GraphicApplication/src/input_layer/public/InputState.hpp @@ -0,0 +1,118 @@ +#pragma once + +#include "InputSink.hpp" + +#include +#include +#include +#include + +namespace tp { + + struct ButtonState { + bool pressed = false; + bool down = false; + bool released = false; + }; + + // The input of one frame: the ordered event log plus the per-frame state derived from it. Read-only for consumers. + class InputState { + public: + [[nodiscard]] const std::vector& events() const { return mFrameEvents; } + + [[nodiscard]] Vec2F pointer() const { return mPointer; } + [[nodiscard]] Vec2F previousPointer() const { return mPreviousPointer; } + [[nodiscard]] Vec2F pointerDelta() const { return mPointer - mPreviousPointer; } + [[nodiscard]] bool isPointerInside() const { return mPointerInside; } + [[nodiscard]] const PointerSample& pointerSample() const { return mPointerSample; } + [[nodiscard]] halnf pointerPressure() const; + + [[nodiscard]] const ButtonState& button(PointerButton button) const { return mPointerButtons[(size_t) button]; } + [[nodiscard]] const ButtonState& key(Key key) const { return mKeys[(size_t) key]; } + + [[nodiscard]] Vec2F scroll() const { return mScrollDelta; } + [[nodiscard]] bool isContinuousScroll() const { return mContinuousScroll; } + [[nodiscard]] bool isScrollEnded() const { return mScrollEnded; } + + private: + friend class InputQueue; + + void beginFrame(std::vector events); + void apply(const InputEvent& event); + + std::vector mFrameEvents; + + Vec2F mPointer = { 0, 0 }; + Vec2F mPreviousPointer = { 0, 0 }; + bool mPointerInside = false; + PointerSample mPointerSample; + Vec2F mScrollDelta = { 0, 0 }; + bool mContinuousScroll = false; + bool mScrollEnded = false; + + std::array mPointerButtons{}; + std::array mKeys{}; + }; + + // Collects delivered events between frames and releases held buttons and keys when focus or the pointer is lost. + class InputQueue final : public InputSink { + public: + void post(const InputEvent& event) override; + + [[nodiscard]] size_t pendingEventCount() const { return mPendingEvents.size(); } + void applyPendingEvents(); + + [[nodiscard]] const InputState& state() const { return mState; } + + private: + void releaseHeldPointerButtons(); + void releaseHeldKeys(); + [[nodiscard]] bool tracksHeldInput(const InputEvent& event); + + std::vector mPendingEvents; + std::set mHeldPointerButtons; + std::set mHeldKeys; + InputState mState; + }; + + // A consumer's view of one frame: pointer coordinates relative to an origin, optionally without button/key state. + class InputView { + public: + enum class Buttons { Visible, Hidden }; + + explicit InputView(const InputState& state, Vec2F origin = { 0, 0 }, Buttons buttons = Buttons::Visible) : + mState(state), + mOrigin(origin), + mButtons(buttons) {} + + [[nodiscard]] InputView relativeTo(Vec2F origin) const { return InputView(mState, origin, mButtons); } + [[nodiscard]] InputView withButtons(Buttons buttons) const { return InputView(mState, mOrigin, buttons); } + + [[nodiscard]] const InputState& state() const { return mState; } + + [[nodiscard]] Vec2F getPointer() const { return mState.pointer() - mOrigin; } + [[nodiscard]] Vec2F getPointerPrev() const { return mState.previousPointer() - mOrigin; } + [[nodiscard]] Vec2F getPointerDelta() const { return mState.pointerDelta(); } + [[nodiscard]] halnf getPointerPressure() const { return areButtonsVisible() ? mState.pointerPressure() : 0; } + + [[nodiscard]] bool isPressed(PointerButton button) const { return visible(mState.button(button)).pressed; } + [[nodiscard]] bool isReleased(PointerButton button) const { return visible(mState.button(button)).released; } + [[nodiscard]] bool isDown(PointerButton button) const { return visible(mState.button(button)).down; } + [[nodiscard]] bool isPressed(Key key) const { return visible(mState.key(key)).pressed; } + [[nodiscard]] bool isReleased(Key key) const { return visible(mState.key(key)).released; } + [[nodiscard]] bool isDown(Key key) const { return visible(mState.key(key)).down; } + + [[nodiscard]] halnf getScrollX() const { return mState.scroll().x; } + [[nodiscard]] halnf getScrollY() const { return mState.scroll().y; } + + private: + [[nodiscard]] bool areButtonsVisible() const { return mButtons == Buttons::Visible; } + [[nodiscard]] ButtonState visible(const ButtonState& state) const { + return areButtonsVisible() ? state : ButtonState{}; + } + + const InputState& mState; + Vec2F mOrigin; + Buttons mButtons; + }; +} diff --git a/GraphicApplication/src/input_layer/public/PointerSample.hpp b/GraphicApplication/src/input_layer/public/PointerSample.hpp new file mode 100644 index 00000000..9621f785 --- /dev/null +++ b/GraphicApplication/src/input_layer/public/PointerSample.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include "Vec.hpp" + +#include + +namespace tp { + + enum class PointerDevice { Mouse, Finger, Stylus }; + + enum class StylusTip { Pen, Eraser, Brush, Pencil, Airbrush }; + + struct PointerSample { + PointerDevice device = PointerDevice::Mouse; + StylusTip tip = StylusTip::Pen; + halnf pressure = 1; + Vec2F tilt = { 0, 0 }; + halnf rotation = 0; + halnf distance = 0; + uint32_t timeMs = 0; + + [[nodiscard]] bool isStylus() const { return device == PointerDevice::Stylus; } + [[nodiscard]] bool operator==(const PointerSample& other) const = default; + }; +} diff --git a/GraphicApplication/src/input_layer/public/TouchGesture.hpp b/GraphicApplication/src/input_layer/public/TouchGesture.hpp new file mode 100644 index 00000000..4e70325f --- /dev/null +++ b/GraphicApplication/src/input_layer/public/TouchGesture.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include "Vec.hpp" + +#include +#include + +namespace tp { + + enum class GesturePhase { Begin, Update, End }; + + struct TouchGesture { + GesturePhase phase = GesturePhase::Update; + Vec2F center = { 0, 0 }; + Vec2F pan = { 0, 0 }; + halnf scale = 1; + halnf rotation = 0; + int32_t tapFingers = 0; + + [[nodiscard]] bool operator==(const TouchGesture& other) const = default; + }; + + struct TouchPoint { + int32_t id = 0; + Vec2F position = { 0, 0 }; + }; + + class TouchGestureTracker { + public: + static constexpr uint32_t kTapMaximumMs = 300; + static constexpr halnf kTapMaximumTravel = 20; + + void update(const std::vector& touches, std::vector& gestures, uint32_t timeMs = 0); + void cancel(std::vector& gestures); + + [[nodiscard]] bool isActive() const { return mActive; } + + private: + void track(const TouchPoint& first, const TouchPoint& second); + void followTap(const std::vector& touches, uint32_t timeMs); + [[nodiscard]] int32_t finishedTapFingers(uint32_t timeMs) const; + + std::vector mTapStarts; + uint32_t mTapStartMs = 0; + bool mTapStillPossible = false; + + bool mActive = false; + int32_t mFirst = 0; + int32_t mSecond = 0; + Vec2F mCenter = { 0, 0 }; + halnf mDistance = 0; + halnf mAngle = 0; + }; + + void accumulate(TouchGesture& into, const TouchGesture& next); + + [[nodiscard]] halnf wrapAngle(halnf radians); +} diff --git a/GraphicApplication/tests/InputCoalescerTests.cpp b/GraphicApplication/tests/InputCoalescerTests.cpp new file mode 100644 index 00000000..1090fdd6 --- /dev/null +++ b/GraphicApplication/tests/InputCoalescerTests.cpp @@ -0,0 +1,265 @@ +#include "InputCoalescer.hpp" + +#include "UnitTest++/UnitTest++.h" + +#include +#include +#include + +using namespace tp; + +namespace { + std::string point(Vec2F value) { return std::to_string((int) value.x) + "," + std::to_string((int) value.y); } + + std::string describe(const InputEvent& event) { + return std::visit( + InputEventVisitor{ + [](const PointerSampled& sampled) { + return "sample " + std::to_string((int) std::lround(sampled.sample.pressure * 100)); + }, + [](const PointerMoved& moved) { return "move " + point(moved.position); }, + [](const PointerLeft&) { return std::string("leave"); }, + [](const PointerButtonChanged& changed) { + return std::string(changed.pressed ? "press " : "release ") + std::to_string((int) changed.button); + }, + [](const Scrolled& scrolled) { + return std::string(scrolled.source == ScrollSource::Touchpad ? "touchpad " : "scroll ") + + point(scrolled.delta); + }, + [](const ScrollEnded&) { return std::string("scroll end"); }, + [](const KeyChanged& changed) { + const char* actions[] = { "key down ", "key up ", "repeat " }; + return actions[(int) changed.action] + std::to_string((int) changed.key); + }, + [](const TextEntered& text) { return "text " + text.utf8; }, + [](const KeyboardFocusChanged& focus) { return std::string(focus.focused ? "focus" : "blur"); }, + [](const GestureChanged&) { return std::string("gesture"); }, + }, + event + ); + } + + class LoggingSink : public InputSink { + public: + void post(const InputEvent& event) override { log.push_back(describe(event)); } + + std::vector log; + }; + + std::vector drain(InputCoalescer& coalescer) { + LoggingSink sink; + coalescer.deliver(sink); + return sink.log; + } + + void move(InputCoalescer& coalescer, Vec2F position) { coalescer.post(PointerMoved{ position }); } + + void scroll(InputCoalescer& coalescer, Vec2F delta, ScrollSource source = ScrollSource::Wheel) { + coalescer.post(Scrolled{ delta, source }); + } + + void button(InputCoalescer& coalescer, bool pressed) { + coalescer.post(PointerButtonChanged{ PointerButton::Left, pressed }); + } +} + +SUITE(InputCoalescerPolicies) { + TEST(OppositeScrollsThatCancelDeliverNothing) { + InputCoalescer coalescer; + scroll(coalescer, { 0, 1 }); + scroll(coalescer, { 0, -1 }); + CHECK(drain(coalescer).empty()); + } + + TEST(BurstOfMotionCollapsesToLatestPosition) { + InputCoalescer coalescer; + move(coalescer, { 1, 1 }); + move(coalescer, { 2, 2 }); + move(coalescer, { 3, 3 }); + + const std::vector log = drain(coalescer); + REQUIRE CHECK_EQUAL(1u, log.size()); + CHECK_EQUAL("move 3,3", log[0]); + CHECK_EQUAL(3u, coalescer.statistics().received); + CHECK_EQUAL(1u, coalescer.statistics().delivered); + } + + TEST(EveryPolicyKeepsEachMotionSampleForDrawingApps) { + InputCoalescer coalescer; + coalescer.setPolicies({ DeliveryPolicy::Every, DeliveryPolicy::Accumulate }); + move(coalescer, { 1, 1 }); + move(coalescer, { 2, 2 }); + move(coalescer, { 3, 3 }); + CHECK_EQUAL(3u, drain(coalescer).size()); + } + + TEST(MotionNeverMergesAcrossButtons) { + InputCoalescer coalescer; + move(coalescer, { 1, 1 }); + move(coalescer, { 5, 5 }); + button(coalescer, true); + move(coalescer, { 6, 6 }); + move(coalescer, { 9, 9 }); + button(coalescer, false); + + const std::vector log = drain(coalescer); + REQUIRE CHECK_EQUAL(4u, log.size()); + CHECK_EQUAL("move 5,5", log[0]); + CHECK_EQUAL("press 0", log[1]); + CHECK_EQUAL("move 9,9", log[2]); + CHECK_EQUAL("release 0", log[3]); + } + + TEST(ScrollDeltasAddUpAndDoNotCrossKeys) { + InputCoalescer coalescer; + scroll(coalescer, { 0, 1 }); + scroll(coalescer, { 0, 2 }); + coalescer.post(KeyChanged{ Key::LeftControl, KeyAction::Press }); + scroll(coalescer, { 0, -1 }); + + const std::vector log = drain(coalescer); + REQUIRE CHECK_EQUAL(3u, log.size()); + CHECK_EQUAL("scroll 0,3", log[0]); + CHECK_EQUAL("key down " + std::to_string((int) Key::LeftControl), log[1]); + CHECK_EQUAL("scroll 0,-1", log[2]); + } + + TEST(TouchpadScrollsKeepTheirSourceAndEnd) { + InputCoalescer coalescer; + scroll(coalescer, { 0, 1 }, ScrollSource::Touchpad); + scroll(coalescer, { 0, 2 }, ScrollSource::Touchpad); + coalescer.post(ScrollEnded{}); + + const std::vector log = drain(coalescer); + REQUIRE CHECK_EQUAL(2u, log.size()); + CHECK_EQUAL("touchpad 0,3", log[0]); + CHECK_EQUAL("scroll end", log[1]); + } + + TEST(WheelAndTouchpadScrollsAreNotMergedTogether) { + InputCoalescer coalescer; + scroll(coalescer, { 0, 1 }, ScrollSource::Wheel); + scroll(coalescer, { 0, 2 }, ScrollSource::Touchpad); + + const std::vector log = drain(coalescer); + REQUIRE CHECK_EQUAL(2u, log.size()); + CHECK_EQUAL("scroll 0,1", log[0]); + CHECK_EQUAL("touchpad 0,2", log[1]); + } + + TEST(ScrollsOnEitherSideOfAnEndAreNotMerged) { + InputCoalescer coalescer; + scroll(coalescer, { 0, 1 }, ScrollSource::Touchpad); + coalescer.post(ScrollEnded{}); + scroll(coalescer, { 0, 1 }, ScrollSource::Touchpad); + CHECK_EQUAL(3u, drain(coalescer).size()); + } + + TEST(DiscreteEventsAreNeverMerged) { + InputCoalescer coalescer; + coalescer.post(KeyChanged{ Key::A, KeyAction::Press }); + coalescer.post(KeyChanged{ Key::A, KeyAction::Release }); + coalescer.post(KeyChanged{ Key::A, KeyAction::Press }); + coalescer.post(KeyChanged{ Key::A, KeyAction::Repeat }); + coalescer.post(KeyChanged{ Key::A, KeyAction::Repeat }); + coalescer.post(TextEntered{ "a" }); + coalescer.post(TextEntered{ "b" }); + coalescer.post(KeyboardFocusChanged{ false }); + coalescer.post(PointerLeft{}); + CHECK_EQUAL(9u, drain(coalescer).size()); + } + + TEST(TapSeesEveryRawEventBeforeCoalescing) { + InputCoalescer coalescer; + LoggingSink tap; + coalescer.setTap(&tap); + move(coalescer, { 1, 1 }); + move(coalescer, { 2, 2 }); + coalescer.post(TextEntered{ "q" }); + + CHECK_EQUAL(3u, tap.log.size()); + CHECK_EQUAL(2u, drain(coalescer).size()); + } + + TEST(SuccessiveGestureUpdatesAccumulate) { + InputCoalescer coalescer; + TouchGesture update; + update.scale = 2; + update.pan = { 1, 0 }; + coalescer.post(GestureChanged{ update }); + coalescer.post(GestureChanged{ update }); + + std::vector delivered; + struct Collect : InputSink { + std::vector& events; + explicit Collect(std::vector& events) : + events(events) {} + void post(const InputEvent& event) override { events.push_back(event); } + } collect(delivered); + coalescer.deliver(collect); + + REQUIRE CHECK_EQUAL(1u, delivered.size()); + const TouchGesture& merged = std::get(delivered[0]).gesture; + CHECK_CLOSE(4.0f, merged.scale, 0.0001f); + CHECK_CLOSE(2.0f, merged.pan.x, 0.0001f); + } + + TEST(DeliveryEmptiesTheQueueAndKeepsOrder) { + InputCoalescer coalescer; + coalescer.post(TextEntered{ "x" }); + move(coalescer, { 4, 4 }); + const std::vector log = drain(coalescer); + REQUIRE CHECK_EQUAL(2u, log.size()); + CHECK_EQUAL("text x", log[0]); + CHECK_EQUAL("move 4,4", log[1]); + CHECK(coalescer.isEmpty()); + CHECK_EQUAL(0u, drain(coalescer).size()); + } +} + +SUITE(InputCoalescerPenSamples) { + PointerSample pen(halnf pressure) { return PointerSample{ PointerDevice::Stylus, StylusTip::Pen, pressure }; } + + TEST(EachDeliveredMotionCarriesThePressureItWasRecordedWith) { + InputCoalescer coalescer; + coalescer.setPolicies({ DeliveryPolicy::Every, DeliveryPolicy::Accumulate }); + coalescer.post(PointerSampled{ pen(0.25f) }); + move(coalescer, { 1, 1 }); + coalescer.post(PointerSampled{ pen(0.75f) }); + move(coalescer, { 2, 2 }); + + const std::vector log = drain(coalescer); + REQUIRE CHECK_EQUAL(4u, log.size()); + CHECK_EQUAL("sample 25", log[0]); + CHECK_EQUAL("move 1,1", log[1]); + CHECK_EQUAL("sample 75", log[2]); + CHECK_EQUAL("move 2,2", log[3]); + } + + TEST(LatestMotionKeepsTheNewestSample) { + InputCoalescer coalescer; + coalescer.post(PointerSampled{ pen(0.2f) }); + move(coalescer, { 1, 1 }); + coalescer.post(PointerSampled{ pen(0.9f) }); + move(coalescer, { 4, 4 }); + + const std::vector log = drain(coalescer); + REQUIRE CHECK_EQUAL(2u, log.size()); + CHECK_EQUAL("sample 90", log[0]); + CHECK_EQUAL("move 4,4", log[1]); + } + + TEST(UnchangedSampleIsNotRepeated) { + InputCoalescer coalescer; + coalescer.setPolicies({ DeliveryPolicy::Every, DeliveryPolicy::Accumulate }); + coalescer.post(PointerSampled{ pen(0.5f) }); + move(coalescer, { 1, 1 }); + button(coalescer, true); + move(coalescer, { 2, 2 }); + + const std::vector log = drain(coalescer); + REQUIRE CHECK_EQUAL(4u, log.size()); + CHECK_EQUAL("sample 50", log[0]); + CHECK_EQUAL("move 2,2", log[3]); + } +} diff --git a/GraphicApplication/tests/InputRecordingTests.cpp b/GraphicApplication/tests/InputRecordingTests.cpp new file mode 100644 index 00000000..491114fb --- /dev/null +++ b/GraphicApplication/tests/InputRecordingTests.cpp @@ -0,0 +1,135 @@ +#include "InputRecording.hpp" + +#include "UnitTest++/UnitTest++.h" + +#include + +using namespace tp; + +namespace { + struct ManualClock { + double now = 0; + InputRecorder::Clock clock() { + return [this]() { return now; }; + } + }; + + struct Collector : InputSink { + std::vector events; + void post(const InputEvent& event) override { events.push_back(event); } + }; + + InputRecording penStroke() { + ManualClock time; + InputRecorder recorder(time.clock()); + recorder.setWindowSize({ 1600, 1000 }); + PointerSample pen; + pen.device = PointerDevice::Stylus; + pen.pressure = 0.25f; + pen.tilt = { 0.1f, -0.2f }; + pen.timeMs = 77; + + time.now = 1000; + recorder.post(KeyChanged{ Key::LeftShift, KeyAction::Press }); + time.now = 1010; + recorder.post(PointerSampled{ pen }); + recorder.post(PointerMoved{ { 10, 20 } }); + recorder.post(PointerButtonChanged{ PointerButton::Left, true }); + time.now = 1012.5; + recorder.post(PointerMoved{ { 30, 40 } }); + time.now = 1030; + recorder.post(PointerButtonChanged{ PointerButton::Left, false }); + time.now = 1200; + recorder.post(KeyChanged{ Key::LeftShift, KeyAction::Release }); + return recorder.recording(); + } + + InputRecording roundTripped(const InputRecording& recording) { + const std::optional parsed = InputRecording::parse(recording.serialize()); + return parsed ? *parsed : InputRecording{}; + } +} + +SUITE(InputRecording) { + TEST(RecorderTimesEventsFromTheFirstOne) { + const InputRecording recording = penStroke(); + REQUIRE CHECK_EQUAL(7u, recording.events.size()); + CHECK_EQUAL(0.0, recording.events[0].timeMs); + CHECK_EQUAL(12.5, recording.events[4].timeMs); + CHECK_EQUAL(200.0, recording.durationMs()); + } + + TEST(ExplicitClockStartTimesTheFirstEventFromThere) { + ManualClock time; + InputRecorder recorder(time.clock()); + time.now = 500; + recorder.startClockNow(); + time.now = 740; + recorder.post(PointerMoved{ { 1, 1 } }); + CHECK_EQUAL(240.0, recorder.recording().events.front().timeMs); + } + + TEST(SerializedRecordingParsesBackIdentically) { + const InputRecording recording = penStroke(); + const InputRecording parsed = roundTripped(recording); + CHECK_EQUAL(1600.0f, parsed.windowSize.x); + CHECK(recording.events == parsed.events); + } + + TEST(EveryEventKindRoundTrips) { + ManualClock time; + InputRecorder recorder(time.clock()); + TouchGesture pinch; + pinch.phase = GesturePhase::Update; + pinch.center = { 100, 50 }; + pinch.pan = { 2, -1 }; + pinch.scale = 1.25f; + pinch.rotation = 0.1f; + pinch.tapFingers = 2; + + recorder.post(Scrolled{ { 0, 2 }, ScrollSource::Wheel }); + recorder.post(Scrolled{ { 0, -3 }, ScrollSource::Touchpad }); + recorder.post(ScrollEnded{}); + recorder.post(KeyChanged{ Key::A, KeyAction::Repeat }); + recorder.post(TextEntered{ "a b%\xd0\x96" }); + recorder.post(GestureChanged{ pinch }); + recorder.post(KeyboardFocusChanged{ true }); + recorder.post(PointerLeft{}); + recorder.post(PointerButtonChanged{ PointerButton::Forward, true }); + + CHECK(roundTripped(recorder.recording()).events == recorder.recording().events); + } + + TEST(ParseRejectsForeignText) { + CHECK(!InputRecording::parse("hello\n").has_value()); + CHECK(!InputRecording::parse(std::string(InputRecording::kHeader) + "\n1.0 teleport 3\n").has_value()); + CHECK(!InputRecording::parse(std::string(InputRecording::kHeader) + "\n1.0 button sideways 1\n").has_value()); + } + + TEST(ReplayPostsEventsInOrder) { + const InputRecording recording = penStroke(); + Collector collector; + recording.replayInto(collector); + + REQUIRE CHECK_EQUAL(recording.events.size(), collector.events.size()); + for (size_t index = 0; index < recording.events.size(); index++) { + CHECK(recording.events[index].event == collector.events[index]); + } + } + + TEST(SliceKeepsThePointerActivityAndRebasesTime) { + const InputRecording sliced = penStroke().slicedToPointerActivity(); + REQUIRE CHECK_EQUAL(5u, sliced.events.size()); + CHECK(std::holds_alternative(sliced.events.front().event)); + CHECK_EQUAL(0.0, sliced.events.front().timeMs); + CHECK(std::holds_alternative(sliced.events.back().event)); + CHECK_EQUAL(20.0, sliced.events.back().timeMs); + } + + TEST(TranslateMovesOnlyPositions) { + const InputRecording moved = penStroke().translated({ 5, -5 }); + CHECK_EQUAL(15.0f, std::get(moved.events[2].event).position.x); + CHECK_EQUAL(15.0f, std::get(moved.events[2].event).position.y); + CHECK_EQUAL(0.25f, std::get(moved.events[1].event).sample.pressure); + } +} diff --git a/GraphicApplication/tests/InputStateTests.cpp b/GraphicApplication/tests/InputStateTests.cpp new file mode 100644 index 00000000..8868a05c --- /dev/null +++ b/GraphicApplication/tests/InputStateTests.cpp @@ -0,0 +1,128 @@ +#include "InputState.hpp" + +#include "UnitTest++/UnitTest++.h" + +using namespace tp; + +SUITE(InputState) { + TEST(AllInputKindsArriveInOneOrderedBatch) { + InputQueue queue; + TouchGesture gesture; + gesture.scale = 1.5f; + + queue.post(TextEntered{ "a" }); + queue.post(KeyChanged{ Key::A, KeyAction::Repeat }); + queue.post(GestureChanged{ gesture }); + queue.post(KeyboardFocusChanged{ false }); + + CHECK_EQUAL(4u, queue.pendingEventCount()); + queue.applyPendingEvents(); + CHECK_EQUAL(0u, queue.pendingEventCount()); + + const std::vector& events = queue.state().events(); + REQUIRE CHECK_EQUAL(4u, events.size()); + CHECK(std::get(events[0]).utf8 == "a"); + CHECK(std::get(events[1]) == (KeyChanged{ Key::A, KeyAction::Repeat })); + CHECK_CLOSE(1.5f, std::get(events[2]).gesture.scale, 0.0001f); + CHECK(!std::get(events[3]).focused); + } + + TEST(PressAndReleaseInOneBatchRemainVisible) { + InputQueue queue; + queue.post(PointerButtonChanged{ PointerButton::Left, true }); + queue.post(PointerButtonChanged{ PointerButton::Left, false }); + queue.applyPendingEvents(); + + const ButtonState& left = queue.state().button(PointerButton::Left); + CHECK(left.pressed); + CHECK(left.released); + CHECK(!left.down); + + queue.applyPendingEvents(); + CHECK(!queue.state().button(PointerButton::Left).pressed); + CHECK(!queue.state().button(PointerButton::Left).released); + } + + TEST(KeysAndPointerButtonsAreSeparateStates) { + InputQueue queue; + queue.post(KeyChanged{ Key::A, KeyAction::Press }); + queue.applyPendingEvents(); + + CHECK(queue.state().key(Key::A).down); + CHECK(!queue.state().button(PointerButton::Left).down); + } + + TEST(PointerAndScrollDescribeTheWholeBatch) { + InputQueue queue; + queue.post(PointerMoved{ { 10, 20 } }); + queue.post(PointerMoved{ { 15, 25 } }); + queue.post(Scrolled{ { 1, 2 }, ScrollSource::Wheel }); + queue.post(Scrolled{ { 3, 4 }, ScrollSource::Touchpad }); + queue.applyPendingEvents(); + + const InputState& state = queue.state(); + CHECK_CLOSE(15, state.pointer().x, 0.0001f); + CHECK_CLOSE(25, state.pointer().y, 0.0001f); + CHECK_CLOSE(15, state.pointerDelta().x, 0.0001f); + CHECK_CLOSE(4, state.scroll().x, 0.0001f); + CHECK_CLOSE(6, state.scroll().y, 0.0001f); + CHECK(state.isContinuousScroll()); + CHECK(state.isPointerInside()); + + queue.applyPendingEvents(); + CHECK_CLOSE(0, queue.state().pointerDelta().x, 0.0001f); + CHECK_CLOSE(0, queue.state().scroll().y, 0.0001f); + CHECK(!queue.state().isContinuousScroll()); + } + + TEST(PointerLeftIsAnExplicitEventThatReleasesHeldButtons) { + InputQueue queue; + queue.post(PointerMoved{ { 5, 5 } }); + queue.post(PointerButtonChanged{ PointerButton::Right, true }); + queue.applyPendingEvents(); + CHECK(queue.state().button(PointerButton::Right).down); + + queue.post(PointerLeft{}); + queue.applyPendingEvents(); + + CHECK(!queue.state().isPointerInside()); + CHECK(queue.state().button(PointerButton::Right).released); + const std::vector& events = queue.state().events(); + REQUIRE CHECK_EQUAL(2u, events.size()); + CHECK(std::holds_alternative(events[0])); + CHECK(std::holds_alternative(events[1])); + } + + TEST(FocusLossReleasesHeldKeysBeforeTheFocusEvent) { + InputQueue queue; + queue.post(KeyChanged{ Key::A, KeyAction::Press }); + queue.applyPendingEvents(); + CHECK(queue.state().key(Key::A).down); + + queue.post(KeyboardFocusChanged{ false }); + queue.applyPendingEvents(); + + CHECK(!queue.state().key(Key::A).down); + CHECK(queue.state().key(Key::A).released); + const std::vector& events = queue.state().events(); + REQUIRE CHECK_EQUAL(2u, events.size()); + CHECK(std::get(events[0]).action == KeyAction::Release); + CHECK(!std::get(events[1]).focused); + } + + TEST(ViewOffsetsThePointerAndCanHideButtons) { + InputQueue queue; + queue.post(PointerMoved{ { 30, 40 } }); + queue.post(PointerButtonChanged{ PointerButton::Left, true }); + queue.applyPendingEvents(); + + const InputView view(queue.state(), { 10, 10 }); + CHECK_CLOSE(20, view.getPointer().x, 0.0001f); + CHECK(view.isDown(PointerButton::Left)); + + const InputView hidden = view.withButtons(InputView::Buttons::Hidden); + CHECK(!hidden.isDown(PointerButton::Left)); + CHECK_CLOSE(0, hidden.getPointerPressure(), 0.0001f); + CHECK_CLOSE(30, hidden.relativeTo({ 0, 0 }).getPointer().x, 0.0001f); + } +} diff --git a/GraphicApplication/tests/Main.cpp b/GraphicApplication/tests/Main.cpp new file mode 100644 index 00000000..7ae121b1 --- /dev/null +++ b/GraphicApplication/tests/Main.cpp @@ -0,0 +1,3 @@ +#include "UnitTest++/UnitTest++.h" + +int main() { return UnitTest::RunAllTests(); } diff --git a/GraphicApplication/tests/TouchGestureTests.cpp b/GraphicApplication/tests/TouchGestureTests.cpp new file mode 100644 index 00000000..770e134e --- /dev/null +++ b/GraphicApplication/tests/TouchGestureTests.cpp @@ -0,0 +1,174 @@ +#include "UnitTest++/UnitTest++.h" + +#include "InputCoalescer.hpp" +#include "TouchGesture.hpp" + +#include +#include + +using namespace tp; + +namespace { + std::vector feed(TouchGestureTracker& tracker, std::vector touches, uint32_t timeMs = 0) { + std::vector gestures; + tracker.update(touches, gestures, timeMs); + return gestures; + } + + int32_t tapFingersOf(const std::vector& gestures) { + return gestures.empty() || gestures.back().phase != GesturePhase::End ? -1 : gestures.back().tapFingers; + } + + class GestureLog : public InputSink { + public: + void post(const InputEvent& event) override { + if (const auto* changed = std::get_if(&event)) { + gestures.push_back(changed->gesture); + } + } + std::vector gestures; + }; +} + +SUITE(TouchGestureTracker) { + TEST(OneFingerProducesNoGesture) { + TouchGestureTracker tracker; + + CHECK(feed(tracker, { { 1, { 10, 10 } } }).empty()); + CHECK(!tracker.isActive()); + } + + TEST(SecondFingerBeginsAtTheMidpoint) { + TouchGestureTracker tracker; + + const auto gestures = feed(tracker, { { 1, { 0, 0 } }, { 2, { 100, 0 } } }); + + REQUIRE CHECK_EQUAL(1u, gestures.size()); + CHECK(gestures[0].phase == GesturePhase::Begin); + CHECK_CLOSE(50.0f, gestures[0].center.x, 0.001f); + CHECK(tracker.isActive()); + } + + TEST(MovingBothFingersPans) { + TouchGestureTracker tracker; + feed(tracker, { { 1, { 0, 0 } }, { 2, { 100, 0 } } }); + + const auto gestures = feed(tracker, { { 1, { 10, 20 } }, { 2, { 110, 20 } } }); + + REQUIRE CHECK_EQUAL(1u, gestures.size()); + CHECK(gestures[0].phase == GesturePhase::Update); + CHECK_CLOSE(10.0f, gestures[0].pan.x, 0.001f); + CHECK_CLOSE(20.0f, gestures[0].pan.y, 0.001f); + CHECK_CLOSE(1.0f, gestures[0].scale, 0.001f); + CHECK_CLOSE(0.0f, gestures[0].rotation, 0.001f); + } + + TEST(SpreadingFingersScalesAroundTheMidpoint) { + TouchGestureTracker tracker; + feed(tracker, { { 1, { 50, 0 } }, { 2, { 150, 0 } } }); + + const auto gestures = feed(tracker, { { 1, { 0, 0 } }, { 2, { 200, 0 } } }); + + REQUIRE CHECK_EQUAL(1u, gestures.size()); + CHECK_CLOSE(2.0f, gestures[0].scale, 0.001f); + CHECK_CLOSE(0.0f, gestures[0].pan.x, 0.001f); + CHECK_CLOSE(100.0f, gestures[0].center.x, 0.001f); + } + + TEST(TurningFingersRotates) { + TouchGestureTracker tracker; + feed(tracker, { { 1, { -50, 0 } }, { 2, { 50, 0 } } }); + + const auto gestures = feed(tracker, { { 1, { 0, -50 } }, { 2, { 0, 50 } } }); + + REQUIRE CHECK_EQUAL(1u, gestures.size()); + CHECK_CLOSE(std::numbers::pi_v / 2, gestures[0].rotation, 0.001f); + CHECK_CLOSE(1.0f, gestures[0].scale, 0.001f); + } + + TEST(LiftingAFingerEnds) { + TouchGestureTracker tracker; + feed(tracker, { { 1, { 0, 0 } }, { 2, { 100, 0 } } }); + + const auto gestures = feed(tracker, { { 1, { 0, 0 } } }, 1000); + + REQUIRE CHECK_EQUAL(1u, gestures.size()); + CHECK(gestures[0].phase == GesturePhase::End); + CHECK_EQUAL(0, gestures[0].tapFingers); + CHECK(!tracker.isActive()); + } + + TEST(QuickTwoFingerTouchIsATwoFingerTap) { + TouchGestureTracker tracker; + feed(tracker, { { 1, { 0, 0 } } }, 1000); + feed(tracker, { { 1, { 0, 0 } }, { 2, { 100, 0 } } }, 1020); + CHECK(feed(tracker, { { 2, { 102, 1 } } }, 1120).empty()); + + CHECK_EQUAL(2, tapFingersOf(feed(tracker, {}, 1150))); + } + + TEST(QuickThreeFingerTouchIsAThreeFingerTap) { + TouchGestureTracker tracker; + feed(tracker, { { 1, { 0, 0 } }, { 2, { 100, 0 } } }, 1000); + feed(tracker, { { 1, { 0, 0 } }, { 2, { 100, 0 } }, { 3, { 50, 80 } } }, 1030); + feed(tracker, { { 3, { 50, 80 } } }, 1150); + + CHECK_EQUAL(3, tapFingersOf(feed(tracker, {}, 1200))); + } + + TEST(PinchingIsNotATap) { + TouchGestureTracker tracker; + feed(tracker, { { 1, { 0, 0 } }, { 2, { 100, 0 } } }, 1000); + feed(tracker, { { 1, { 0, 0 } }, { 2, { 160, 0 } } }, 1100); + + CHECK_EQUAL(0, tapFingersOf(feed(tracker, {}, 1200))); + } + + TEST(HoldingTwoFingersIsNotATap) { + TouchGestureTracker tracker; + feed(tracker, { { 1, { 0, 0 } }, { 2, { 100, 0 } } }, 1000); + + CHECK_EQUAL(0, tapFingersOf(feed(tracker, {}, 1000 + TouchGestureTracker::kTapMaximumMs + 50))); + } + + TEST(OneFingerTapIsNotReported) { + TouchGestureTracker tracker; + feed(tracker, { { 1, { 0, 0 } } }, 1000); + + CHECK(feed(tracker, {}, 1050).empty()); + } + + TEST(SwappedFingerRebasesWithoutJumping) { + TouchGestureTracker tracker; + feed(tracker, { { 1, { 0, 0 } }, { 2, { 100, 0 } } }); + + CHECK(feed(tracker, { { 1, { 0, 0 } }, { 3, { 400, 300 } } }).empty()); + const auto gestures = feed(tracker, { { 1, { 0, 0 } }, { 3, { 410, 300 } } }); + + REQUIRE CHECK_EQUAL(1u, gestures.size()); + CHECK_CLOSE(5.0f, gestures[0].pan.x, 0.001f); + } +} + +SUITE(TouchGestureCoalescing) { + TEST(ConsecutiveUpdatesMergeIntoOne) { + InputCoalescer coalescer; + coalescer.post(GestureChanged{ { GesturePhase::Begin, { 50, 0 } } }); + coalescer.post(GestureChanged{ { GesturePhase::Update, { 60, 0 }, { 10, 0 }, 2.0f, 0.1f } }); + coalescer.post(GestureChanged{ { GesturePhase::Update, { 65, 5 }, { 5, 5 }, 1.5f, 0.2f } }); + coalescer.post(GestureChanged{ { GesturePhase::End, { 65, 5 } } }); + + GestureLog log; + coalescer.deliver(log); + + REQUIRE CHECK_EQUAL(3u, log.gestures.size()); + CHECK(log.gestures[0].phase == GesturePhase::Begin); + CHECK(log.gestures[1].phase == GesturePhase::Update); + CHECK_CLOSE(15.0f, log.gestures[1].pan.x, 0.001f); + CHECK_CLOSE(5.0f, log.gestures[1].pan.y, 0.001f); + CHECK_CLOSE(3.0f, log.gestures[1].scale, 0.001f); + CHECK_CLOSE(0.3f, log.gestures[1].rotation, 0.001f); + CHECK_CLOSE(65.0f, log.gestures[1].center.x, 0.001f); + CHECK(log.gestures[2].phase == GesturePhase::End); + } +} -- 2.49.1 From d3fc5be922a51b735e69a7036512356624fda374 Mon Sep 17 00:00:00 2001 From: agent Date: Thu, 29 Oct 2026 05:24:06 +0300 Subject: [PATCH 2/5] GraphicApplication: diagnostics for the frame loop diagnostics/ holds what the application loop uses to observe itself: - Profiler samples section durations and counters per frame (wall and CPU time, mean/median/p95/p99). It is an explicit instance with no global; ProfileScope takes the profiler it reports to. GPU time is reported as a section like any other measurement. - UiTrail is the stack of profile scopes open on the frame-loop thread. The profiler owns it and ProfileScope pushes onto it, so a hang report names the scope that stalled. - HangWatchdog writes a report when a non-idle phase stalls: open scope trail, input count, thread stacks sampled through Callstack, and a blocked/busy verdict. Debugger pauses are not counted. Applications add their own state to reports through report file sources. --- .../src/diagnostics/private/HangWatchdog.cpp | 393 ++++++++++++++++++ .../src/diagnostics/private/Profiler.cpp | 219 ++++++++++ .../src/diagnostics/private/UiTrail.cpp | 59 +++ .../src/diagnostics/public/HangWatchdog.hpp | 122 ++++++ .../src/diagnostics/public/Profiler.hpp | 116 ++++++ .../src/diagnostics/public/UiTrail.hpp | 31 ++ .../tests/HangWatchdogTests.cpp | 255 ++++++++++++ GraphicApplication/tests/ProfilerTests.cpp | 138 ++++++ 8 files changed, 1333 insertions(+) create mode 100644 GraphicApplication/src/diagnostics/private/HangWatchdog.cpp create mode 100644 GraphicApplication/src/diagnostics/private/Profiler.cpp create mode 100644 GraphicApplication/src/diagnostics/private/UiTrail.cpp create mode 100644 GraphicApplication/src/diagnostics/public/HangWatchdog.hpp create mode 100644 GraphicApplication/src/diagnostics/public/Profiler.hpp create mode 100644 GraphicApplication/src/diagnostics/public/UiTrail.hpp create mode 100644 GraphicApplication/tests/HangWatchdogTests.cpp create mode 100644 GraphicApplication/tests/ProfilerTests.cpp diff --git a/GraphicApplication/src/diagnostics/private/HangWatchdog.cpp b/GraphicApplication/src/diagnostics/private/HangWatchdog.cpp new file mode 100644 index 00000000..d28e2520 --- /dev/null +++ b/GraphicApplication/src/diagnostics/private/HangWatchdog.cpp @@ -0,0 +1,393 @@ +#include "HangWatchdog.hpp" + +#include "StackTrace.hpp" + +#include +#include +#include +#include +#include +#include + +#if defined(__linux__) +#include +#endif + +namespace tp { + + namespace { + int64_t nowNs() { + return std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()) + .count(); + } + + std::string timestamp(const char* pattern) { + const std::time_t now = std::time(nullptr); + char text[64]; + std::strftime(text, sizeof(text), pattern, std::localtime(&now)); + return text; + } + + void writeFile(const std::filesystem::path& path, const std::string& text, std::ios::openmode mode) { + std::ofstream file(path, mode); + file << text; + } + + constexpr double kCheckIntervalMs = 100; + } + + HangWatchdog::HangWatchdog(HangSettings settings, UiTrail& trail) : + mSettings(std::move(settings)), + mTrail(trail) { + mSettings.directory = directoryFor(mSettings.directory.empty() ? defaultHangsDirectory() : mSettings.directory); + if (!mSettings.clockNs) { + mSettings.clockNs = nowNs; + } + mPhaseStartNs.store(now()); + allowDebuggerAttach(); + StackTrace::prepareThreadSampling(); + mUiTid = StackTrace::callingThreadId(); + mTrail.trackCallingThread(); + pruneOldReports(); + mThread = std::thread([this]() { watch(); }); + } + + HangWatchdog::~HangWatchdog() { + { + std::lock_guard lock(mStopMutex); + mStopping = true; + } + mStopped.notify_all(); + mThread.join(); + mTrail.stopTracking(); + } + + std::string HangWatchdog::formatThreads(const std::vector& threads, int uiTid) { + std::ostringstream text; + for (const ThreadSample& thread : threads) { + text << (thread.tid == uiTid ? "UI THREAD " : "thread ") << thread.tid << " \"" << thread.name << "\" state " + << thread.state; + if (!thread.waitChannel.empty() && thread.waitChannel != "0") { + text << " waiting in " << thread.waitChannel; + } + text << "\n"; + if (!thread.answered) { + text << " (did not answer the sample signal)\n"; + } + for (uintptr_t frame : thread.frames) { + text << " " << StackTrace::describe(frame) << "\n"; + } + text << "\n"; + } + return text.str(); + } + + void HangWatchdog::enterPhase(UiPhase phase) { + mPhaseStartNs.store(now(), std::memory_order_relaxed); + mPhaseInputEvents.store(mInputEvents.load(std::memory_order_relaxed), std::memory_order_relaxed); + mPhase.store(phase, std::memory_order_relaxed); + mBeat.fetch_add(1, std::memory_order_release); + } + + void HangWatchdog::noteInputEvents(uint64_t count) { mInputEvents.fetch_add(count, std::memory_order_relaxed); } + + int64_t HangWatchdog::now() const { return mSettings.clockNs(); } + + void HangWatchdog::addReportFileSource(ReportFileSource reportFileSource) { + std::lock_guard lock(mReportFileSourcesMutex); + mReportFileSources.push_back(std::move(reportFileSource)); + } + + std::filesystem::path HangWatchdog::defaultHangsDirectory() { + const char* home = std::getenv("HOME"); + const std::filesystem::path root = home && *home ? std::filesystem::path(home) : std::filesystem::current_path(); + return root / processName() / "hangs"; + } + + std::filesystem::path HangWatchdog::directoryFor(const std::filesystem::path& applicationHangsDirectory) { + if (const char* configured = std::getenv("MODULES_HANG_DIR"); configured && *configured) { + return configured; + } + return applicationHangsDirectory; + } + + std::string HangWatchdog::processName() { +#if defined(__linux__) + std::ifstream comm("/proc/self/comm"); + std::string name; + if (std::getline(comm, name) && !name.empty()) { + return name; + } +#endif + return "app"; + } + + void HangWatchdog::allowDebuggerAttach() { +#if defined(__linux__) && defined(PR_SET_PTRACER) + prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0); +#endif + } + + const char* HangWatchdog::phaseName(UiPhase phase) { + switch (phase) { + case UiPhase::Idle: return "idle"; + case UiPhase::Input: return "processing input"; + case UiPhase::Draw: return "drawing"; + case UiPhase::Present: return "presenting"; + } + return "unknown"; + } + + void HangWatchdog::watch() { + Stall stall; + int64_t previous = now(); + std::unique_lock lock(mStopMutex); + while (!mStopping) { + mStopped.wait_for(lock, std::chrono::duration(kCheckIntervalMs), [this]() { + return mStopping; + }); + if (mStopping) { + return; + } + lock.unlock(); + const int64_t current = now(); + discountProcessPause(current, previous); + inspect(stall, current); + previous = current; + mInspections.fetch_add(1); + lock.lock(); + } + } + + void HangWatchdog::discountProcessPause(int64_t now, int64_t previous) { + const double sinceLastCheckMs = (double) (now - previous) / 1.0e6; + const double pausedMs = sinceLastCheckMs - kCheckIntervalMs; + if (pausedMs < mSettings.processPauseMs) { + return; + } + + const uint64_t beat = mBeat.load(std::memory_order_acquire); + if (beat != mPausedBeat) { + mPausedBeat = beat; + mPausedNs = 0; + } + mPausedNs += (int64_t) (pausedMs * 1.0e6); + } + + double HangWatchdog::stalledMs(int64_t now) const { + const bool pausedThisPhase = mPausedBeat == mBeat.load(std::memory_order_acquire); + const int64_t paused = pausedThisPhase ? mPausedNs : 0; + return (double) (now - mPhaseStartNs.load(std::memory_order_relaxed) - paused) / 1.0e6; + } + + uint64_t HangWatchdog::inputEventsInPhase() const { + return mInputEvents.load(std::memory_order_relaxed) - mPhaseInputEvents.load(std::memory_order_relaxed); + } + + void HangWatchdog::inspect(Stall& stall, int64_t now) { + const uint64_t beat = mBeat.load(std::memory_order_acquire); + const UiPhase phase = mPhase.load(std::memory_order_relaxed); + const double stalled = stalledMs(now); + + if (!stall.report.empty() && stall.beat != beat) { + finishRecovered(stall, stalled); + stall = {}; + } + if (phase == UiPhase::Idle || stalled < mSettings.thresholdMs) { + return; + } + + if (stall.report.empty()) { + stall.beat = beat; + stall.phase = phase; + beginReport(stall, stalled); + return; + } + if (stall.followUps < mSettings.followUpMs.size() && stalled >= mSettings.followUpMs[stall.followUps]) { + appendFollowUp(stall, stalled); + } + } + + std::string HangWatchdog::uiSnapshot(double stalled, UiPhase phase) const { + std::ostringstream text; + text << "ui thread " << phaseName(phase) << " for " << (int64_t) stalled << " ms\n"; + text << "input events handled during this phase " << inputEventsInPhase() << "\n"; + text << "scope trail (outermost first):\n"; + const std::vector names = mTrail.snapshot(); + if (names.empty()) { + text << " (no profiled scope open)\n"; + } + for (size_t index = 0; index < names.size(); index++) { + text << " " << std::string(index * 2, ' ') << names[index] << "\n"; + } + return text.str(); + } + + std::filesystem::path HangWatchdog::newReportPath() const { + const std::string base = mSettings.application + "-" + timestamp("%Y-%m-%d_%H-%M-%S") + "-build" + mSettings.build; + std::filesystem::path path = mSettings.directory / base; + for (int suffix = 2; std::filesystem::exists(path); suffix++) { + path = mSettings.directory / (base + "-" + std::to_string(suffix)); + } + return path; + } + + std::string HangWatchdog::reportHeader() const { + std::ostringstream text; + text << "application " << mSettings.application << "\n"; + text << "build " << mSettings.build << "\n"; + text << "detected " << timestamp("%Y-%m-%d %H:%M:%S") << "\n"; + text << "threshold " << (int64_t) mSettings.thresholdMs << " ms\n"; + text << "debugger attached " << (StackTrace::isDebuggerAttached() ? "yes" : "no") << "\n\n"; + return text.str(); + } + + void HangWatchdog::beginReport(Stall& stall, double stalled) { + std::error_code error; + std::filesystem::create_directories(mSettings.directory, error); + stall.report = newReportPath(); + std::filesystem::create_directories(stall.report, error); + if (error) { + return; + } + + writeFile( + stall.report / "summary.txt", reportHeader() + uiSnapshot(stalled, stall.phase), std::ios::out | std::ios::trunc + ); + writeSourcedFiles(stall.report); + mReportsWritten.fetch_add(1); + + const Investigation investigation = investigate(); + writeFile( + stall.report / "summary.txt", "\nverdict: " + investigation.verdict + "\n", std::ios::out | std::ios::app + ); + if (!investigation.threads.empty()) { + writeFile(stall.report / "threads.txt", investigation.threads, std::ios::out | std::ios::trunc); + } + pruneOldReports(); + } + + void HangWatchdog::writeSourcedFiles(const std::filesystem::path& report) { + std::vector reportFileSources; + { + std::lock_guard lock(mReportFileSourcesMutex); + reportFileSources = mReportFileSources; + } + for (const ReportFileSource& reportFileSource : reportFileSources) { + for (const HangReportFile& file : reportFileSource()) { + writeFile(report / file.fileName, file.text, std::ios::out | std::ios::trunc); + } + } + } + + void HangWatchdog::appendFollowUp(Stall& stall, double stalled) { + stall.followUps++; + const std::string header = "\nstill stuck at " + timestamp("%H:%M:%S") + "\n"; + writeFile(stall.report / "summary.txt", header + uiSnapshot(stalled, stall.phase), std::ios::out | std::ios::app); + + const Investigation investigation = investigate(); + writeFile(stall.report / "summary.txt", "verdict: " + investigation.verdict + "\n", std::ios::out | std::ios::app); + if (!investigation.threads.empty()) { + writeFile( + stall.report / "threads.txt", + "\n===== follow-up at " + timestamp("%H:%M:%S") + " =====\n" + investigation.threads, + std::ios::out | std::ios::app + ); + } + } + + void HangWatchdog::finishRecovered(Stall& stall, double stalled) { + (void) stalled; + writeFile( + stall.report / "summary.txt", "\nrecovered at " + timestamp("%H:%M:%S") + "\n", std::ios::out | std::ios::app + ); + } + + HangWatchdog::Investigation HangWatchdog::investigate() const { + if (StackTrace::isDebuggerAttached()) { + return { "not sampled: a debugger is attached, its thread view shows where the ui thread is", "" }; + } + if (!StackTrace::canSampleThreads()) { + return { "not sampled: thread stacks are not available on this platform", "" }; + } + + std::vector> uiSamples; + std::string uiState; + std::string uiWait; + std::string threads = sampleStacks(uiSamples, uiState, uiWait); + std::string verdict = stackVerdict(uiSamples, uiState, uiWait); + if (inputEventsInPhase() > 0) { + verdict += + "; " + std::to_string(inputEventsInPhase()) + " input events were handled without leaving the input phase"; + } + return { verdict, threads }; + } + + std::string HangWatchdog::sampleStacks( + std::vector>& uiSamples, std::string& uiState, std::string& uiWait + ) const { + std::ostringstream text; + for (int sample = 0; sample < std::max(1, mSettings.stackSamples); sample++) { + if (sample > 0) { + std::this_thread::sleep_for(std::chrono::duration(mSettings.stackSampleIntervalMs)); + } + const std::vector threads = StackTrace::sampleAllThreads(); + for (const ThreadSample& thread : threads) { + if (thread.tid == mUiTid) { + uiSamples.push_back(thread.frames); + uiState = thread.state; + uiWait = thread.waitChannel; + } + } + text << "===== sample " << sample + 1 << " =====\n" << formatThreads(threads, mUiTid); + } + return text.str(); + } + + std::string HangWatchdog::stackVerdict( + const std::vector>& uiSamples, const std::string& state, const std::string& waitChannel + ) { + if (uiSamples.empty()) { + return "ui thread not found"; + } + const bool sameStack = + std::all_of(uiSamples.begin(), uiSamples.end(), [&](const auto& frames) { return frames == uiSamples.front(); }); + const bool sleeping = !state.empty() && (state[0] == 'S' || state[0] == 'D'); + if (sameStack && sleeping) { + std::string text = "blocked: the ui thread sleeps at the same place in every sample"; + if (!waitChannel.empty() && waitChannel != "0") { + text += " (kernel wait " + waitChannel + ")"; + } + return text; + } + if (!sleeping) { + return "busy: the ui thread is running, check the changing frames in threads.txt"; + } + return "waiting repeatedly: the ui thread sleeps but at different places, likely polling or lock contention"; + } + + std::vector HangWatchdog::reports() const { + std::vector found; + std::error_code error; + for (const auto& entry : std::filesystem::directory_iterator(mSettings.directory, error)) { + if (entry.is_directory()) { + found.push_back(entry.path()); + } + } + std::sort(found.begin(), found.end(), [](const auto& a, const auto& b) { + return std::filesystem::last_write_time(a) < std::filesystem::last_write_time(b); + }); + return found; + } + + void HangWatchdog::pruneOldReports() const { + const std::vector found = reports(); + if (found.size() <= mSettings.keptReports) { + return; + } + std::error_code error; + for (size_t index = 0; index + mSettings.keptReports < found.size(); index++) { + std::filesystem::remove_all(found[index], error); + } + } +} diff --git a/GraphicApplication/src/diagnostics/private/Profiler.cpp b/GraphicApplication/src/diagnostics/private/Profiler.cpp new file mode 100644 index 00000000..973b320c --- /dev/null +++ b/GraphicApplication/src/diagnostics/private/Profiler.cpp @@ -0,0 +1,219 @@ +#include "Profiler.hpp" + +#include +#include +#include +#include + +namespace tp { + + namespace { + double clockMs(clockid_t clock) { + timespec now{}; + clock_gettime(clock, &now); + return (double) now.tv_sec * 1000.0 + (double) now.tv_nsec / 1.0e6; + } + + int64_t monotonicNs() { + timespec now{}; + clock_gettime(CLOCK_MONOTONIC, &now); + return (int64_t) now.tv_sec * 1000000000ll + now.tv_nsec; + } + + double percentile(const std::vector& sorted, double fraction) { + if (sorted.empty()) { + return 0; + } + const double position = fraction * (double) (sorted.size() - 1); + const size_t lower = (size_t) std::floor(position); + const size_t upper = std::min(lower + 1, sorted.size() - 1); + const double blend = position - (double) lower; + return sorted[lower] + (sorted[upper] - sorted[lower]) * blend; + } + } + + ProfileStatistics summarize(std::vector values) { + ProfileStatistics statistics; + statistics.samples = values.size(); + if (values.empty()) { + return statistics; + } + + std::sort(values.begin(), values.end()); + for (double value : values) { + statistics.total += value; + } + statistics.mean = statistics.total / (double) values.size(); + statistics.minimum = values.front(); + statistics.maximum = values.back(); + statistics.median = percentile(values, 0.5); + statistics.p95 = percentile(values, 0.95); + statistics.p99 = percentile(values, 0.99); + return statistics; + } + + void Profiler::setEnabled(bool enabled) { + mEnabled = enabled; + if (!enabled) { + mInFrame = false; + } + } + + ProfileId Profiler::intern(std::deque& names, const char* name) { + const ptrdiff_t existing = indexOf(names, name); + if (existing >= 0) { + return (ProfileId) existing; + } + if (names.size() >= kMaxNames) { + return (ProfileId) (kMaxNames - 1); + } + names.emplace_back(name); + return (ProfileId) (names.size() - 1); + } + + ptrdiff_t Profiler::indexOf(const std::deque& names, const std::string& name) { + const auto found = std::find(names.begin(), names.end(), name); + return found == names.end() ? -1 : found - names.begin(); + } + + ProfileId Profiler::section(const char* name) { return intern(mSectionNames, name); } + + ProfileId Profiler::counter(const char* name) { return intern(mCounterNames, name); } + + const char* Profiler::sectionName(ProfileId section) const { + return section < mSectionNames.size() ? mSectionNames[section].c_str() : "?"; + } + + void Profiler::beginFrame() { + if (!mEnabled) { + return; + } + mSectionMs.fill(0); + mSectionCalls.fill(0); + mCounters.fill(0); + mFrameWallStart = clockMs(CLOCK_MONOTONIC); + mFrameCpuStart = threadCpuMs(); + mInFrame = true; + } + + void Profiler::endFrame() { + if (!mEnabled || !mInFrame) { + return; + } + mInFrame = false; + + FrameProfile frame; + frame.index = mNextFrame++; + frame.wallMs = clockMs(CLOCK_MONOTONIC) - mFrameWallStart; + frame.cpuMs = threadCpuMs() - mFrameCpuStart; + frame.sectionMs.assign(mSectionMs.begin(), mSectionMs.begin() + (ptrdiff_t) mSectionNames.size()); + frame.sectionCalls.assign(mSectionCalls.begin(), mSectionCalls.begin() + (ptrdiff_t) mSectionNames.size()); + frame.counters.assign(mCounters.begin(), mCounters.begin() + (ptrdiff_t) mCounterNames.size()); + mFrames.push_back(std::move(frame)); + } + + void Profiler::addSectionTime(ProfileId section, double milliseconds) { + if (!mInFrame || section >= kMaxNames) { + return; + } + mSectionMs[section] += milliseconds; + mSectionCalls[section]++; + } + + void Profiler::addCounter(ProfileId counter, double amount) { + if (!mInFrame || counter >= kMaxNames) { + return; + } + mCounters[counter] += amount; + } + + void Profiler::clear() { + mFrames.clear(); + mNextFrame = 0; + mInFrame = false; + } + + std::vector Profiler::sectionSeries(const std::string& name) const { + std::vector series; + const ptrdiff_t index = indexOf(mSectionNames, name); + if (index < 0) { + return series; + } + for (const FrameProfile& frame : mFrames) { + series.push_back((size_t) index < frame.sectionMs.size() ? frame.sectionMs[(size_t) index] : 0.0); + } + return series; + } + + std::vector Profiler::counterSeries(const std::string& name) const { + std::vector series; + const ptrdiff_t index = indexOf(mCounterNames, name); + if (index < 0) { + return series; + } + for (const FrameProfile& frame : mFrames) { + series.push_back((size_t) index < frame.counters.size() ? frame.counters[(size_t) index] : 0.0); + } + return series; + } + + double Profiler::counterTotal(const std::string& name) const { + double total = 0; + for (double value : counterSeries(name)) { + total += value; + } + return total; + } + + double Profiler::sectionTotal(const std::string& name) const { + double total = 0; + for (double value : sectionSeries(name)) { + total += value; + } + return total; + } + + uint64_t Profiler::sectionCallTotal(const std::string& name) const { + uint64_t total = 0; + const ptrdiff_t index = indexOf(mSectionNames, name); + if (index < 0) { + return 0; + } + for (const FrameProfile& frame : mFrames) { + if ((size_t) index < frame.sectionCalls.size()) { + total += frame.sectionCalls[(size_t) index]; + } + } + return total; + } + + double Profiler::threadCpuMs() { return clockMs(CLOCK_THREAD_CPUTIME_ID); } + + double Profiler::processCpuMs() { return clockMs(CLOCK_PROCESS_CPUTIME_ID); } + + double Profiler::peakResidentMegabytes() { + rusage usage{}; + getrusage(RUSAGE_SELF, &usage); + return (double) usage.ru_maxrss / 1024.0; + } + + ProfileScope::ProfileScope(Profiler& profiler, ProfileId section) : + mProfiler(profiler), + mSection(section) { + mProfiler.trail().push(mProfiler.sectionName(section)); + if (mProfiler.isInFrame()) { + mStartNs = monotonicNs(); + } + } + + ProfileScope::ProfileScope(Profiler& profiler, const char* sectionName) : + ProfileScope(profiler, profiler.section(sectionName)) {} + + ProfileScope::~ProfileScope() { + mProfiler.trail().pop(); + if (mStartNs < 0) { + return; + } + mProfiler.addSectionTime(mSection, (double) (monotonicNs() - mStartNs) / 1.0e6); + } +} diff --git a/GraphicApplication/src/diagnostics/private/UiTrail.cpp b/GraphicApplication/src/diagnostics/private/UiTrail.cpp new file mode 100644 index 00000000..609915bb --- /dev/null +++ b/GraphicApplication/src/diagnostics/private/UiTrail.cpp @@ -0,0 +1,59 @@ +#include "UiTrail.hpp" + +#include + +namespace tp { + + void UiTrail::push(const char* name) { + if (!isTracked()) { + return; + } + const int depth = mDepth.load(std::memory_order_relaxed); + if (depth < kDepth) { + mNames[depth].store(name, std::memory_order_relaxed); + } else { + mOverflow.fetch_add(1, std::memory_order_relaxed); + } + mDepth.store(depth + 1, std::memory_order_release); + } + + void UiTrail::pop() { + if (!isTracked()) { + return; + } + const int depth = mDepth.load(std::memory_order_relaxed); + if (depth <= 0) { + return; + } + if (depth > kDepth) { + mOverflow.fetch_sub(1, std::memory_order_relaxed); + } + mDepth.store(depth - 1, std::memory_order_release); + } + + bool UiTrail::isTracked() const { + return mTrackedThread.load(std::memory_order_relaxed) == std::this_thread::get_id(); + } + + void UiTrail::trackCallingThread() { + mDepth.store(0); + mOverflow.store(0); + mTrackedThread.store(std::this_thread::get_id()); + } + + void UiTrail::stopTracking() { mTrackedThread.store(std::thread::id{}); } + + std::vector UiTrail::snapshot() const { + const int depth = std::min(mDepth.load(std::memory_order_acquire), kDepth); + std::vector names; + for (int index = 0; index < depth; index++) { + const char* name = mNames[index].load(std::memory_order_relaxed); + names.emplace_back(name ? name : "?"); + } + const int overflow = mOverflow.load(std::memory_order_relaxed); + if (overflow > 0) { + names.push_back("... " + std::to_string(overflow) + " deeper"); + } + return names; + } +} diff --git a/GraphicApplication/src/diagnostics/public/HangWatchdog.hpp b/GraphicApplication/src/diagnostics/public/HangWatchdog.hpp new file mode 100644 index 00000000..766be64a --- /dev/null +++ b/GraphicApplication/src/diagnostics/public/HangWatchdog.hpp @@ -0,0 +1,122 @@ +#pragma once + +#include "StackTrace.hpp" +#include "UiTrail.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace tp { + + enum class UiPhase : uint8_t { Idle, Input, Draw, Present }; + + struct HangSettings { + double thresholdMs = 2000; + std::vector followUpMs = { 10000, 30000 }; + int stackSamples = 5; + double stackSampleIntervalMs = 200; + double processPauseMs = 1000; + size_t keptReports = 20; + std::string application = "app"; + std::string build = "dev"; + std::filesystem::path directory; + std::function clockNs; + }; + + struct HangReportFile { + std::string fileName; + std::string text; + }; + + class HangWatchdog { + public: + using ReportFileSource = std::function()>; + + HangWatchdog(HangSettings settings, UiTrail& trail); + ~HangWatchdog(); + + HangWatchdog(const HangWatchdog&) = delete; + HangWatchdog& operator=(const HangWatchdog&) = delete; + + void enterPhase(UiPhase phase); + void noteInputEvents(uint64_t count = 1); + void addReportFileSource(ReportFileSource reportFileSource); + + [[nodiscard]] const std::filesystem::path& directory() const { return mSettings.directory; } + [[nodiscard]] std::vector reports() const; + [[nodiscard]] size_t reportsWritten() const { return mReportsWritten.load(); } + [[nodiscard]] uint64_t inspections() const { return mInspections.load(); } + + [[nodiscard]] static std::filesystem::path defaultHangsDirectory(); + [[nodiscard]] static std::filesystem::path directoryFor(const std::filesystem::path& applicationHangsDirectory); + [[nodiscard]] static std::string processName(); + static void allowDebuggerAttach(); + [[nodiscard]] static const char* phaseName(UiPhase phase); + + private: + struct Stall { + uint64_t beat = 0; + UiPhase phase = UiPhase::Idle; + std::filesystem::path report; + size_t followUps = 0; + }; + + struct Investigation { + std::string verdict; + std::string threads; + }; + + void watch(); + void inspect(Stall& stall, int64_t now); + void discountProcessPause(int64_t now, int64_t previous); + [[nodiscard]] double stalledMs(int64_t now) const; + [[nodiscard]] uint64_t inputEventsInPhase() const; + + void beginReport(Stall& stall, double stalled); + void appendFollowUp(Stall& stall, double stalled); + void finishRecovered(Stall& stall, double stalled); + [[nodiscard]] std::string reportHeader() const; + [[nodiscard]] std::string uiSnapshot(double stalled, UiPhase phase) const; + void writeSourcedFiles(const std::filesystem::path& report); + [[nodiscard]] Investigation investigate() const; + [[nodiscard]] std::string + sampleStacks(std::vector>& uiSamples, std::string& uiState, std::string& uiWait) const; + [[nodiscard]] static std::string formatThreads(const std::vector& threads, int uiTid); + [[nodiscard]] static std::string stackVerdict( + const std::vector>& uiSamples, const std::string& state, const std::string& waitChannel + ); + + void pruneOldReports() const; + [[nodiscard]] std::filesystem::path newReportPath() const; + [[nodiscard]] int64_t now() const; + + HangSettings mSettings; + UiTrail& mTrail; + std::atomic mBeat{ 0 }; + std::atomic mPhaseStartNs{ 0 }; + std::atomic mPhase{ UiPhase::Idle }; + std::atomic mInputEvents{ 0 }; + std::atomic mPhaseInputEvents{ 0 }; + std::atomic mReportsWritten{ 0 }; + std::atomic mInspections{ 0 }; + int mUiTid = 0; + + uint64_t mPausedBeat = 0; + int64_t mPausedNs = 0; + + std::mutex mReportFileSourcesMutex; + std::vector mReportFileSources; + + std::mutex mStopMutex; + std::condition_variable mStopped; + bool mStopping = false; + std::thread mThread; + }; +} diff --git a/GraphicApplication/src/diagnostics/public/Profiler.hpp b/GraphicApplication/src/diagnostics/public/Profiler.hpp new file mode 100644 index 00000000..0a0bd8a4 --- /dev/null +++ b/GraphicApplication/src/diagnostics/public/Profiler.hpp @@ -0,0 +1,116 @@ +#pragma once + +#include "UiTrail.hpp" + +#include +#include +#include +#include +#include + +namespace tp { + + using ProfileId = uint32_t; + + struct FrameProfile { + uint64_t index = 0; + double wallMs = 0; + double cpuMs = 0; + std::vector sectionMs; + std::vector sectionCalls; + std::vector counters; + }; + + struct ProfileStatistics { + size_t samples = 0; + double total = 0; + double mean = 0; + double minimum = 0; + double median = 0; + double p95 = 0; + double p99 = 0; + double maximum = 0; + }; + + [[nodiscard]] ProfileStatistics summarize(std::vector values); + + // A frame holds two generic channels: sections measure milliseconds, counters + // measure amounts. Nothing here knows which device or subsystem produced a + // number - a GPU timer reports into the section channel like any other sampler. + class Profiler { + public: + static constexpr size_t kMaxNames = 128; + + void setEnabled(bool enabled); + [[nodiscard]] bool isEnabled() const { return mEnabled; } + + [[nodiscard]] ProfileId section(const char* name); + [[nodiscard]] ProfileId counter(const char* name); + + void beginFrame(); + void endFrame(); + [[nodiscard]] bool isInFrame() const { return mInFrame; } + + // A duration measured outside the profiler (a GPU timer, a blocking wait, ...). + // ProfileScope does the same for a scoped block. Both count one sample. + void addSectionTime(ProfileId section, double milliseconds); + void addCounter(ProfileId counter, double amount); + + void clear(); + + [[nodiscard]] const std::vector& frames() const { return mFrames; } + [[nodiscard]] const std::deque& sectionNames() const { return mSectionNames; } + [[nodiscard]] const std::deque& counterNames() const { return mCounterNames; } + [[nodiscard]] const char* sectionName(ProfileId section) const; + + [[nodiscard]] std::vector sectionSeries(const std::string& name) const; + [[nodiscard]] std::vector counterSeries(const std::string& name) const; + [[nodiscard]] double counterTotal(const std::string& name) const; + [[nodiscard]] double sectionTotal(const std::string& name) const; + [[nodiscard]] uint64_t sectionCallTotal(const std::string& name) const; + + // Open profile scopes of the frame loop, readable from a watchdog thread. + [[nodiscard]] UiTrail& trail() { return mTrail; } + [[nodiscard]] const UiTrail& trail() const { return mTrail; } + + [[nodiscard]] static double threadCpuMs(); + [[nodiscard]] static double processCpuMs(); + [[nodiscard]] static double peakResidentMegabytes(); + + private: + static ProfileId intern(std::deque& names, const char* name); + [[nodiscard]] static ptrdiff_t indexOf(const std::deque& names, const std::string& name); + + bool mEnabled = false; + bool mInFrame = false; + uint64_t mNextFrame = 0; + double mFrameWallStart = 0; + double mFrameCpuStart = 0; + + std::deque mSectionNames; + std::deque mCounterNames; + + std::array mSectionMs{}; + std::array mSectionCalls{}; + std::array mCounters{}; + + std::vector mFrames; + UiTrail mTrail; + }; + + // Times a block into one section and records it on the trail while it is open. + class ProfileScope { + public: + ProfileScope(Profiler& profiler, ProfileId section); + ProfileScope(Profiler& profiler, const char* sectionName); + ~ProfileScope(); + + ProfileScope(const ProfileScope&) = delete; + ProfileScope& operator=(const ProfileScope&) = delete; + + private: + Profiler& mProfiler; + ProfileId mSection; + int64_t mStartNs = -1; + }; +} diff --git a/GraphicApplication/src/diagnostics/public/UiTrail.hpp b/GraphicApplication/src/diagnostics/public/UiTrail.hpp new file mode 100644 index 00000000..81ae6b59 --- /dev/null +++ b/GraphicApplication/src/diagnostics/public/UiTrail.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include +#include + +namespace tp { + + // The stack of named scopes open on the tracked thread; another thread may take a snapshot at any time. + class UiTrail { + public: + static constexpr int kDepth = 32; + + void push(const char* name); + void pop(); + + void trackCallingThread(); + void stopTracking(); + + [[nodiscard]] std::vector snapshot() const; + + private: + [[nodiscard]] bool isTracked() const; + + std::atomic mNames[kDepth] = {}; + std::atomic mDepth{ 0 }; + std::atomic mOverflow{ 0 }; + std::atomic mTrackedThread{}; + }; +} diff --git a/GraphicApplication/tests/HangWatchdogTests.cpp b/GraphicApplication/tests/HangWatchdogTests.cpp new file mode 100644 index 00000000..ff19f66b --- /dev/null +++ b/GraphicApplication/tests/HangWatchdogTests.cpp @@ -0,0 +1,255 @@ +#include "HangWatchdog.hpp" + +#include "UnitTest++/UnitTest++.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace tp; + +namespace { + struct TempDirectory { + std::filesystem::path path; + + explicit TempDirectory(const std::string& name) : + path(std::filesystem::temp_directory_path() / ("hang-watchdog-test-" + name)) { + std::filesystem::remove_all(path); + } + ~TempDirectory() { + if (!std::getenv("KEEP_HANG_REPORTS")) { + std::filesystem::remove_all(path); + } + } + }; + + HangSettings fastSettings(const std::filesystem::path& directory) { + HangSettings settings; + settings.thresholdMs = 150; + settings.followUpMs = {}; + settings.stackSamples = 3; + settings.stackSampleIntervalMs = 30; + settings.application = "test"; + settings.build = "0"; + settings.directory = directory; + return settings; + } + + std::string readFile(const std::filesystem::path& path) { + std::ifstream file(path); + std::stringstream text; + text << file.rdbuf(); + return text.str(); + } + + UiTrail& testTrail() { + static UiTrail trail; + return trail; + } + + struct TestScope { + explicit TestScope(const char* name) { testTrail().push(name); } + ~TestScope() { testTrail().pop(); } + }; + + bool hasVerdict(const std::filesystem::path& report) { + return readFile(report / "summary.txt").find("verdict:") != std::string::npos; + } + + template + bool waitUntil(Done&& done, double limitMs = 5000) { + const auto start = std::chrono::steady_clock::now(); + while (!done()) { + if (std::chrono::duration(std::chrono::steady_clock::now() - start).count() > limitMs) { + return false; + } + std::this_thread::yield(); + } + return true; + } + + class Gate { + public: + void waitOpen() { + std::unique_lock lock(mMutex); + mOpened.wait(lock, [this]() { return mOpen; }); + } + void open() { + { + std::lock_guard lock(mMutex); + mOpen = true; + } + mOpened.notify_all(); + } + + private: + std::mutex mMutex; + std::condition_variable mOpened; + bool mOpen = false; + }; +} + +SUITE(HangWatchdog) { + TEST(StuckDrawWritesAReportNamingTheOpenScopes) { + TempDirectory directory("stuck"); + HangWatchdog watchdog(fastSettings(directory.path), testTrail()); + Gate gate; + watchdog.enterPhase(UiPhase::Draw); + { + TestScope outer("test.outer"); + TestScope waiting("test.waiting_on_gate"); + std::thread release([&]() { + waitUntil([&]() { return watchdog.reportsWritten() > 0 && hasVerdict(watchdog.reports().front()); }); + gate.open(); + }); + gate.waitOpen(); + release.join(); + } + watchdog.enterPhase(UiPhase::Idle); + REQUIRE CHECK_EQUAL(1u, watchdog.reports().size()); + const std::string summary = readFile(watchdog.reports().front() / "summary.txt"); + CHECK(summary.find("drawing") != std::string::npos); + CHECK(summary.find("test.outer") != std::string::npos); + CHECK(summary.find("test.waiting_on_gate") != std::string::npos); + CHECK(summary.find("verdict: blocked") != std::string::npos); + const std::string threads = readFile(watchdog.reports().front() / "threads.txt"); + CHECK(threads.find("UI THREAD") != std::string::npos); + CHECK( + threads.find("waitOpen") != std::string::npos || threads.find("pthread_cond") != std::string::npos || + threads.find("libc") != std::string::npos + ); + CHECK(waitUntil([&]() { + return readFile(watchdog.reports().front() / "summary.txt").find("recovered") != std::string::npos; + })); + } + + TEST(SpinningUiThreadIsReportedAsBusy) { + TempDirectory directory("busy"); + HangWatchdog watchdog(fastSettings(directory.path), testTrail()); + watchdog.enterPhase(UiPhase::Input); + { + TestScope spinning("test.spinning"); + volatile uint64_t work = 0; + while (watchdog.reportsWritten() == 0 || !hasVerdict(watchdog.reports().front())) { + work = work + 1; + } + } + watchdog.enterPhase(UiPhase::Idle); + const std::string summary = readFile(watchdog.reports().front() / "summary.txt"); + CHECK(summary.find("test.spinning") != std::string::npos); + CHECK(summary.find("verdict: busy") != std::string::npos); + } + + TEST(AProcessPauseIsNotCountedAsAHang) { + TempDirectory directory("paused"); + std::atomic clock{ 0 }; + HangSettings settings = fastSettings(directory.path); + settings.thresholdMs = 2000; + settings.processPauseMs = 1000; + settings.clockNs = [&clock]() { return clock.load(); }; + HangWatchdog watchdog(settings, testTrail()); + watchdog.enterPhase(UiPhase::Draw); + + const auto step = [&](int64_t milliseconds) { + const uint64_t seen = watchdog.inspections(); + clock.fetch_add(milliseconds * 1000000); + REQUIRE CHECK(waitUntil([&]() { return watchdog.inspections() > seen; })); + }; + step(100); + step(30000); + step(100); + CHECK_EQUAL(0u, watchdog.reportsWritten()); + + for (int tick = 0; tick < 25; tick++) { + step(100); + } + CHECK_EQUAL(1u, watchdog.reportsWritten()); + watchdog.enterPhase(UiPhase::Idle); + } + + TEST(SummaryIsWrittenBeforeThreadsAreSampled) { + TempDirectory directory("summary-first"); + HangSettings settings = fastSettings(directory.path); + std::filesystem::path seenReport; + bool summaryExisted = false; + HangWatchdog watchdog(settings, testTrail()); + watchdog.addReportFileSource([&]() { + seenReport = watchdog.reports().empty() ? std::filesystem::path() : watchdog.reports().front(); + summaryExisted = !seenReport.empty() && std::filesystem::exists(seenReport / "summary.txt"); + return std::vector{}; + }); + watchdog.enterPhase(UiPhase::Present); + REQUIRE CHECK(waitUntil([&]() { return watchdog.reportsWritten() > 0; })); + watchdog.enterPhase(UiPhase::Idle); + CHECK(summaryExisted); + const std::string summary = readFile(seenReport / "summary.txt"); + CHECK(summary.find("debugger attached no") != std::string::npos); + } + + TEST(InputEventsHandledInOnePhaseAreCounted) { + TempDirectory directory("input-loop"); + HangWatchdog watchdog(fastSettings(directory.path), testTrail()); + watchdog.enterPhase(UiPhase::Input); + { + TestScope inputLoop("test.input_loop"); + while (watchdog.reportsWritten() == 0 || !hasVerdict(watchdog.reports().front())) { + watchdog.noteInputEvents(); + } + } + watchdog.enterPhase(UiPhase::Idle); + const std::string summary = readFile(watchdog.reports().front() / "summary.txt"); + CHECK(summary.find("input events handled during this phase 0") == std::string::npos); + CHECK(summary.find("input events were handled without leaving the input phase") != std::string::npos); + } + + TEST(IdleNeverProducesAReport) { + TempDirectory directory("idle"); + HangWatchdog watchdog(fastSettings(directory.path), testTrail()); + watchdog.enterPhase(UiPhase::Idle); + Gate gate; + std::thread release([&]() { + const auto start = std::chrono::steady_clock::now(); + waitUntil([&]() { return std::chrono::steady_clock::now() - start > std::chrono::milliseconds(600); }); + gate.open(); + }); + gate.waitOpen(); + release.join(); + CHECK_EQUAL(0u, watchdog.reportsWritten()); + } + + TEST(ReportFileSourcesAddTheirOwnFiles) { + TempDirectory directory("reportFileSources"); + HangWatchdog watchdog(fastSettings(directory.path), testTrail()); + watchdog.addReportFileSource([]() { return std::vector{ { "engine.txt", "queue 3\n" } }; }); + watchdog.enterPhase(UiPhase::Input); + REQUIRE CHECK(waitUntil([&]() { return watchdog.reportsWritten() > 0; })); + watchdog.enterPhase(UiPhase::Idle); + CHECK_EQUAL(std::string("queue 3\n"), readFile(watchdog.reports().front() / "engine.txt")); + } + + TEST(OnlyTheNewestReportsAreKept) { + TempDirectory directory("retention"); + std::filesystem::create_directories(directory.path); + for (int index = 0; index < 5; index++) { + std::filesystem::create_directories(directory.path / ("old-" + std::to_string(index))); + } + HangSettings settings = fastSettings(directory.path); + settings.keptReports = 2; + HangWatchdog watchdog(settings, testTrail()); + CHECK_EQUAL(2u, watchdog.reports().size()); + } + + TEST(DirectoryIsTheApplicationsHangFolderUnlessOverridden) { + const std::filesystem::path application = "/home/someone/sketch2d/hangs"; + CHECK(HangWatchdog::directoryFor(application) == application); + setenv("MODULES_HANG_DIR", "/tmp/custom-hangs", 1); + CHECK(HangWatchdog::directoryFor(application) == std::filesystem::path("/tmp/custom-hangs")); + unsetenv("MODULES_HANG_DIR"); + } +} diff --git a/GraphicApplication/tests/ProfilerTests.cpp b/GraphicApplication/tests/ProfilerTests.cpp new file mode 100644 index 00000000..75667094 --- /dev/null +++ b/GraphicApplication/tests/ProfilerTests.cpp @@ -0,0 +1,138 @@ +#include "Profiler.hpp" + +#include "UnitTest++/UnitTest++.h" + +#include + +using namespace tp; + +namespace { + void burnCpu(double milliseconds) { + const double end = Profiler::threadCpuMs() + milliseconds; + volatile double sink = 0; + while (Profiler::threadCpuMs() < end) { + sink = sink + std::sqrt(sink + 1.0); + } + } + + struct ProfilerScope { + Profiler profiler; + ProfilerScope() { profiler.setEnabled(true); } + }; +} + +SUITE(ProfileStatistics) { + TEST(SummaryReportsPercentilesOfUnsortedValues) { + std::vector values; + for (int value = 100; value >= 1; value--) { + values.push_back(value); + } + + const ProfileStatistics statistics = summarize(values); + CHECK_EQUAL(100u, statistics.samples); + CHECK_CLOSE(50.5, statistics.mean, 1e-9); + CHECK_CLOSE(50.5, statistics.median, 1e-9); + CHECK_CLOSE(95.05, statistics.p95, 1e-9); + CHECK_CLOSE(99.01, statistics.p99, 1e-9); + CHECK_EQUAL(1.0, statistics.minimum); + CHECK_EQUAL(100.0, statistics.maximum); + CHECK_EQUAL(5050.0, statistics.total); + } + + TEST(EmptySummaryIsZero) { + const ProfileStatistics statistics = summarize({}); + CHECK_EQUAL(0u, statistics.samples); + CHECK_EQUAL(0.0, statistics.p99); + } +} + +SUITE(Profiler) { + TEST(DisabledProfilerRecordsNothing) { + Profiler profiler; + const ProfileId section = profiler.section("work"); + profiler.beginFrame(); + profiler.addSectionTime(section, 5); + profiler.endFrame(); + CHECK(profiler.frames().empty()); + } + + TEST(FramesAccumulateSectionsCallsAndCounters) { + ProfilerScope scope; + Profiler& profiler = scope.profiler; + const ProfileId dabs = profiler.counter("dabs"); + const ProfileId stamp = profiler.section("stamp"); + + profiler.beginFrame(); + profiler.addSectionTime(stamp, 1.5); + profiler.addSectionTime(stamp, 0.5); + profiler.addCounter(dabs, 3); + profiler.endFrame(); + profiler.beginFrame(); + profiler.addCounter(dabs, 4); + profiler.endFrame(); + + REQUIRE CHECK_EQUAL(2u, profiler.frames().size()); + CHECK_EQUAL(2.0, profiler.frames()[0].sectionMs[stamp]); + CHECK_EQUAL(2u, profiler.frames()[0].sectionCalls[stamp]); + CHECK_EQUAL(0.0, profiler.frames()[1].sectionMs[stamp]); + CHECK_EQUAL(7.0, profiler.counterTotal("dabs")); + CHECK_EQUAL(2u, profiler.sectionCallTotal("stamp")); + CHECK_EQUAL(1u, profiler.frames()[1].index); + } + + TEST(WorkOutsideAFrameIsIgnored) { + ProfilerScope scope; + const ProfileId dabs = scope.profiler.counter("dabs"); + scope.profiler.addCounter(dabs, 9); + scope.profiler.beginFrame(); + scope.profiler.endFrame(); + CHECK_EQUAL(0.0, scope.profiler.counterTotal("dabs")); + } + + TEST(SectionsInternByName) { + Profiler profiler; + const ProfileId first = profiler.section("draw"); + CHECK_EQUAL(first, profiler.section("draw")); + CHECK(first != profiler.section("process")); + } + + TEST(FrameMeasuresWallAndThreadCpuTime) { + ProfilerScope scope; + scope.profiler.beginFrame(); + burnCpu(4); + scope.profiler.endFrame(); + const FrameProfile& frame = scope.profiler.frames().front(); + CHECK(frame.cpuMs >= 4.0); + CHECK(frame.wallMs >= frame.cpuMs * 0.9); + } + + TEST(ScopeTimesItsSectionAndRecordsTheTrail) { + ProfilerScope scope; + Profiler& profiler = scope.profiler; + profiler.trail().trackCallingThread(); + profiler.beginFrame(); + { + const ProfileScope timed(profiler, "timed scope"); + REQUIRE CHECK_EQUAL(1u, profiler.trail().snapshot().size()); + CHECK_EQUAL(std::string("timed scope"), profiler.trail().snapshot()[0]); + burnCpu(2); + } + profiler.endFrame(); + profiler.trail().stopTracking(); + + CHECK(profiler.trail().snapshot().empty()); + CHECK(profiler.sectionTotal("timed scope") >= 2.0); + CHECK_EQUAL(1u, profiler.sectionCallTotal("timed scope")); + } + + TEST(DurationMeasuredElsewhereUsesTheSectionChannel) { + ProfilerScope scope; + const ProfileId gpu = scope.profiler.section("gpu frame"); + scope.profiler.beginFrame(); + scope.profiler.addSectionTime(gpu, 0.75); + scope.profiler.endFrame(); + REQUIRE CHECK_EQUAL(1u, scope.profiler.sectionSeries("gpu frame").size()); + CHECK_EQUAL(0.75, scope.profiler.sectionSeries("gpu frame")[0]); + CHECK_EQUAL(1u, scope.profiler.sectionCallTotal("gpu frame")); + } +} -- 2.49.1 From 711793ffd710a24c65fc64b6e7dd98c5ea34f6b2 Mon Sep 17 00:00:00 2001 From: agent Date: Thu, 29 Oct 2026 05:24:06 +0300 Subject: [PATCH 3/5] GraphicApplication: GPU device, window surfaces, window and platform backends gpu/ replaces the OpenGL path and is split by lifetime: - GpuDevice is the instance, device, graphics queue, command pool, one-shot uploads and render-target registry, shared by every window. - WindowSurface is one window's surface, swapchain, render pass, depth-stencil and frames in flight. beginFrame() returns a GpuFrame (command buffer, frame slot, extent), the narrow view renderers record into. Render-finished semaphores are per swapchain image, Vulkan results are checked and failures throw GpuError. backends/contract/ is the only thing a backend and the window share. PlatformWindow is what a backend implements; it knows no coalescer or GPU object. PlatformWindowHost is all a backend may call back: rawInput(), onContentResized(), onSurfaceAvailable() and onSurfaceLost(), so the window drives the GPU surface. Desktop-only manipulation is the optional DesktopWindowControls interface. A backend also defines its surface extensions, its resource directory and createPlatformWindow(), which receives a typed PlatformContext. - backends/wayland/ keeps xdg configure handling to itself and acknowledges it when the frame loop applies it. The seat is split per device (keyboard, pointer, tablet, text input, clipboard); clipboard contents are read asynchronously on the event loop. evdev key codes and the touchpad pinch converter live here. Resources resolve next to the executable. - backends/android/ runs on native_app_glue and finishes the activity when the application returns or fails. window/ owns the platform window, its WindowSurface, the coalescer and the input queue, and exposes roles: WindowEventLoop, WindowInput (state, injection, raw tap, delivery policies, the one place input is muted), WindowControls (with desktop() for the optional controls) and WindowServices. Window's public header names no backend. --- .../backends/android/private/AndroidEntry.cpp | 45 ++ .../backends/android/private/AndroidKeys.cpp | 130 ++++ .../backends/android/private/AndroidKeys.hpp | 11 + .../android/private/AndroidPlatform.cpp | 301 ++++++++ .../android/private/AndroidPlatform.hpp | 18 + .../android/private/AndroidWindow.cpp | 505 +++++++++++++ .../android/private/AndroidWindow.hpp | 91 +++ .../backends/android/public/AndroidEntry.hpp | 13 + .../contract/private/PlatformInfo.cpp | 50 ++ .../backends/contract/private/WindowFrame.cpp | 94 +++ .../contract/public/DesktopWindowControls.hpp | 19 + .../contract/public/PlatformContext.hpp | 11 + .../backends/contract/public/PlatformInfo.hpp | 47 ++ .../contract/public/PlatformWindow.hpp | 73 ++ .../backends/contract/public/WindowFrame.hpp | 42 ++ .../backends/contract/public/WindowTypes.hpp | 40 ++ .../wayland/private/LinuxInputCodes.cpp | 153 ++++ .../wayland/private/LinuxInputCodes.hpp | 13 + .../wayland/private/PointerPinchConverter.cpp | 38 + .../wayland/private/PointerPinchConverter.hpp | 21 + .../wayland/private/WaylandClipboard.cpp | 260 +++++++ .../wayland/private/WaylandClipboard.hpp | 66 ++ .../wayland/private/WaylandConnection.cpp | 193 +++++ .../wayland/private/WaylandConnection.hpp | 72 ++ .../wayland/private/WaylandKeyboard.cpp | 262 +++++++ .../wayland/private/WaylandKeyboard.hpp | 54 ++ .../wayland/private/WaylandPointer.cpp | 231 ++++++ .../wayland/private/WaylandPointer.hpp | 59 ++ .../backends/wayland/private/WaylandSeat.cpp | 113 +++ .../backends/wayland/private/WaylandSeat.hpp | 61 ++ .../wayland/private/WaylandSeatContext.hpp | 44 ++ .../wayland/private/WaylandTablet.cpp | 210 ++++++ .../wayland/private/WaylandTablet.hpp | 60 ++ .../wayland/private/WaylandTextInput.cpp | 95 +++ .../wayland/private/WaylandTextInput.hpp | 41 ++ .../wayland/private/WaylandWindow.cpp | 665 ++++++++++++++++++ .../wayland/private/WaylandWindow.hpp | 122 ++++ .../src/gpu/private/GpuDevice.cpp | 226 ++++++ .../src/gpu/private/GpuTimer.cpp | 96 +++ .../src/gpu/private/GpuTimer.hpp | 43 ++ .../src/gpu/private/RenderTargetRegistry.cpp | 23 + .../src/gpu/private/WindowSurface.cpp | 571 +++++++++++++++ .../src/gpu/public/GpuDevice.hpp | 53 ++ .../src/gpu/public/GpuError.hpp | 28 + .../src/gpu/public/RenderTargetRegistry.hpp | 29 + .../src/gpu/public/WindowSurface.hpp | 125 ++++ .../src/window/private/Window.cpp | 64 ++ .../src/window/private/WindowContext.hpp | 36 + .../src/window/private/WindowControls.cpp | 19 + .../src/window/private/WindowEventLoop.cpp | 45 ++ .../src/window/private/WindowInput.cpp | 27 + .../src/window/private/WindowServices.cpp | 19 + .../src/window/public/Window.hpp | 60 ++ .../src/window/public/WindowControls.hpp | 28 + .../src/window/public/WindowEventLoop.hpp | 36 + .../src/window/public/WindowInput.hpp | 27 + .../src/window/public/WindowServices.hpp | 28 + .../tests/LinuxInputCodesTests.cpp | 39 + .../tests/PlatformInfoTests.cpp | 47 ++ .../tests/PointerPinchConverterTests.cpp | 57 ++ .../tests/RenderTargetRegistryTests.cpp | 39 + GraphicApplication/tests/WindowFrameTests.cpp | 106 +++ 62 files changed, 6194 insertions(+) create mode 100644 GraphicApplication/src/backends/android/private/AndroidEntry.cpp create mode 100644 GraphicApplication/src/backends/android/private/AndroidKeys.cpp create mode 100644 GraphicApplication/src/backends/android/private/AndroidKeys.hpp create mode 100644 GraphicApplication/src/backends/android/private/AndroidPlatform.cpp create mode 100644 GraphicApplication/src/backends/android/private/AndroidPlatform.hpp create mode 100644 GraphicApplication/src/backends/android/private/AndroidWindow.cpp create mode 100644 GraphicApplication/src/backends/android/private/AndroidWindow.hpp create mode 100644 GraphicApplication/src/backends/android/public/AndroidEntry.hpp create mode 100644 GraphicApplication/src/backends/contract/private/PlatformInfo.cpp create mode 100644 GraphicApplication/src/backends/contract/private/WindowFrame.cpp create mode 100644 GraphicApplication/src/backends/contract/public/DesktopWindowControls.hpp create mode 100644 GraphicApplication/src/backends/contract/public/PlatformContext.hpp create mode 100644 GraphicApplication/src/backends/contract/public/PlatformInfo.hpp create mode 100644 GraphicApplication/src/backends/contract/public/PlatformWindow.hpp create mode 100644 GraphicApplication/src/backends/contract/public/WindowFrame.hpp create mode 100644 GraphicApplication/src/backends/contract/public/WindowTypes.hpp create mode 100644 GraphicApplication/src/backends/wayland/private/LinuxInputCodes.cpp create mode 100644 GraphicApplication/src/backends/wayland/private/LinuxInputCodes.hpp create mode 100644 GraphicApplication/src/backends/wayland/private/PointerPinchConverter.cpp create mode 100644 GraphicApplication/src/backends/wayland/private/PointerPinchConverter.hpp create mode 100644 GraphicApplication/src/backends/wayland/private/WaylandClipboard.cpp create mode 100644 GraphicApplication/src/backends/wayland/private/WaylandClipboard.hpp create mode 100644 GraphicApplication/src/backends/wayland/private/WaylandConnection.cpp create mode 100644 GraphicApplication/src/backends/wayland/private/WaylandConnection.hpp create mode 100644 GraphicApplication/src/backends/wayland/private/WaylandKeyboard.cpp create mode 100644 GraphicApplication/src/backends/wayland/private/WaylandKeyboard.hpp create mode 100644 GraphicApplication/src/backends/wayland/private/WaylandPointer.cpp create mode 100644 GraphicApplication/src/backends/wayland/private/WaylandPointer.hpp create mode 100644 GraphicApplication/src/backends/wayland/private/WaylandSeat.cpp create mode 100644 GraphicApplication/src/backends/wayland/private/WaylandSeat.hpp create mode 100644 GraphicApplication/src/backends/wayland/private/WaylandSeatContext.hpp create mode 100644 GraphicApplication/src/backends/wayland/private/WaylandTablet.cpp create mode 100644 GraphicApplication/src/backends/wayland/private/WaylandTablet.hpp create mode 100644 GraphicApplication/src/backends/wayland/private/WaylandTextInput.cpp create mode 100644 GraphicApplication/src/backends/wayland/private/WaylandTextInput.hpp create mode 100644 GraphicApplication/src/backends/wayland/private/WaylandWindow.cpp create mode 100644 GraphicApplication/src/backends/wayland/private/WaylandWindow.hpp create mode 100644 GraphicApplication/src/gpu/private/GpuDevice.cpp create mode 100644 GraphicApplication/src/gpu/private/GpuTimer.cpp create mode 100644 GraphicApplication/src/gpu/private/GpuTimer.hpp create mode 100644 GraphicApplication/src/gpu/private/RenderTargetRegistry.cpp create mode 100644 GraphicApplication/src/gpu/private/WindowSurface.cpp create mode 100644 GraphicApplication/src/gpu/public/GpuDevice.hpp create mode 100644 GraphicApplication/src/gpu/public/GpuError.hpp create mode 100644 GraphicApplication/src/gpu/public/RenderTargetRegistry.hpp create mode 100644 GraphicApplication/src/gpu/public/WindowSurface.hpp create mode 100644 GraphicApplication/src/window/private/Window.cpp create mode 100644 GraphicApplication/src/window/private/WindowContext.hpp create mode 100644 GraphicApplication/src/window/private/WindowControls.cpp create mode 100644 GraphicApplication/src/window/private/WindowEventLoop.cpp create mode 100644 GraphicApplication/src/window/private/WindowInput.cpp create mode 100644 GraphicApplication/src/window/private/WindowServices.cpp create mode 100644 GraphicApplication/src/window/public/Window.hpp create mode 100644 GraphicApplication/src/window/public/WindowControls.hpp create mode 100644 GraphicApplication/src/window/public/WindowEventLoop.hpp create mode 100644 GraphicApplication/src/window/public/WindowInput.hpp create mode 100644 GraphicApplication/src/window/public/WindowServices.hpp create mode 100644 GraphicApplication/tests/LinuxInputCodesTests.cpp create mode 100644 GraphicApplication/tests/PlatformInfoTests.cpp create mode 100644 GraphicApplication/tests/PointerPinchConverterTests.cpp create mode 100644 GraphicApplication/tests/RenderTargetRegistryTests.cpp create mode 100644 GraphicApplication/tests/WindowFrameTests.cpp diff --git a/GraphicApplication/src/backends/android/private/AndroidEntry.cpp b/GraphicApplication/src/backends/android/private/AndroidEntry.cpp new file mode 100644 index 00000000..9783d4ae --- /dev/null +++ b/GraphicApplication/src/backends/android/private/AndroidEntry.cpp @@ -0,0 +1,45 @@ +#include "AndroidEntry.hpp" + +#include "AndroidPlatform.hpp" + +#include +#include +#include + +#include +#include + +namespace tp { + + namespace { + void drainUntilDestroyed(android_app* app) { + while (!app->destroyRequested) { + int events = 0; + android_poll_source* source = nullptr; + if (ALooper_pollOnce(-1, nullptr, &events, (void**) &source) >= 0 && source) { + source->process(app, source); + } + } + } + } + + void runAndroidApplication( + android_app* app, AndroidMain main, const char* const* assetDirectories, int assetDirectoryCount + ) { + for (int index = 0; index < assetDirectoryCount; index++) { + android::extractAssets(*app, assetDirectories[index]); + } + setenv("HOME", app->activity->internalDataPath, 1); + + try { + main(app); + } catch (const std::exception& error) { + __android_log_print(ANDROID_LOG_FATAL, "modules", "application failed: %s", error.what()); + } + + if (!app->destroyRequested) { + ANativeActivity_finish(app->activity); + } + drainUntilDestroyed(app); + } +} diff --git a/GraphicApplication/src/backends/android/private/AndroidKeys.cpp b/GraphicApplication/src/backends/android/private/AndroidKeys.cpp new file mode 100644 index 00000000..c15c4bd3 --- /dev/null +++ b/GraphicApplication/src/backends/android/private/AndroidKeys.cpp @@ -0,0 +1,130 @@ +#include "AndroidKeys.hpp" + +#include + +#include + +namespace tp::android { + + namespace { + const std::unordered_map& keyCodes() { + static const std::unordered_map keys = { + { AKEYCODE_SPACE, Key::Space }, + { AKEYCODE_APOSTROPHE, Key::Apostrophe }, + { AKEYCODE_COMMA, Key::Comma }, + { AKEYCODE_MINUS, Key::Minus }, + { AKEYCODE_PERIOD, Key::Period }, + { AKEYCODE_SLASH, Key::Slash }, + { AKEYCODE_0, Key::Digit0 }, + { AKEYCODE_1, Key::Digit1 }, + { AKEYCODE_2, Key::Digit2 }, + { AKEYCODE_3, Key::Digit3 }, + { AKEYCODE_4, Key::Digit4 }, + { AKEYCODE_5, Key::Digit5 }, + { AKEYCODE_6, Key::Digit6 }, + { AKEYCODE_7, Key::Digit7 }, + { AKEYCODE_8, Key::Digit8 }, + { AKEYCODE_9, Key::Digit9 }, + { AKEYCODE_SEMICOLON, Key::Semicolon }, + { AKEYCODE_EQUALS, Key::Equal }, + { AKEYCODE_A, Key::A }, + { AKEYCODE_B, Key::B }, + { AKEYCODE_C, Key::C }, + { AKEYCODE_D, Key::D }, + { AKEYCODE_E, Key::E }, + { AKEYCODE_F, Key::F }, + { AKEYCODE_G, Key::G }, + { AKEYCODE_H, Key::H }, + { AKEYCODE_I, Key::I }, + { AKEYCODE_J, Key::J }, + { AKEYCODE_K, Key::K }, + { AKEYCODE_L, Key::L }, + { AKEYCODE_M, Key::M }, + { AKEYCODE_N, Key::N }, + { AKEYCODE_O, Key::O }, + { AKEYCODE_P, Key::P }, + { AKEYCODE_Q, Key::Q }, + { AKEYCODE_R, Key::R }, + { AKEYCODE_S, Key::S }, + { AKEYCODE_T, Key::T }, + { AKEYCODE_U, Key::U }, + { AKEYCODE_V, Key::V }, + { AKEYCODE_W, Key::W }, + { AKEYCODE_X, Key::X }, + { AKEYCODE_Y, Key::Y }, + { AKEYCODE_Z, Key::Z }, + { AKEYCODE_LEFT_BRACKET, Key::LeftBracket }, + { AKEYCODE_BACKSLASH, Key::Backslash }, + { AKEYCODE_RIGHT_BRACKET, Key::RightBracket }, + { AKEYCODE_GRAVE, Key::GraveAccent }, + { AKEYCODE_ESCAPE, Key::Escape }, + { AKEYCODE_ENTER, Key::Enter }, + { AKEYCODE_TAB, Key::Tab }, + { AKEYCODE_DEL, Key::Backspace }, + { AKEYCODE_INSERT, Key::Insert }, + { AKEYCODE_FORWARD_DEL, Key::Delete }, + { AKEYCODE_DPAD_RIGHT, Key::Right }, + { AKEYCODE_DPAD_LEFT, Key::Left }, + { AKEYCODE_DPAD_DOWN, Key::Down }, + { AKEYCODE_DPAD_UP, Key::Up }, + { AKEYCODE_PAGE_UP, Key::PageUp }, + { AKEYCODE_PAGE_DOWN, Key::PageDown }, + { AKEYCODE_MOVE_HOME, Key::Home }, + { AKEYCODE_MOVE_END, Key::End }, + { AKEYCODE_CAPS_LOCK, Key::CapsLock }, + { AKEYCODE_SCROLL_LOCK, Key::ScrollLock }, + { AKEYCODE_NUM_LOCK, Key::NumLock }, + { AKEYCODE_SYSRQ, Key::PrintScreen }, + { AKEYCODE_BREAK, Key::Pause }, + { AKEYCODE_F1, Key::F1 }, + { AKEYCODE_F2, Key::F2 }, + { AKEYCODE_F3, Key::F3 }, + { AKEYCODE_F4, Key::F4 }, + { AKEYCODE_F5, Key::F5 }, + { AKEYCODE_F6, Key::F6 }, + { AKEYCODE_F7, Key::F7 }, + { AKEYCODE_F8, Key::F8 }, + { AKEYCODE_F9, Key::F9 }, + { AKEYCODE_F10, Key::F10 }, + { AKEYCODE_F11, Key::F11 }, + { AKEYCODE_F12, Key::F12 }, + { AKEYCODE_NUMPAD_0, Key::Keypad0 }, + { AKEYCODE_NUMPAD_1, Key::Keypad1 }, + { AKEYCODE_NUMPAD_2, Key::Keypad2 }, + { AKEYCODE_NUMPAD_3, Key::Keypad3 }, + { AKEYCODE_NUMPAD_4, Key::Keypad4 }, + { AKEYCODE_NUMPAD_5, Key::Keypad5 }, + { AKEYCODE_NUMPAD_6, Key::Keypad6 }, + { AKEYCODE_NUMPAD_7, Key::Keypad7 }, + { AKEYCODE_NUMPAD_8, Key::Keypad8 }, + { AKEYCODE_NUMPAD_9, Key::Keypad9 }, + { AKEYCODE_NUMPAD_DOT, Key::KeypadDecimal }, + { AKEYCODE_NUMPAD_DIVIDE, Key::KeypadDivide }, + { AKEYCODE_NUMPAD_MULTIPLY, Key::KeypadMultiply }, + { AKEYCODE_NUMPAD_SUBTRACT, Key::KeypadSubtract }, + { AKEYCODE_NUMPAD_ADD, Key::KeypadAdd }, + { AKEYCODE_NUMPAD_ENTER, Key::KeypadEnter }, + { AKEYCODE_NUMPAD_EQUALS, Key::KeypadEqual }, + { AKEYCODE_SHIFT_LEFT, Key::LeftShift }, + { AKEYCODE_CTRL_LEFT, Key::LeftControl }, + { AKEYCODE_ALT_LEFT, Key::LeftAlt }, + { AKEYCODE_META_LEFT, Key::LeftSuper }, + { AKEYCODE_SHIFT_RIGHT, Key::RightShift }, + { AKEYCODE_CTRL_RIGHT, Key::RightControl }, + { AKEYCODE_ALT_RIGHT, Key::RightAlt }, + { AKEYCODE_META_RIGHT, Key::RightSuper }, + { AKEYCODE_MENU, Key::Menu }, + }; + return keys; + } + } + + std::optional keyFromAndroid(int32_t keyCode) { + const auto& keys = keyCodes(); + const auto found = keys.find(keyCode); + if (found == keys.end()) { + return std::nullopt; + } + return found->second; + } +} diff --git a/GraphicApplication/src/backends/android/private/AndroidKeys.hpp b/GraphicApplication/src/backends/android/private/AndroidKeys.hpp new file mode 100644 index 00000000..89952ab6 --- /dev/null +++ b/GraphicApplication/src/backends/android/private/AndroidKeys.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include "InputCodes.hpp" + +#include +#include + +namespace tp::android { + + [[nodiscard]] std::optional keyFromAndroid(int32_t keyCode); +} diff --git a/GraphicApplication/src/backends/android/private/AndroidPlatform.cpp b/GraphicApplication/src/backends/android/private/AndroidPlatform.cpp new file mode 100644 index 00000000..625a961a --- /dev/null +++ b/GraphicApplication/src/backends/android/private/AndroidPlatform.cpp @@ -0,0 +1,301 @@ +#include "AndroidPlatform.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace tp::android { + + namespace { + + struct AttachedEnv { + explicit AttachedEnv(ANativeActivity* activity) : + vm(activity->vm) { + if (vm->GetEnv((void**) &env, JNI_VERSION_1_6) == JNI_EDETACHED) { + vm->AttachCurrentThread(&env, nullptr); + attached = true; + } + } + + ~AttachedEnv() { + if (attached) { + vm->DetachCurrentThread(); + } + } + + JavaVM* vm; + JNIEnv* env = nullptr; + bool attached = false; + }; + + void appendUtf8(std::string& out, uint32_t codepoint) { + if (codepoint < 0x80) { + out += (char) codepoint; + } else if (codepoint < 0x800) { + out += (char) (0xC0 | (codepoint >> 6)); + out += (char) (0x80 | (codepoint & 0x3F)); + } else if (codepoint < 0x10000) { + out += (char) (0xE0 | (codepoint >> 12)); + out += (char) (0x80 | ((codepoint >> 6) & 0x3F)); + out += (char) (0x80 | (codepoint & 0x3F)); + } else { + out += (char) (0xF0 | (codepoint >> 18)); + out += (char) (0x80 | ((codepoint >> 12) & 0x3F)); + out += (char) (0x80 | ((codepoint >> 6) & 0x3F)); + out += (char) (0x80 | (codepoint & 0x3F)); + } + } + } + + std::string unicodeText(android_app& app, const AInputEvent* event) { + AttachedEnv attached(app.activity); + JNIEnv* env = attached.env; + if (!env) { + return {}; + } + + jclass keyEventClass = env->FindClass("android/view/KeyEvent"); + jmethodID constructor = env->GetMethodID(keyEventClass, "", "(JJIIIIIIII)V"); + jmethodID getUnicodeChar = env->GetMethodID(keyEventClass, "getUnicodeChar", "(I)I"); + jobject keyEvent = env->NewObject( + keyEventClass, + constructor, + (jlong) AKeyEvent_getDownTime(event), + (jlong) AKeyEvent_getEventTime(event), + (jint) AKeyEvent_getAction(event), + (jint) AKeyEvent_getKeyCode(event), + (jint) AKeyEvent_getRepeatCount(event), + (jint) AKeyEvent_getMetaState(event), + (jint) AInputEvent_getDeviceId(event), + (jint) AKeyEvent_getScanCode(event), + (jint) AKeyEvent_getFlags(event), + (jint) AInputEvent_getSource(event) + ); + const jint codepoint = env->CallIntMethod(keyEvent, getUnicodeChar, (jint) AKeyEvent_getMetaState(event)); + env->DeleteLocalRef(keyEvent); + env->DeleteLocalRef(keyEventClass); + + std::string text; + if (codepoint > 0 && codepoint >= 0x20 && codepoint != 0x7F) { + appendUtf8(text, (uint32_t) codepoint); + } + return text; + } + + void setSoftKeyboardVisible(android_app& app, bool visible) { + AttachedEnv attached(app.activity); + JNIEnv* env = attached.env; + if (!env) { + return; + } + + jobject activity = app.activity->clazz; + jclass activityClass = env->GetObjectClass(activity); + jclass contextClass = env->FindClass("android/content/Context"); + jfieldID serviceField = env->GetStaticFieldID(contextClass, "INPUT_METHOD_SERVICE", "Ljava/lang/String;"); + jobject serviceName = env->GetStaticObjectField(contextClass, serviceField); + jmethodID getSystemService = + env->GetMethodID(activityClass, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;"); + jobject inputManager = env->CallObjectMethod(activity, getSystemService, serviceName); + + jmethodID getWindow = env->GetMethodID(activityClass, "getWindow", "()Landroid/view/Window;"); + jobject window = env->CallObjectMethod(activity, getWindow); + jclass windowClass = env->FindClass("android/view/Window"); + jmethodID getDecorView = env->GetMethodID(windowClass, "getDecorView", "()Landroid/view/View;"); + jobject decorView = env->CallObjectMethod(window, getDecorView); + + jclass managerClass = env->FindClass("android/view/inputmethod/InputMethodManager"); + if (visible) { + jmethodID show = env->GetMethodID(managerClass, "showSoftInput", "(Landroid/view/View;I)Z"); + env->CallBooleanMethod(inputManager, show, decorView, 0); + } else { + jclass viewClass = env->FindClass("android/view/View"); + jmethodID getToken = env->GetMethodID(viewClass, "getWindowToken", "()Landroid/os/IBinder;"); + jobject token = env->CallObjectMethod(decorView, getToken); + jmethodID hide = env->GetMethodID(managerClass, "hideSoftInputFromWindow", "(Landroid/os/IBinder;I)Z"); + env->CallBooleanMethod(inputManager, hide, token, 0); + env->DeleteLocalRef(token); + env->DeleteLocalRef(viewClass); + } + if (env->ExceptionCheck()) { + env->ExceptionClear(); + } + + env->DeleteLocalRef(managerClass); + env->DeleteLocalRef(decorView); + env->DeleteLocalRef(windowClass); + env->DeleteLocalRef(window); + env->DeleteLocalRef(inputManager); + env->DeleteLocalRef(serviceName); + env->DeleteLocalRef(contextClass); + env->DeleteLocalRef(activityClass); + } + + namespace { + struct InsetEdges { + jobject bars = nullptr; + jobject gestures = nullptr; + }; + + InsetEdges typedInsetEdges(JNIEnv* env, jobject insets, jclass insetsClass) { + jclass typeClass = env->FindClass("android/view/WindowInsets$Type"); + const auto typeMask = [&](const char* name) { + return env->CallStaticIntMethod(typeClass, env->GetStaticMethodID(typeClass, name, "()I")); + }; + const jint bars = typeMask("systemBars") | typeMask("displayCutout"); + const jint gestures = typeMask("mandatorySystemGestures"); + env->DeleteLocalRef(typeClass); + + jmethodID getInsetsOf = env->GetMethodID(insetsClass, "getInsets", "(I)Landroid/graphics/Insets;"); + return { env->CallObjectMethod(insets, getInsetsOf, bars), env->CallObjectMethod(insets, getInsetsOf, gestures) }; + } + + InsetEdges legacyInsetEdges(JNIEnv* env, jobject insets, jclass insetsClass) { + const auto edgesOf = [&](const char* getter) { + return env->CallObjectMethod(insets, env->GetMethodID(insetsClass, getter, "()Landroid/graphics/Insets;")); + }; + return { edgesOf("getSystemWindowInsets"), edgesOf("getMandatorySystemGestureInsets") }; + } + } + + EdgeInsets systemInsets(android_app& app) { + AttachedEnv attached(app.activity); + JNIEnv* env = attached.env; + if (!env) { + return {}; + } + + EdgeInsets result; + jobject activity = app.activity->clazz; + jclass activityClass = env->GetObjectClass(activity); + jmethodID getWindow = env->GetMethodID(activityClass, "getWindow", "()Landroid/view/Window;"); + jobject window = env->CallObjectMethod(activity, getWindow); + jclass windowClass = env->FindClass("android/view/Window"); + jmethodID getDecorView = env->GetMethodID(windowClass, "getDecorView", "()Landroid/view/View;"); + jobject decorView = window ? env->CallObjectMethod(window, getDecorView) : nullptr; + jclass viewClass = env->FindClass("android/view/View"); + jmethodID getInsets = env->GetMethodID(viewClass, "getRootWindowInsets", "()Landroid/view/WindowInsets;"); + jobject insets = decorView ? env->CallObjectMethod(decorView, getInsets) : nullptr; + + if (insets) { + jclass insetsClass = env->FindClass("android/view/WindowInsets"); + const InsetEdges sources = android_get_device_api_level() >= 30 ? typedInsetEdges(env, insets, insetsClass) + : legacyInsetEdges(env, insets, insetsClass); + jclass edgesClass = env->FindClass("android/graphics/Insets"); + const auto edge = [&](jobject edges, const char* name) { + return edges ? (halnf) env->GetIntField(edges, env->GetFieldID(edgesClass, name, "I")) : 0.0f; + }; + const auto both = [&](const char* name) { + return std::max(edge(sources.bars, name), edge(sources.gestures, name)); + }; + result = { both("left"), both("top"), both("right"), both("bottom") }; + + env->DeleteLocalRef(edgesClass); + if (sources.gestures) { + env->DeleteLocalRef(sources.gestures); + } + if (sources.bars) { + env->DeleteLocalRef(sources.bars); + } + env->DeleteLocalRef(insetsClass); + env->DeleteLocalRef(insets); + } + if (env->ExceptionCheck()) { + env->ExceptionClear(); + } + + env->DeleteLocalRef(viewClass); + if (decorView) { + env->DeleteLocalRef(decorView); + } + env->DeleteLocalRef(windowClass); + if (window) { + env->DeleteLocalRef(window); + } + env->DeleteLocalRef(activityClass); + return result; + } + + bool openUrl(android_app& app, const std::string& url) { + AttachedEnv attached(app.activity); + JNIEnv* env = attached.env; + if (!env || url.empty()) { + return false; + } + + jclass uriClass = env->FindClass("android/net/Uri"); + jmethodID parse = env->GetStaticMethodID(uriClass, "parse", "(Ljava/lang/String;)Landroid/net/Uri;"); + jstring text = env->NewStringUTF(url.c_str()); + jobject uri = env->CallStaticObjectMethod(uriClass, parse, text); + + jclass intentClass = env->FindClass("android/content/Intent"); + jmethodID constructor = env->GetMethodID(intentClass, "", "(Ljava/lang/String;Landroid/net/Uri;)V"); + jstring action = env->NewStringUTF("android.intent.action.VIEW"); + jobject intent = env->NewObject(intentClass, constructor, action, uri); + jmethodID addFlags = env->GetMethodID(intentClass, "addFlags", "(I)Landroid/content/Intent;"); + constexpr jint kNewTask = 0x10000000; + env->DeleteLocalRef(env->CallObjectMethod(intent, addFlags, kNewTask)); + + jobject activity = app.activity->clazz; + jclass activityClass = env->GetObjectClass(activity); + jmethodID startActivity = env->GetMethodID(activityClass, "startActivity", "(Landroid/content/Intent;)V"); + env->CallVoidMethod(activity, startActivity, intent); + const bool failed = env->ExceptionCheck(); + if (failed) { + env->ExceptionClear(); + } + + env->DeleteLocalRef(activityClass); + env->DeleteLocalRef(intent); + env->DeleteLocalRef(action); + env->DeleteLocalRef(intentClass); + if (uri) { + env->DeleteLocalRef(uri); + } + env->DeleteLocalRef(text); + env->DeleteLocalRef(uriClass); + return !failed; + } + + namespace { + void + extractDirectory(AAssetManager* manager, const std::string& assetDirectory, const std::filesystem::path& target) { + AAssetDir* directory = AAssetManager_openDir(manager, assetDirectory.c_str()); + if (!directory) { + return; + } + std::filesystem::create_directories(target); + while (const char* name = AAssetDir_getNextFileName(directory)) { + const std::string assetPath = assetDirectory.empty() ? name : assetDirectory + "/" + name; + AAsset* asset = AAssetManager_open(manager, assetPath.c_str(), AASSET_MODE_STREAMING); + if (!asset) { + continue; + } + std::ofstream out(target / name, std::ios::binary | std::ios::trunc); + std::vector buffer(64 * 1024); + int read = 0; + while ((read = AAsset_read(asset, buffer.data(), buffer.size())) > 0) { + out.write(buffer.data(), read); + } + AAsset_close(asset); + } + AAssetDir_close(directory); + } + } + + void extractAssets(android_app& app, const char* directory) { + AAssetManager* manager = app.activity->assetManager; + const std::filesystem::path target = std::filesystem::path(app.activity->internalDataPath) / directory; + extractDirectory(manager, directory, target); + __android_log_print(ANDROID_LOG_INFO, "modules", "assets extracted to %s", target.c_str()); + } +} diff --git a/GraphicApplication/src/backends/android/private/AndroidPlatform.hpp b/GraphicApplication/src/backends/android/private/AndroidPlatform.hpp new file mode 100644 index 00000000..b54efa47 --- /dev/null +++ b/GraphicApplication/src/backends/android/private/AndroidPlatform.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include "PlatformInfo.hpp" + +#include + +struct android_app; +struct AInputEvent; + +namespace tp::android { + + [[nodiscard]] std::string unicodeText(android_app& app, const AInputEvent* event); + void setSoftKeyboardVisible(android_app& app, bool visible); + [[nodiscard]] EdgeInsets systemInsets(android_app& app); + bool openUrl(android_app& app, const std::string& url); + + void extractAssets(android_app& app, const char* directory); +} diff --git a/GraphicApplication/src/backends/android/private/AndroidWindow.cpp b/GraphicApplication/src/backends/android/private/AndroidWindow.cpp new file mode 100644 index 00000000..eadc73ab --- /dev/null +++ b/GraphicApplication/src/backends/android/private/AndroidWindow.cpp @@ -0,0 +1,505 @@ +#include "AndroidWindow.hpp" + +#include "AndroidKeys.hpp" +#include "AndroidPlatform.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace tp { + + namespace { + constexpr const char* kTag = "modules"; + constexpr halnf kReferenceDensity = 160.0f; + constexpr halnf kWheelStep = 1.0f; + + PointerDevice deviceFor(int32_t type) { + switch (type) { + case AMOTION_EVENT_TOOL_TYPE_STYLUS: + case AMOTION_EVENT_TOOL_TYPE_ERASER: return PointerDevice::Stylus; + case AMOTION_EVENT_TOOL_TYPE_FINGER: return PointerDevice::Finger; + default: return PointerDevice::Mouse; + } + } + + StylusTip tipFor(int32_t type) { + return type == AMOTION_EVENT_TOOL_TYPE_ERASER ? StylusTip::Eraser : StylusTip::Pen; + } + + std::optional buttonFor(int32_t androidButton) { + switch (androidButton) { + case AMOTION_EVENT_BUTTON_PRIMARY: return PointerButton::Left; + case AMOTION_EVENT_BUTTON_SECONDARY: return PointerButton::Right; + case AMOTION_EVENT_BUTTON_TERTIARY: return PointerButton::Middle; + case AMOTION_EVENT_BUTTON_BACK: return PointerButton::Back; + case AMOTION_EVENT_BUTTON_FORWARD: return PointerButton::Forward; + default: return std::nullopt; + } + } + } + + AndroidWindow::AndroidWindow(PlatformWindowHost& host, android_app& app) : + mHost(host), + mApp(app) { + mFrame.setTranslucent(false); + mFrame.setTiled(true); + + mApp.userData = this; + mApp.onAppCmd = onCommand; + mApp.onInputEvent = onInput; + + if (mApp.config) { + const int32_t density = AConfiguration_getDensity(mApp.config); + if (density > 0 && density != ACONFIGURATION_DENSITY_ANY) { + mScale = (halnf) density / kReferenceDensity; + } + } + + while (!mApp.window && !mApp.destroyRequested) { + pollOnce(-1); + } + if (mApp.window) { + attachWindow(mApp.window); + } + } + + AndroidWindow::~AndroidWindow() { + mApp.onAppCmd = nullptr; + mApp.onInputEvent = nullptr; + mApp.userData = nullptr; + } + + VkSurfaceKHR AndroidWindow::createSurface(VkInstance instance) const { + if (!mNativeWindow) { + return VK_NULL_HANDLE; + } + VkAndroidSurfaceCreateInfoKHR info{ + .sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR, + .window = mNativeWindow, + }; + VkSurfaceKHR surface = VK_NULL_HANDLE; + if (vkCreateAndroidSurfaceKHR(instance, &info, nullptr, &surface) != VK_SUCCESS) { + return VK_NULL_HANDLE; + } + return surface; + } + + VkExtent2D AndroidWindow::framebufferExtent() const { + if (!mNativeWindow) { + return { 0, 0 }; + } + return { (uint32_t) ANativeWindow_getWidth(mNativeWindow), (uint32_t) ANativeWindow_getHeight(mNativeWindow) }; + } + + void AndroidWindow::onTranslucencyResolved(bool) { mFrame.setTranslucent(false); } + + void AndroidWindow::updateGeometry() { + if (!mNativeWindow) { + return; + } + const halnf width = (halnf) ANativeWindow_getWidth(mNativeWindow) / mScale; + const halnf height = (halnf) ANativeWindow_getHeight(mNativeWindow) / mScale; + if (width <= 0 || height <= 0) { + return; + } + + const EdgeInsets pixels = android::systemInsets(mApp); + const EdgeInsets insets = { + pixels.left / mScale, pixels.top / mScale, pixels.right / mScale, pixels.bottom / mScale + }; + const bool sizeChanged = Vec2F(width, height) != mFrame.contentSize(); + if (!sizeChanged && insets == mSafeInsets) { + return; + } + + mSafeInsets = insets; + mFrame.setContentSize({ width, height }); + if (mConfigured) { + mHost.onContentResized(); + } + } + + void AndroidWindow::attachWindow(ANativeWindow* window) { + mNativeWindow = window; + updateGeometry(); + mHost.onSurfaceAvailable(); + __android_log_print( + ANDROID_LOG_INFO, + kTag, + "window %dx%d scale %.2f", + ANativeWindow_getWidth(window), + ANativeWindow_getHeight(window), + mScale + ); + } + + void AndroidWindow::detachWindow() { + mHost.onSurfaceLost(); + mNativeWindow = nullptr; + } + + void AndroidWindow::onCommand(android_app* app, int32_t command) { + if (auto* self = static_cast(app->userData)) { + self->handleCommand(command); + } + } + + int32_t AndroidWindow::onInput(android_app* app, AInputEvent* event) { + auto* self = static_cast(app->userData); + return self && self->handleInput(event) ? 1 : 0; + } + + void AndroidWindow::handleCommand(int32_t command) { + switch (command) { + case APP_CMD_INIT_WINDOW: + if (mApp.window) { + attachWindow(mApp.window); + } + break; + case APP_CMD_TERM_WINDOW: detachWindow(); break; + case APP_CMD_WINDOW_RESIZED: + case APP_CMD_CONFIG_CHANGED: + case APP_CMD_CONTENT_RECT_CHANGED: updateGeometry(); break; + case APP_CMD_GAINED_FOCUS: + mFocused = true; + input().post(KeyboardFocusChanged{ true }); + break; + case APP_CMD_LOST_FOCUS: + mFocused = false; + input().post(KeyboardFocusChanged{ false }); + break; + default: break; + } + } + + bool AndroidWindow::handleInput(AInputEvent* event) { + switch (AInputEvent_getType(event)) { + case AINPUT_EVENT_TYPE_MOTION: return handleMotion(event); + case AINPUT_EVENT_TYPE_KEY: return handleKey(event); + default: return false; + } + } + + bool AndroidWindow::handleMotion(AInputEvent* event) { + const int32_t action = AMotionEvent_getAction(event) & AMOTION_EVENT_ACTION_MASK; + const int32_t toolType = AMotionEvent_getToolType(event, 0); + if (toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS || toolType == AMOTION_EVENT_TOOL_TYPE_ERASER) { + mPenSeen = true; + } + if (toolType == AMOTION_EVENT_TOOL_TYPE_MOUSE) { + mMouseSeen = true; + } + InputSink& input = this->input(); + + if (toolType == AMOTION_EVENT_TOOL_TYPE_FINGER && handleTouchGesture(event, action)) { + return true; + } + + const auto position = [&](size_t history) -> Vec2F { + if (history == SIZE_MAX) { + return { AMotionEvent_getX(event, 0) / mScale, AMotionEvent_getY(event, 0) / mScale }; + } + return { AMotionEvent_getHistoricalX(event, 0, history) / mScale, + AMotionEvent_getHistoricalY(event, 0, history) / mScale }; + }; + const auto sample = [&](size_t history) { + PointerSample result; + result.device = deviceFor(toolType); + result.tip = tipFor(toolType); + const bool current = history == SIZE_MAX; + result.pressure = + current ? AMotionEvent_getPressure(event, 0) : AMotionEvent_getHistoricalPressure(event, 0, history); + const halnf tilt = current ? AMotionEvent_getAxisValue(event, AMOTION_EVENT_AXIS_TILT, 0) + : AMotionEvent_getHistoricalAxisValue(event, AMOTION_EVENT_AXIS_TILT, 0, history); + const halnf orientation = + current ? AMotionEvent_getOrientation(event, 0) : AMotionEvent_getHistoricalOrientation(event, 0, history); + result.tilt = { std::sin(orientation) * tilt, -std::cos(orientation) * tilt }; + result.rotation = orientation; + result.timeMs = + (uint32_t) ((current ? AMotionEvent_getEventTime(event) : AMotionEvent_getHistoricalEventTime(event, history)) / + 1000000); + if (!result.isStylus()) { + result.pressure = 1; + } else if (result.pressure > 0) { + mLastContactPressure = result.pressure; + } + return result; + }; + + switch (action) { + case AMOTION_EVENT_ACTION_DOWN: + case AMOTION_EVENT_ACTION_BUTTON_PRESS: + { + input.post(PointerSampled{ sample(SIZE_MAX) }); + input.post(PointerMoved{ position(SIZE_MAX) }); + if (action == AMOTION_EVENT_ACTION_BUTTON_PRESS) { + deliverButtonChanges(input, AMotionEvent_getButtonState(event)); + } else if ( + !mPrimaryHeld && (AMotionEvent_getButtonState(event) == 0 || toolType != AMOTION_EVENT_TOOL_TYPE_MOUSE) + ) { + mPrimaryHeld = true; + input.post(PointerButtonChanged{ PointerButton::Left, true }); + } + return true; + } + case AMOTION_EVENT_ACTION_MOVE: + case AMOTION_EVENT_ACTION_HOVER_MOVE: + { + const size_t history = AMotionEvent_getHistorySize(event); + for (size_t index = 0; index < history; index++) { + input.post(PointerSampled{ sample(index) }); + input.post(PointerMoved{ position(index) }); + } + input.post(PointerSampled{ sample(SIZE_MAX) }); + input.post(PointerMoved{ position(SIZE_MAX) }); + return true; + } + case AMOTION_EVENT_ACTION_UP: + case AMOTION_EVENT_ACTION_CANCEL: + case AMOTION_EVENT_ACTION_BUTTON_RELEASE: + { + PointerSample released = sample(SIZE_MAX); + if (released.isStylus()) { + released.pressure = mLastContactPressure; + } + input.post(PointerSampled{ released }); + input.post(PointerMoved{ position(SIZE_MAX) }); + if (action == AMOTION_EVENT_ACTION_BUTTON_RELEASE) { + deliverButtonChanges(input, AMotionEvent_getButtonState(event)); + } else if (mPrimaryHeld) { + mPrimaryHeld = false; + input.post(PointerButtonChanged{ PointerButton::Left, false }); + } + if (toolType == AMOTION_EVENT_TOOL_TYPE_FINGER) { + input.post(PointerLeft{}); + } + return true; + } + case AMOTION_EVENT_ACTION_HOVER_EXIT: input.post(PointerLeft{}); return true; + case AMOTION_EVENT_ACTION_SCROLL: + { + const Vec2F delta = { -AMotionEvent_getAxisValue(event, AMOTION_EVENT_AXIS_HSCROLL, 0) * kWheelStep, + AMotionEvent_getAxisValue(event, AMOTION_EVENT_AXIS_VSCROLL, 0) * kWheelStep }; + input.post(PointerMoved{ position(SIZE_MAX) }); + input.post(Scrolled{ delta, ScrollSource::Wheel }); + return true; + } + default: return false; + } + } + + bool AndroidWindow::handleTouchGesture(AInputEvent* event, int32_t action) { + const size_t count = AMotionEvent_getPointerCount(event); + const bool lifting = action == AMOTION_EVENT_ACTION_POINTER_UP; + const size_t liftedIndex = + lifting ? (size_t) ((AMotionEvent_getAction(event) & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> + AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT) + : SIZE_MAX; + const bool ending = action == AMOTION_EVENT_ACTION_UP || action == AMOTION_EVENT_ACTION_CANCEL; + + std::vector touches; + if (!ending) { + for (size_t index = 0; index < count; index++) { + if (index == liftedIndex) { + continue; + } + touches.push_back( + { AMotionEvent_getPointerId(event, index), + { AMotionEvent_getX(event, index) / mScale, AMotionEvent_getY(event, index) / mScale } } + ); + } + } + + if (touches.size() < 2 && !mGestures.isActive() && !mGestureHoldsPointer) { + return false; + } + + InputSink& input = this->input(); + std::vector gestures; + mGestures.update(touches, gestures, (uint32_t) (AMotionEvent_getEventTime(event) / 1000000)); + for (const TouchGesture& gesture : gestures) { + input.post(GestureChanged{ gesture }); + } + + if (touches.size() >= 2 && !mGestureHoldsPointer) { + mGestureHoldsPointer = true; + if (mPrimaryHeld) { + mPrimaryHeld = false; + input.post(PointerButtonChanged{ PointerButton::Left, false }); + } + input.post(PointerLeft{}); + } + + if (ending) { + mGestureHoldsPointer = false; + } + return true; + } + + void AndroidWindow::deliverButtonChanges(InputSink& input, int32_t state) { + const int32_t changed = state ^ mButtonState; + for (int32_t bit = 1; bit <= AMOTION_EVENT_BUTTON_FORWARD; bit <<= 1) { + if (!(changed & bit)) { + continue; + } + if (auto button = buttonFor(bit)) { + input.post(PointerButtonChanged{ *button, (state & bit) != 0 }); + } + } + mButtonState = state; + } + + bool AndroidWindow::handleKey(AInputEvent* event) { + const int32_t keyCode = AKeyEvent_getKeyCode(event); + if (keyCode == AKEYCODE_VOLUME_UP || keyCode == AKEYCODE_VOLUME_DOWN) { + return false; + } + + const int32_t action = AKeyEvent_getAction(event); + InputSink& input = this->input(); + const std::optional key = + keyCode == AKEYCODE_BACK ? std::optional(Key::Escape) : android::keyFromAndroid(keyCode); + if (key && action == AKEY_EVENT_ACTION_DOWN) { + const bool repeats = AKeyEvent_getRepeatCount(event) > 0; + input.post(KeyChanged{ *key, repeats ? KeyAction::Repeat : KeyAction::Press }); + } else if (key && action == AKEY_EVENT_ACTION_UP) { + input.post(KeyChanged{ *key, KeyAction::Release }); + } + + if (action == AKEY_EVENT_ACTION_DOWN) { + const std::string text = android::unicodeText(mApp, event); + if (!text.empty()) { + input.post(TextEntered{ text }); + } + } + return true; + } + + void AndroidWindow::pollOnce(int timeoutMs) { + int events = 0; + android_poll_source* source = nullptr; + if (ALooper_pollOnce(timeoutMs, nullptr, &events, (void**) &source) >= 0 && source) { + source->process(&mApp, source); + } + } + + void AndroidWindow::wake() { + if (mApp.looper) { + ALooper_wake(mApp.looper); + } + } + + void AndroidWindow::dispatchEvents(halnf timeoutMs) { + int timeout = timeoutMs < 0 ? -1 : (int) std::ceil(timeoutMs); + if (!mNativeWindow && timeout >= 0) { + timeout = 100; + } + pollOnce(timeout); + while (true) { + int events = 0; + android_poll_source* source = nullptr; + if (ALooper_pollOnce(0, nullptr, &events, (void**) &source) < 0) { + break; + } + if (source) { + source->process(&mApp, source); + } + if (mApp.destroyRequested) { + break; + } + } + } + + void AndroidWindow::waitUntilConfigured() { + while (!mNativeWindow && !mApp.destroyRequested) { + pollOnce(-1); + } + updateGeometry(); + mConfigured = true; + mHost.onContentResized(); + __android_log_print(ANDROID_LOG_INFO, kTag, "platform %s", describe(platformInfo()).c_str()); + } + + bool AndroidWindow::shouldClose() const { return mApp.destroyRequested != 0; } + + void AndroidWindow::requestClose() { ANativeActivity_finish(mApp.activity); } + + void AndroidWindow::minimize() { ANativeActivity_finish(mApp.activity); } + + bool AndroidWindow::openUrl(const std::string& url) { return android::openUrl(mApp, url); } + + void AndroidWindow::setTextInputEnabled(bool enabled) { + if (enabled == mTextInput) { + return; + } + mTextInput = enabled; + android::setSoftKeyboardVisible(mApp, enabled); + } + + namespace { + std::string systemProperty(const char* name) { + char value[PROP_VALUE_MAX] = {}; + __system_property_get(name, value); + return value; + } + + constexpr int32_t kTabletSmallestWidthDp = 600; + } + + PlatformInfo AndroidWindow::platformInfo() const { + PlatformInfo info; + info.system = OperatingSystem::Android; + info.systemVersion = systemProperty("ro.build.version.release"); + info.deviceName = systemProperty("ro.product.manufacturer") + " " + systemProperty("ro.product.model"); + info.contentScale = mScale; + info.screenSize = mFrame.contentSize(); + info.formFactor = FormFactor::Phone; + if (AConfiguration* config = mApp.config) { + const int32_t smallest = AConfiguration_getSmallestScreenWidthDp(config); + if (smallest >= kTabletSmallestWidthDp) { + info.formFactor = FormFactor::Tablet; + } + if (AConfiguration_getUiModeType(config) == ACONFIGURATION_UI_MODE_TYPE_DESK) { + info.formFactor = FormFactor::Desktop; + } + info.hasTouch = AConfiguration_getTouchscreen(config) == ACONFIGURATION_TOUCHSCREEN_FINGER; + info.hasPhysicalKeyboard = AConfiguration_getKeyboard(config) == ACONFIGURATION_KEYBOARD_QWERTY; + } + info.safeInsets = mSafeInsets; + info.hasPen = mPenSeen; + info.hasMouse = mMouseSeen; + info.hasHoverPointer = mMouseSeen || mPenSeen; + return info; + } + + std::vector platformSurfaceExtensions() { + return { VK_KHR_SURFACE_EXTENSION_NAME, VK_KHR_ANDROID_SURFACE_EXTENSION_NAME }; + } + + // runAndroidApplication extracts the packaged assets into the app's internal storage, which it exports as HOME. + std::filesystem::path platformResourceDirectory() { + const char* home = std::getenv("HOME"); + return home ? std::filesystem::path(home) / "rsc" : std::filesystem::path("rsc"); + } + + std::unique_ptr + createPlatformWindow(PlatformWindowHost& host, Vec2F, const WindowIdentity&, const PlatformContext& context) { + if (!context.androidApp) { + throw std::runtime_error("the Android backend needs PlatformContext::androidApp"); + } + return std::make_unique(host, *context.androidApp); + } +} diff --git a/GraphicApplication/src/backends/android/private/AndroidWindow.hpp b/GraphicApplication/src/backends/android/private/AndroidWindow.hpp new file mode 100644 index 00000000..d2c4be38 --- /dev/null +++ b/GraphicApplication/src/backends/android/private/AndroidWindow.hpp @@ -0,0 +1,91 @@ +#pragma once + +#include "PlatformWindow.hpp" +#include "TouchGesture.hpp" + +struct android_app; +struct ANativeWindow; +struct AInputEvent; + +namespace tp { + + class AndroidWindow final : public PlatformWindow { + public: + AndroidWindow(PlatformWindowHost& host, android_app& app); + ~AndroidWindow() override; + + AndroidWindow(const AndroidWindow&) = delete; + AndroidWindow& operator=(const AndroidWindow&) = delete; + + [[nodiscard]] VkSurfaceKHR createSurface(VkInstance instance) const override; + [[nodiscard]] VkExtent2D framebufferExtent() const override; + [[nodiscard]] bool wantsTranslucency() const override { return false; } + void onTranslucencyResolved(bool translucent) override; + + void setAcceptsInput(bool) override {} + + void dispatchEvents(halnf timeoutMs) override; + void wake() override; + void waitUntilConfigured() override; + [[nodiscard]] bool hasPendingConfigure() const override { return false; } + void applyPendingConfigure() override {} + void commitFrameGeometry() override {} + [[nodiscard]] bool isWaitingForFrame() const override { return mNativeWindow == nullptr; } + + [[nodiscard]] bool shouldClose() const override; + void requestClose() override; + + [[nodiscard]] const WindowFrame& frame() const override { return mFrame; } + [[nodiscard]] halnf scale() const override { return mScale; } + [[nodiscard]] bool isMaximized() const override { return true; } + [[nodiscard]] bool isFocused() const override { return mFocused; } + + void minimize() override; + [[nodiscard]] bool isFullScreen() const override { return true; } + + void setCursor(CursorShape) override {} + void setClipboardText(const std::string& text) override { mClipboard = text; } + [[nodiscard]] std::string clipboardText() override { return mClipboard; } + [[nodiscard]] ClipboardImage clipboardImage() override { return {}; } + bool openUrl(const std::string& url) override; + void setTextInputEnabled(bool enabled) override; + void setTextInputRect(const RectF&) override {} + + [[nodiscard]] PlatformInfo platformInfo() const override; + + private: + static void onCommand(android_app* app, int32_t command); + static int32_t onInput(android_app* app, AInputEvent* event); + + void handleCommand(int32_t command); + [[nodiscard]] bool handleInput(AInputEvent* event); + [[nodiscard]] bool handleMotion(AInputEvent* event); + [[nodiscard]] bool handleKey(AInputEvent* event); + void deliverButtonChanges(InputSink& input, int32_t state); + [[nodiscard]] bool handleTouchGesture(AInputEvent* event, int32_t action); + + void attachWindow(ANativeWindow* window); + void detachWindow(); + void updateGeometry(); + void pollOnce(int timeoutMs); + [[nodiscard]] InputSink& input() { return mHost.rawInput(); } + + PlatformWindowHost& mHost; + android_app& mApp; + ANativeWindow* mNativeWindow = nullptr; + WindowFrame mFrame; + std::string mClipboard; + halnf mScale = 1; + bool mFocused = false; + bool mConfigured = false; + bool mPrimaryHeld = false; + bool mTextInput = false; + EdgeInsets mSafeInsets; + bool mPenSeen = false; + bool mMouseSeen = false; + int32_t mButtonState = 0; + halnf mLastContactPressure = 1; + TouchGestureTracker mGestures; + bool mGestureHoldsPointer = false; + }; +} diff --git a/GraphicApplication/src/backends/android/public/AndroidEntry.hpp b/GraphicApplication/src/backends/android/public/AndroidEntry.hpp new file mode 100644 index 00000000..c2745704 --- /dev/null +++ b/GraphicApplication/src/backends/android/public/AndroidEntry.hpp @@ -0,0 +1,13 @@ +#pragma once + +struct android_app; + +namespace tp { + + using AndroidMain = int (*)(android_app*); + + // Extracts assets, runs main and finishes the activity when main returns or fails. + void runAndroidApplication( + android_app* app, AndroidMain main, const char* const* assetDirectories, int assetDirectoryCount + ); +} diff --git a/GraphicApplication/src/backends/contract/private/PlatformInfo.cpp b/GraphicApplication/src/backends/contract/private/PlatformInfo.cpp new file mode 100644 index 00000000..0cb691a2 --- /dev/null +++ b/GraphicApplication/src/backends/contract/private/PlatformInfo.cpp @@ -0,0 +1,50 @@ +#include "PlatformInfo.hpp" + +#include + +namespace tp { + + const char* toString(OperatingSystem system) { + switch (system) { + case OperatingSystem::Linux: return "Linux"; + case OperatingSystem::Android: return "Android"; + case OperatingSystem::Windows: return "Windows"; + case OperatingSystem::MacOS: return "macOS"; + case OperatingSystem::IOS: return "iOS"; + case OperatingSystem::Unknown: break; + } + return "unknown"; + } + + const char* toString(FormFactor formFactor) { + switch (formFactor) { + case FormFactor::Desktop: return "desktop"; + case FormFactor::Tablet: return "tablet"; + case FormFactor::Phone: return "phone"; + case FormFactor::Unknown: break; + } + return "unknown"; + } + + std::string describe(const PlatformInfo& info) { + char text[512]; + std::snprintf( + text, + sizeof(text), + "%s %s, %s%s%s, scale %.2f, screen %.0fx%.0f, input:%s%s%s%s", + toString(info.system), + info.systemVersion.c_str(), + toString(info.formFactor), + info.deviceName.empty() ? "" : " ", + info.deviceName.c_str(), + info.contentScale, + info.screenSize.x, + info.screenSize.y, + info.hasMouse ? " mouse" : "", + info.hasTouch ? " touch" : "", + info.hasPen ? " pen" : "", + info.hasPhysicalKeyboard ? " keyboard" : "" + ); + return text; + } +} diff --git a/GraphicApplication/src/backends/contract/private/WindowFrame.cpp b/GraphicApplication/src/backends/contract/private/WindowFrame.cpp new file mode 100644 index 00000000..584649ad --- /dev/null +++ b/GraphicApplication/src/backends/contract/private/WindowFrame.cpp @@ -0,0 +1,94 @@ +#include "WindowFrame.hpp" + +#include + +using namespace tp; + +CursorShape tp::cursorForEdge(WindowEdge edge) { + switch (edge) { + case WindowEdge::Left: + case WindowEdge::Right: return CursorShape::ResizeHorizontal; + case WindowEdge::Top: + case WindowEdge::Bottom: return CursorShape::ResizeVertical; + case WindowEdge::TopLeft: + case WindowEdge::BottomRight: return CursorShape::ResizeNorthWestSouthEast; + case WindowEdge::TopRight: + case WindowEdge::BottomLeft: return CursorShape::ResizeNorthEastSouthWest; + case WindowEdge::None: return CursorShape::Default; + } + return CursorShape::Default; +} + +Vec2F WindowFrame::surfaceSize() const { + const halnf inset = margin(); + return { mContentSize.x + inset * 2, mContentSize.y + inset * 2 }; +} + +RectF WindowFrame::contentRect() const { + const halnf inset = margin(); + return { inset, inset, mContentSize.x, mContentSize.y }; +} + +RectF WindowFrame::inputRect() const { + const RectF content = contentRect(); + const halnf outer = outerResizeBorder(); + return { content.x - outer, content.y - outer, content.z + outer * 2, content.w + outer * 2 }; +} + +halnf WindowFrame::outerResizeBorder() const { return std::min(kResizeBorder, margin()); } + +halnf WindowFrame::innerResizeBorder() const { + return std::max(kMinimumInnerResizeBorder, kResizeBorder - outerResizeBorder()); +} + +WindowEdge WindowFrame::edgeAt(Vec2F point) const { + if (mTiled) { + return WindowEdge::None; + } + + const RectF zone = inputRect(); + const bool insideZone = + point.x >= zone.x && point.y >= zone.y && point.x < zone.x + zone.z && point.y < zone.y + zone.w; + if (!insideZone) { + return WindowEdge::None; + } + + const RectF content = contentRect(); + const halnf inner = innerResizeBorder(); + + const bool onLeft = point.x < content.x + inner; + const bool onRight = point.x >= content.x + content.z - inner; + const bool onTop = point.y < content.y + inner; + const bool onBottom = point.y >= content.y + content.w - inner; + + const bool nearLeft = point.x < content.x + kCornerResizeLength; + const bool nearRight = point.x >= content.x + content.z - kCornerResizeLength; + const bool nearTop = point.y < content.y + kCornerResizeLength; + const bool nearBottom = point.y >= content.y + content.w - kCornerResizeLength; + + if ((onTop && nearLeft) || (onLeft && nearTop)) { + return WindowEdge::TopLeft; + } + if ((onTop && nearRight) || (onRight && nearTop)) { + return WindowEdge::TopRight; + } + if ((onBottom && nearLeft) || (onLeft && nearBottom)) { + return WindowEdge::BottomLeft; + } + if ((onBottom && nearRight) || (onRight && nearBottom)) { + return WindowEdge::BottomRight; + } + if (onTop) { + return WindowEdge::Top; + } + if (onBottom) { + return WindowEdge::Bottom; + } + if (onLeft) { + return WindowEdge::Left; + } + if (onRight) { + return WindowEdge::Right; + } + return WindowEdge::None; +} diff --git a/GraphicApplication/src/backends/contract/public/DesktopWindowControls.hpp b/GraphicApplication/src/backends/contract/public/DesktopWindowControls.hpp new file mode 100644 index 00000000..b9eee666 --- /dev/null +++ b/GraphicApplication/src/backends/contract/public/DesktopWindowControls.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include "Vec.hpp" +#include "WindowTypes.hpp" + +namespace tp { + + // Window manipulation that only desktop window systems offer; absent on other platforms. + class DesktopWindowControls { + public: + virtual ~DesktopWindowControls() = default; + + virtual void toggleMaximize() = 0; + virtual void setFullScreen(bool fullScreen) = 0; + virtual void beginMove() = 0; + virtual void beginResize(WindowEdge edge) = 0; + virtual void showWindowMenu(Vec2F contentPosition) = 0; + }; +} diff --git a/GraphicApplication/src/backends/contract/public/PlatformContext.hpp b/GraphicApplication/src/backends/contract/public/PlatformContext.hpp new file mode 100644 index 00000000..676e9560 --- /dev/null +++ b/GraphicApplication/src/backends/contract/public/PlatformContext.hpp @@ -0,0 +1,11 @@ +#pragma once + +struct android_app; + +namespace tp { + + // Process objects a backend needs to open a window; desktop backends need none. + struct PlatformContext { + android_app* androidApp = nullptr; + }; +} diff --git a/GraphicApplication/src/backends/contract/public/PlatformInfo.hpp b/GraphicApplication/src/backends/contract/public/PlatformInfo.hpp new file mode 100644 index 00000000..fb481118 --- /dev/null +++ b/GraphicApplication/src/backends/contract/public/PlatformInfo.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include "Vec.hpp" + +#include + +namespace tp { + + enum class OperatingSystem { Linux, Android, Windows, MacOS, IOS, Unknown }; + + enum class FormFactor { Desktop, Tablet, Phone, Unknown }; + + struct EdgeInsets { + halnf left = 0; + halnf top = 0; + halnf right = 0; + halnf bottom = 0; + + [[nodiscard]] bool operator==(const EdgeInsets& other) const = default; + }; + + struct PlatformInfo { + OperatingSystem system = OperatingSystem::Unknown; + FormFactor formFactor = FormFactor::Unknown; + std::string systemVersion; + std::string deviceName; + + halnf contentScale = 1; + Vec2F screenSize = { 0, 0 }; + EdgeInsets safeInsets; + + bool hasMouse = false; + bool hasTouch = false; + bool hasPen = false; + bool hasPhysicalKeyboard = false; + + bool hasHoverPointer = false; + + [[nodiscard]] bool isTouchFirst() const { + return formFactor == FormFactor::Tablet || formFactor == FormFactor::Phone; + } + }; + + [[nodiscard]] const char* toString(OperatingSystem system); + [[nodiscard]] const char* toString(FormFactor formFactor); + [[nodiscard]] std::string describe(const PlatformInfo& info); +} diff --git a/GraphicApplication/src/backends/contract/public/PlatformWindow.hpp b/GraphicApplication/src/backends/contract/public/PlatformWindow.hpp new file mode 100644 index 00000000..c8580ad3 --- /dev/null +++ b/GraphicApplication/src/backends/contract/public/PlatformWindow.hpp @@ -0,0 +1,73 @@ +#pragma once + +#include "InputSink.hpp" +#include "PlatformContext.hpp" +#include "PlatformInfo.hpp" +#include "Rect.hpp" +#include "WindowFrame.hpp" +#include "WindowSurface.hpp" +#include "WindowTypes.hpp" + +#include +#include +#include +#include + +namespace tp { + + class DesktopWindowControls; + + // What a backend reports to the window that owns it. + class PlatformWindowHost { + public: + virtual ~PlatformWindowHost() = default; + + [[nodiscard]] virtual InputSink& rawInput() = 0; + virtual void onContentResized() = 0; + virtual void onSurfaceAvailable() = 0; + virtual void onSurfaceLost() = 0; + }; + + class PlatformWindow : public VulkanSurfaceSource { + public: + ~PlatformWindow() override = default; + + virtual void dispatchEvents(halnf timeoutMs) = 0; + virtual void wake() = 0; + virtual void waitUntilConfigured() = 0; + [[nodiscard]] virtual bool hasPendingConfigure() const = 0; + virtual void applyPendingConfigure() = 0; + virtual void commitFrameGeometry() = 0; + [[nodiscard]] virtual bool isWaitingForFrame() const = 0; + virtual void setAcceptsInput(bool accepts) = 0; + + [[nodiscard]] virtual bool shouldClose() const = 0; + virtual void requestClose() = 0; + + [[nodiscard]] virtual const WindowFrame& frame() const = 0; + [[nodiscard]] virtual halnf scale() const = 0; + [[nodiscard]] virtual bool isMaximized() const = 0; + [[nodiscard]] virtual bool isFullScreen() const = 0; + [[nodiscard]] virtual bool isFocused() const = 0; + virtual void minimize() = 0; + [[nodiscard]] virtual DesktopWindowControls* desktopControls() { return nullptr; } + + virtual void setCursor(CursorShape shape) = 0; + virtual void setClipboardText(const std::string& text) = 0; + [[nodiscard]] virtual std::string clipboardText() = 0; + [[nodiscard]] virtual ClipboardImage clipboardImage() = 0; + virtual bool openUrl(const std::string& url) = 0; + virtual void setTextInputEnabled(bool enabled) = 0; + virtual void setTextInputRect(const RectF& contentRect) = 0; + + [[nodiscard]] virtual PlatformInfo platformInfo() const = 0; + }; + + [[nodiscard]] std::vector platformSurfaceExtensions(); + + [[nodiscard]] std::filesystem::path platformResourceDirectory(); + + [[nodiscard]] std::unique_ptr createPlatformWindow( + PlatformWindowHost& host, Vec2F contentSize, const WindowIdentity& identity, const PlatformContext& context + ); +} diff --git a/GraphicApplication/src/backends/contract/public/WindowFrame.hpp b/GraphicApplication/src/backends/contract/public/WindowFrame.hpp new file mode 100644 index 00000000..ac8db063 --- /dev/null +++ b/GraphicApplication/src/backends/contract/public/WindowFrame.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include "Rect.hpp" +#include "WindowTypes.hpp" + +namespace tp { + + class WindowFrame { + public: + static constexpr halnf kShadowMargin = 18; + static constexpr halnf kCornerRadius = 12; + static constexpr halnf kResizeBorder = 10; + static constexpr halnf kMinimumInnerResizeBorder = 3; + static constexpr halnf kCornerResizeLength = 18; + + void setContentSize(Vec2F size) { mContentSize = size; } + void setTiled(bool tiled) { mTiled = tiled; } + void setTranslucent(bool translucent) { mTranslucent = translucent; } + + [[nodiscard]] Vec2F contentSize() const { return mContentSize; } + [[nodiscard]] bool isTiled() const { return mTiled; } + [[nodiscard]] bool isTranslucent() const { return mTranslucent; } + + [[nodiscard]] bool hasShadow() const { return mTranslucent && !mTiled; } + [[nodiscard]] halnf margin() const { return hasShadow() ? kShadowMargin : 0; } + [[nodiscard]] halnf cornerRadius() const { return hasShadow() ? kCornerRadius : 0; } + + [[nodiscard]] Vec2F surfaceSize() const; + [[nodiscard]] RectF contentRect() const; + [[nodiscard]] RectF inputRect() const; + + [[nodiscard]] WindowEdge edgeAt(Vec2F surfacePoint) const; + + private: + [[nodiscard]] halnf outerResizeBorder() const; + [[nodiscard]] halnf innerResizeBorder() const; + + Vec2F mContentSize = { 0, 0 }; + bool mTiled = false; + bool mTranslucent = true; + }; +} diff --git a/GraphicApplication/src/backends/contract/public/WindowTypes.hpp b/GraphicApplication/src/backends/contract/public/WindowTypes.hpp new file mode 100644 index 00000000..1edee226 --- /dev/null +++ b/GraphicApplication/src/backends/contract/public/WindowTypes.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include + +namespace tp { + + struct ClipboardImage { + std::string mimeType; + std::string bytes; + + [[nodiscard]] bool isEmpty() const { return bytes.empty(); } + }; + + enum class CursorShape { + Default, + Pointer, + Text, + Move, + Grab, + Grabbing, + ResizeHorizontal, + ResizeVertical, + ResizeNorthWestSouthEast, + ResizeNorthEastSouthWest, + NotAllowed, + Crosshair, + Hidden + }; + + struct WindowIdentity { + std::string appId; + std::string title; + + [[nodiscard]] static WindowIdentity generic(); + }; + + enum class WindowEdge { None, Top, Bottom, Left, Right, TopLeft, TopRight, BottomLeft, BottomRight }; + + CursorShape cursorForEdge(WindowEdge edge); +} diff --git a/GraphicApplication/src/backends/wayland/private/LinuxInputCodes.cpp b/GraphicApplication/src/backends/wayland/private/LinuxInputCodes.cpp new file mode 100644 index 00000000..a44bc867 --- /dev/null +++ b/GraphicApplication/src/backends/wayland/private/LinuxInputCodes.cpp @@ -0,0 +1,153 @@ +#include "LinuxInputCodes.hpp" + +#include + +#include + +using namespace tp; + +namespace { + const std::unordered_map& evdevKeys() { + static const std::unordered_map keys = { + { KEY_SPACE, Key::Space }, + { KEY_APOSTROPHE, Key::Apostrophe }, + { KEY_COMMA, Key::Comma }, + { KEY_MINUS, Key::Minus }, + { KEY_DOT, Key::Period }, + { KEY_SLASH, Key::Slash }, + { KEY_0, Key::Digit0 }, + { KEY_1, Key::Digit1 }, + { KEY_2, Key::Digit2 }, + { KEY_3, Key::Digit3 }, + { KEY_4, Key::Digit4 }, + { KEY_5, Key::Digit5 }, + { KEY_6, Key::Digit6 }, + { KEY_7, Key::Digit7 }, + { KEY_8, Key::Digit8 }, + { KEY_9, Key::Digit9 }, + { KEY_SEMICOLON, Key::Semicolon }, + { KEY_EQUAL, Key::Equal }, + { KEY_A, Key::A }, + { KEY_B, Key::B }, + { KEY_C, Key::C }, + { KEY_D, Key::D }, + { KEY_E, Key::E }, + { KEY_F, Key::F }, + { KEY_G, Key::G }, + { KEY_H, Key::H }, + { KEY_I, Key::I }, + { KEY_J, Key::J }, + { KEY_K, Key::K }, + { KEY_L, Key::L }, + { KEY_M, Key::M }, + { KEY_N, Key::N }, + { KEY_O, Key::O }, + { KEY_P, Key::P }, + { KEY_Q, Key::Q }, + { KEY_R, Key::R }, + { KEY_S, Key::S }, + { KEY_T, Key::T }, + { KEY_U, Key::U }, + { KEY_V, Key::V }, + { KEY_W, Key::W }, + { KEY_X, Key::X }, + { KEY_Y, Key::Y }, + { KEY_Z, Key::Z }, + { KEY_LEFTBRACE, Key::LeftBracket }, + { KEY_BACKSLASH, Key::Backslash }, + { KEY_RIGHTBRACE, Key::RightBracket }, + { KEY_GRAVE, Key::GraveAccent }, + { KEY_102ND, Key::World2 }, + { KEY_ESC, Key::Escape }, + { KEY_ENTER, Key::Enter }, + { KEY_TAB, Key::Tab }, + { KEY_BACKSPACE, Key::Backspace }, + { KEY_INSERT, Key::Insert }, + { KEY_DELETE, Key::Delete }, + { KEY_RIGHT, Key::Right }, + { KEY_LEFT, Key::Left }, + { KEY_DOWN, Key::Down }, + { KEY_UP, Key::Up }, + { KEY_PAGEUP, Key::PageUp }, + { KEY_PAGEDOWN, Key::PageDown }, + { KEY_HOME, Key::Home }, + { KEY_END, Key::End }, + { KEY_CAPSLOCK, Key::CapsLock }, + { KEY_SCROLLLOCK, Key::ScrollLock }, + { KEY_NUMLOCK, Key::NumLock }, + { KEY_SYSRQ, Key::PrintScreen }, + { KEY_PAUSE, Key::Pause }, + { KEY_F1, Key::F1 }, + { KEY_F2, Key::F2 }, + { KEY_F3, Key::F3 }, + { KEY_F4, Key::F4 }, + { KEY_F5, Key::F5 }, + { KEY_F6, Key::F6 }, + { KEY_F7, Key::F7 }, + { KEY_F8, Key::F8 }, + { KEY_F9, Key::F9 }, + { KEY_F10, Key::F10 }, + { KEY_F11, Key::F11 }, + { KEY_F12, Key::F12 }, + { KEY_F13, Key::F13 }, + { KEY_F14, Key::F14 }, + { KEY_F15, Key::F15 }, + { KEY_F16, Key::F16 }, + { KEY_F17, Key::F17 }, + { KEY_F18, Key::F18 }, + { KEY_F19, Key::F19 }, + { KEY_F20, Key::F20 }, + { KEY_F21, Key::F21 }, + { KEY_F22, Key::F22 }, + { KEY_F23, Key::F23 }, + { KEY_F24, Key::F24 }, + { KEY_KP0, Key::Keypad0 }, + { KEY_KP1, Key::Keypad1 }, + { KEY_KP2, Key::Keypad2 }, + { KEY_KP3, Key::Keypad3 }, + { KEY_KP4, Key::Keypad4 }, + { KEY_KP5, Key::Keypad5 }, + { KEY_KP6, Key::Keypad6 }, + { KEY_KP7, Key::Keypad7 }, + { KEY_KP8, Key::Keypad8 }, + { KEY_KP9, Key::Keypad9 }, + { KEY_KPDOT, Key::KeypadDecimal }, + { KEY_KPSLASH, Key::KeypadDivide }, + { KEY_KPASTERISK, Key::KeypadMultiply }, + { KEY_KPMINUS, Key::KeypadSubtract }, + { KEY_KPPLUS, Key::KeypadAdd }, + { KEY_KPENTER, Key::KeypadEnter }, + { KEY_KPEQUAL, Key::KeypadEqual }, + { KEY_LEFTSHIFT, Key::LeftShift }, + { KEY_LEFTCTRL, Key::LeftControl }, + { KEY_LEFTALT, Key::LeftAlt }, + { KEY_LEFTMETA, Key::LeftSuper }, + { KEY_RIGHTSHIFT, Key::RightShift }, + { KEY_RIGHTCTRL, Key::RightControl }, + { KEY_RIGHTALT, Key::RightAlt }, + { KEY_RIGHTMETA, Key::RightSuper }, + { KEY_COMPOSE, Key::Menu }, + }; + return keys; + } +} + +std::optional tp::keyFromEvdev(uint32_t evdevKey) { + const auto& keys = evdevKeys(); + const auto found = keys.find(evdevKey); + if (found == keys.end()) { + return std::nullopt; + } + return found->second; +} + +std::optional tp::pointerButtonFromLinux(uint32_t linuxButton) { + switch (linuxButton) { + case BTN_LEFT: return PointerButton::Left; + case BTN_RIGHT: return PointerButton::Right; + case BTN_MIDDLE: return PointerButton::Middle; + case BTN_SIDE: return PointerButton::Back; + case BTN_EXTRA: return PointerButton::Forward; + default: return std::nullopt; + } +} diff --git a/GraphicApplication/src/backends/wayland/private/LinuxInputCodes.hpp b/GraphicApplication/src/backends/wayland/private/LinuxInputCodes.hpp new file mode 100644 index 00000000..65f25007 --- /dev/null +++ b/GraphicApplication/src/backends/wayland/private/LinuxInputCodes.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include "InputCodes.hpp" + +#include +#include + +namespace tp { + + [[nodiscard]] std::optional keyFromEvdev(uint32_t evdevKey); + + [[nodiscard]] std::optional pointerButtonFromLinux(uint32_t linuxButton); +} diff --git a/GraphicApplication/src/backends/wayland/private/PointerPinchConverter.cpp b/GraphicApplication/src/backends/wayland/private/PointerPinchConverter.cpp new file mode 100644 index 00000000..602dd94d --- /dev/null +++ b/GraphicApplication/src/backends/wayland/private/PointerPinchConverter.cpp @@ -0,0 +1,38 @@ +#include "PointerPinchConverter.hpp" + +#include + +namespace tp { + + TouchGesture PointerPinchConverter::begin(Vec2F center) { + mActive = true; + mTotalScale = 1; + return { GesturePhase::Begin, center }; + } + + std::optional + PointerPinchConverter::update(Vec2F center, Vec2F pan, halnf totalScale, halnf rotationDegrees) { + if (!mActive) { + return std::nullopt; + } + + TouchGesture gesture; + gesture.phase = GesturePhase::Update; + gesture.center = center; + gesture.pan = pan; + gesture.scale = mTotalScale > 0 && totalScale > 0 ? totalScale / mTotalScale : 1; + gesture.rotation = wrapAngle(rotationDegrees * std::numbers::pi_v / 180); + if (totalScale > 0) { + mTotalScale = totalScale; + } + return gesture; + } + + std::optional PointerPinchConverter::end(Vec2F center) { + if (!mActive) { + return std::nullopt; + } + mActive = false; + return TouchGesture{ GesturePhase::End, center }; + } +} diff --git a/GraphicApplication/src/backends/wayland/private/PointerPinchConverter.hpp b/GraphicApplication/src/backends/wayland/private/PointerPinchConverter.hpp new file mode 100644 index 00000000..888bdb57 --- /dev/null +++ b/GraphicApplication/src/backends/wayland/private/PointerPinchConverter.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "TouchGesture.hpp" + +#include + +namespace tp { + + // Converts touchpad pinch updates (scale relative to the gesture start, pan and rotation per event) into + // incremental TouchGesture updates. + class PointerPinchConverter { + public: + [[nodiscard]] TouchGesture begin(Vec2F center); + [[nodiscard]] std::optional update(Vec2F center, Vec2F pan, halnf totalScale, halnf rotationDegrees); + [[nodiscard]] std::optional end(Vec2F center); + + private: + halnf mTotalScale = 1; + bool mActive = false; + }; +} diff --git a/GraphicApplication/src/backends/wayland/private/WaylandClipboard.cpp b/GraphicApplication/src/backends/wayland/private/WaylandClipboard.cpp new file mode 100644 index 00000000..aa806433 --- /dev/null +++ b/GraphicApplication/src/backends/wayland/private/WaylandClipboard.cpp @@ -0,0 +1,260 @@ +#include "WaylandClipboard.hpp" + +#include "WaylandConnection.hpp" + +#include + +#include +#include +#include +#include + +namespace tp { + + namespace { + constexpr const char* kTextMimeType = "text/plain;charset=utf-8"; + constexpr const char* kLegacyTextMimeType = "text/plain"; + constexpr const char* kPngMimeType = "image/png"; + constexpr const char* kImageMimePrefix = "image/"; + + WaylandClipboard& clipboard(void* data) { return *static_cast(data); } + + void offerMimeType(void* data, wl_data_offer* offer, const char* mimeType) { + clipboard(data).onOfferMimeType(offer, mimeType); + } + void offerSourceActions(void*, wl_data_offer*, uint32_t) {} + void offerAction(void*, wl_data_offer*, uint32_t) {} + + void dataOffer(void* data, wl_data_device*, wl_data_offer* offer) { clipboard(data).onOffer(offer); } + void dataEnter(void*, wl_data_device*, uint32_t, wl_surface*, wl_fixed_t, wl_fixed_t, wl_data_offer* offer) { + if (offer) { + wl_data_offer_destroy(offer); + } + } + void dataLeave(void*, wl_data_device*) {} + void dataMotion(void*, wl_data_device*, uint32_t, wl_fixed_t, wl_fixed_t) {} + void dataDrop(void*, wl_data_device*) {} + void dataSelection(void* data, wl_data_device*, wl_data_offer* offer) { clipboard(data).onSelection(offer); } + + void sourceTarget(void*, wl_data_source*, const char*) {} + void sourceSend(void* data, wl_data_source*, const char* mimeType, int32_t descriptor) { + clipboard(data).onSourceSend(mimeType, descriptor); + } + void sourceCancelled(void* data, wl_data_source* source) { clipboard(data).onSourceCancelled(source); } + void sourceDropPerformed(void*, wl_data_source*) {} + void sourceDndFinished(void*, wl_data_source*) {} + void sourceAction(void*, wl_data_source*, uint32_t) {} + + const wl_data_offer_listener kOfferListener = { offerMimeType, offerSourceActions, offerAction }; + + const wl_data_device_listener kDataDeviceListener = { + dataOffer, dataEnter, dataLeave, dataMotion, dataDrop, dataSelection, + }; + + const wl_data_source_listener kSourceListener = { + sourceTarget, sourceSend, sourceCancelled, sourceDropPerformed, sourceDndFinished, sourceAction, + }; + + void writeAll(int descriptor, const std::string& text) { + size_t written = 0; + while (written < text.size()) { + const ssize_t result = write(descriptor, text.data() + written, text.size() - written); + if (result < 0 && errno == EINTR) { + continue; + } + if (result <= 0) { + return; + } + written += (size_t) result; + } + } + + const std::string* preferredImageType(const std::vector& types) { + const std::string* chosen = nullptr; + for (const std::string& type : types) { + if (type == kPngMimeType) { + return &type; + } + if (!chosen && type.starts_with(kImageMimePrefix)) { + chosen = &type; + } + } + return chosen; + } + + bool offers(const std::vector& types, const char* mimeType) { + return std::find(types.begin(), types.end(), mimeType) != types.end(); + } + } + + WaylandClipboard::WaylandClipboard(WaylandSeatContext& seat, wl_data_device_manager* manager, wl_seat* waylandSeat) : + mSeat(seat), + mManager(manager) { + mDevice = wl_data_device_manager_get_data_device(manager, waylandSeat); + wl_data_device_add_listener(mDevice, &kDataDeviceListener, this); + } + + WaylandClipboard::~WaylandClipboard() { + closeTransfer(mText); + closeTransfer(mImage); + if (mSource) { + wl_data_source_destroy(mSource); + } + if (mSelection) { + wl_data_offer_destroy(mSelection); + } + if (mPendingOffer && mPendingOffer != mSelection) { + wl_data_offer_destroy(mPendingOffer); + } + if (mDevice) { + wl_data_device_release(mDevice); + } + } + + void WaylandClipboard::setText(const std::string& text) { + mOwnedText = text; + if (mSource) { + wl_data_source_destroy(mSource); + } + + mSource = wl_data_device_manager_create_data_source(mManager); + wl_data_source_add_listener(mSource, &kSourceListener, this); + wl_data_source_offer(mSource, kTextMimeType); + wl_data_source_offer(mSource, kLegacyTextMimeType); + wl_data_device_set_selection(mDevice, mSource, mSeat.lastInputSerial()); + mSeat.connection().flush(); + } + + std::string WaylandClipboard::text() const { + if (mSource) { + return mOwnedText; + } + return mText.complete ? mText.bytes : std::string(); + } + + ClipboardImage WaylandClipboard::image() const { + if (mSource || !mImage.complete) { + return {}; + } + return { mImage.mimeType, mImage.bytes }; + } + + void WaylandClipboard::startTransfer(Transfer& transfer, const std::string& mimeType) { + closeTransfer(transfer); + transfer = Transfer{ mimeType }; + + int pipeDescriptors[2]; + if (pipe2(pipeDescriptors, O_CLOEXEC | O_NONBLOCK) != 0) { + transfer.complete = true; + return; + } + wl_data_offer_receive(mSelection, mimeType.c_str(), pipeDescriptors[1]); + close(pipeDescriptors[1]); + transfer.descriptor = pipeDescriptors[0]; + } + + void WaylandClipboard::readAvailable(Transfer& transfer) { + char buffer[4096]; + while (true) { + const ssize_t result = read(transfer.descriptor, buffer, sizeof(buffer)); + if (result > 0) { + transfer.bytes.append(buffer, (size_t) result); + continue; + } + if (result < 0 && errno == EINTR) { + continue; + } + if (result < 0 && errno == EAGAIN) { + return; + } + closeTransfer(transfer); + transfer.complete = true; + return; + } + } + + void WaylandClipboard::closeTransfer(Transfer& transfer) { + if (transfer.descriptor >= 0) { + close(transfer.descriptor); + } + transfer.descriptor = -1; + } + + void WaylandClipboard::collectPollDescriptors(std::vector& descriptors) const { + for (const Transfer* transfer : { &mText, &mImage }) { + if (transfer->descriptor >= 0) { + descriptors.push_back({ transfer->descriptor, POLLIN, 0 }); + } + } + } + + void WaylandClipboard::onDescriptorsReady(const std::vector& descriptors) { + for (const pollfd& ready : descriptors) { + if (!(ready.revents & (POLLIN | POLLHUP | POLLERR))) { + continue; + } + for (Transfer* transfer : { &mText, &mImage }) { + if (transfer->descriptor == ready.fd) { + readAvailable(*transfer); + } + } + } + } + + void WaylandClipboard::forgetSelection() { + closeTransfer(mText); + closeTransfer(mImage); + mText = {}; + mImage = {}; + if (mSelection) { + wl_data_offer_destroy(mSelection); + } + mSelection = nullptr; + } + + void WaylandClipboard::onOffer(wl_data_offer* offer) { + mPendingOffer = offer; + mPendingOfferTypes.clear(); + wl_data_offer_add_listener(offer, &kOfferListener, this); + } + + void WaylandClipboard::onOfferMimeType(wl_data_offer* offer, const char* mimeType) { + if (offer == mPendingOffer) { + mPendingOfferTypes.emplace_back(mimeType); + } + } + + void WaylandClipboard::onSelection(wl_data_offer* offer) { + const std::vector types = + offer && offer == mPendingOffer ? std::move(mPendingOfferTypes) : std::vector{}; + if (mSelection != offer) { + forgetSelection(); + } + mSelection = offer; + mPendingOfferTypes.clear(); + mPendingOffer = nullptr; + if (!mSelection) { + return; + } + + if (offers(types, kTextMimeType)) { + startTransfer(mText, kTextMimeType); + } + if (const std::string* imageType = preferredImageType(types)) { + startTransfer(mImage, *imageType); + } + mSeat.connection().flush(); + } + + void WaylandClipboard::onSourceSend(const char*, int descriptor) { + writeAll(descriptor, mOwnedText); + close(descriptor); + } + + void WaylandClipboard::onSourceCancelled(wl_data_source* source) { + if (source == mSource) { + mSource = nullptr; + } + wl_data_source_destroy(source); + } +} diff --git a/GraphicApplication/src/backends/wayland/private/WaylandClipboard.hpp b/GraphicApplication/src/backends/wayland/private/WaylandClipboard.hpp new file mode 100644 index 00000000..2a41b5f0 --- /dev/null +++ b/GraphicApplication/src/backends/wayland/private/WaylandClipboard.hpp @@ -0,0 +1,66 @@ +#pragma once + +#include "WaylandSeatContext.hpp" +#include "WindowTypes.hpp" + +#include + +#include +#include + +struct wl_data_device; +struct wl_data_device_manager; +struct wl_data_offer; +struct wl_data_source; +struct wl_seat; + +namespace tp { + + // The selection clipboard. Offered contents are read asynchronously on the event loop, so reading the clipboard + // never blocks the UI thread; a selection that is still arriving reads as empty. + class WaylandClipboard { + public: + WaylandClipboard(WaylandSeatContext& seat, wl_data_device_manager* manager, wl_seat* waylandSeat); + ~WaylandClipboard(); + + WaylandClipboard(const WaylandClipboard&) = delete; + WaylandClipboard& operator=(const WaylandClipboard&) = delete; + + void setText(const std::string& text); + [[nodiscard]] std::string text() const; + [[nodiscard]] ClipboardImage image() const; + + void collectPollDescriptors(std::vector& descriptors) const; + void onDescriptorsReady(const std::vector& descriptors); + + void onOffer(wl_data_offer* offer); + void onOfferMimeType(wl_data_offer* offer, const char* mimeType); + void onSelection(wl_data_offer* offer); + void onSourceSend(const char* mimeType, int descriptor); + void onSourceCancelled(wl_data_source* source); + + private: + struct Transfer { + std::string mimeType; + int descriptor = -1; + std::string bytes; + bool complete = false; + }; + + void startTransfer(Transfer& transfer, const std::string& mimeType); + static void readAvailable(Transfer& transfer); + static void closeTransfer(Transfer& transfer); + void forgetSelection(); + + WaylandSeatContext& mSeat; + wl_data_device_manager* mManager; + wl_data_device* mDevice = nullptr; + wl_data_offer* mPendingOffer = nullptr; + std::vector mPendingOfferTypes; + wl_data_offer* mSelection = nullptr; + wl_data_source* mSource = nullptr; + std::string mOwnedText; + Transfer mText; + Transfer mImage; + }; +} diff --git a/GraphicApplication/src/backends/wayland/private/WaylandConnection.cpp b/GraphicApplication/src/backends/wayland/private/WaylandConnection.cpp new file mode 100644 index 00000000..386e27dd --- /dev/null +++ b/GraphicApplication/src/backends/wayland/private/WaylandConnection.cpp @@ -0,0 +1,193 @@ +#include "WaylandConnection.hpp" + +#include "cursor-shape-v1-client-protocol.h" +#include "pointer-gestures-unstable-v1-client-protocol.h" +#include "fractional-scale-v1-client-protocol.h" +#include "tablet-v2-client-protocol.h" +#include "text-input-unstable-v3-client-protocol.h" +#include "viewporter-client-protocol.h" +#include "xdg-activation-v1-client-protocol.h" +#include "xdg-decoration-unstable-v1-client-protocol.h" +#include "xdg-shell-client-protocol.h" + +#include + +#include +#include +#include +#include + +namespace tp { + + struct WaylandRegistryListener { + static void global(void* data, wl_registry* registry, uint32_t name, const char* interface, uint32_t version) { + static_cast(data)->bindGlobal(registry, name, interface, version); + } + + static void globalRemove(void*, wl_registry*, uint32_t) {} + + static void ping(void*, xdg_wm_base* windowManager, uint32_t serial) { xdg_wm_base_pong(windowManager, serial); } + }; + + namespace { + const wl_registry_listener kRegistryListener = { + WaylandRegistryListener::global, + WaylandRegistryListener::globalRemove, + }; + + const xdg_wm_base_listener kWindowManagerListener = { WaylandRegistryListener::ping }; + + template + Interface* + bindAs(wl_registry* registry, uint32_t name, const wl_interface* interface, uint32_t offered, uint32_t wanted) { + return static_cast(wl_registry_bind(registry, name, interface, std::min(offered, wanted))); + } + } + + WaylandConnection::WaylandConnection() { + mDisplay = wl_display_connect(nullptr); + if (!mDisplay) { + throw std::runtime_error("cannot connect to the Wayland display; this build supports Wayland only"); + } + + mRegistry = wl_display_get_registry(mDisplay); + wl_registry_add_listener(mRegistry, &kRegistryListener, this); + wl_display_roundtrip(mDisplay); + wl_display_roundtrip(mDisplay); + + if (!mCompositor || !mWindowManager) { + throw std::runtime_error("Wayland compositor lacks wl_compositor or xdg_wm_base"); + } + } + + WaylandConnection::~WaylandConnection() { + if (mDecorationManager) { + zxdg_decoration_manager_v1_destroy(mDecorationManager); + } + if (mTextInputManager) { + zwp_text_input_manager_v3_destroy(mTextInputManager); + } + if (mActivation) { + xdg_activation_v1_destroy(mActivation); + } + if (mTabletManager) { + zwp_tablet_manager_v2_destroy(mTabletManager); + } + if (mCursorShapeManager) { + wp_cursor_shape_manager_v1_destroy(mCursorShapeManager); + } + if (mPointerGestures) { + zwp_pointer_gestures_v1_destroy(mPointerGestures); + } + if (mFractionalScaleManager) { + wp_fractional_scale_manager_v1_destroy(mFractionalScaleManager); + } + if (mViewporter) { + wp_viewporter_destroy(mViewporter); + } + if (mDataDeviceManager) { + wl_data_device_manager_destroy(mDataDeviceManager); + } + if (mSeat) { + wl_seat_release(mSeat); + } + if (mWindowManager) { + xdg_wm_base_destroy(mWindowManager); + } + if (mCompositor) { + wl_compositor_destroy(mCompositor); + } + if (mRegistry) { + wl_registry_destroy(mRegistry); + } + if (mDisplay) { + wl_display_flush(mDisplay); + wl_display_disconnect(mDisplay); + } + } + + void + WaylandConnection::bindGlobal(wl_registry* registry, uint32_t name, const std::string& interface, uint32_t version) { + if (interface == wl_compositor_interface.name) { + mCompositor = bindAs(registry, name, &wl_compositor_interface, version, 4); + } else if (interface == xdg_wm_base_interface.name) { + mWindowManager = bindAs(registry, name, &xdg_wm_base_interface, version, 5); + xdg_wm_base_add_listener(mWindowManager, &kWindowManagerListener, this); + } else if (interface == wl_seat_interface.name && mSeatName == 0) { + mSeatName = name; + mSeatVersion = version; + } else if (interface == wl_data_device_manager_interface.name) { + mDataDeviceManager = + bindAs(registry, name, &wl_data_device_manager_interface, version, 3); + } else if (interface == wp_viewporter_interface.name) { + mViewporter = bindAs(registry, name, &wp_viewporter_interface, version, 1); + } else if (interface == wp_fractional_scale_manager_v1_interface.name) { + mFractionalScaleManager = + bindAs(registry, name, &wp_fractional_scale_manager_v1_interface, version, 1); + } else if (interface == zwp_pointer_gestures_v1_interface.name) { + mPointerGestures = + bindAs(registry, name, &zwp_pointer_gestures_v1_interface, version, 1); + } else if (interface == wp_cursor_shape_manager_v1_interface.name) { + mCursorShapeManager = + bindAs(registry, name, &wp_cursor_shape_manager_v1_interface, version, 1); + } else if (interface == zwp_text_input_manager_v3_interface.name) { + mTextInputManager = + bindAs(registry, name, &zwp_text_input_manager_v3_interface, version, 1); + } else if (interface == zxdg_decoration_manager_v1_interface.name) { + mDecorationManager = + bindAs(registry, name, &zxdg_decoration_manager_v1_interface, version, 1); + } else if (interface == zwp_tablet_manager_v2_interface.name) { + mTabletManager = bindAs(registry, name, &zwp_tablet_manager_v2_interface, version, 1); + } else if (interface == xdg_activation_v1_interface.name) { + mActivation = bindAs(registry, name, &xdg_activation_v1_interface, version, 1); + } + } + + wl_seat* WaylandConnection::bindSeat(const void* listener, void* data) { + if (mSeat || mSeatName == 0) { + return mSeat; + } + + mSeat = bindAs(mRegistry, mSeatName, &wl_seat_interface, mSeatVersion, 8); + wl_seat_add_listener(mSeat, static_cast(listener), data); + return mSeat; + } + + bool WaylandConnection::dispatch(int timeoutMs) { + if (wl_display_dispatch_pending(mDisplay) > 0) { + flush(); + return true; + } + + while (wl_display_prepare_read(mDisplay) != 0) { + if (wl_display_dispatch_pending(mDisplay) > 0) { + return true; + } + } + + flush(); + + pollfd descriptor = { wl_display_get_fd(mDisplay), POLLIN, 0 }; + const int ready = poll(&descriptor, 1, timeoutMs); + if (ready <= 0) { + wl_display_cancel_read(mDisplay); + return false; + } + + if (wl_display_read_events(mDisplay) < 0) { + throw std::runtime_error("lost the Wayland connection"); + } + wl_display_dispatch_pending(mDisplay); + flush(); + return true; + } + + void WaylandConnection::roundtrip() { wl_display_roundtrip(mDisplay); } + + void WaylandConnection::flush() { + while (wl_display_flush(mDisplay) < 0 && errno == EAGAIN) { + pollfd descriptor = { wl_display_get_fd(mDisplay), POLLOUT, 0 }; + poll(&descriptor, 1, -1); + } + } +} diff --git a/GraphicApplication/src/backends/wayland/private/WaylandConnection.hpp b/GraphicApplication/src/backends/wayland/private/WaylandConnection.hpp new file mode 100644 index 00000000..3d4d152a --- /dev/null +++ b/GraphicApplication/src/backends/wayland/private/WaylandConnection.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include +#include + +struct wl_display; +struct wl_registry; +struct wl_compositor; +struct wl_seat; +struct wl_data_device_manager; +struct xdg_wm_base; +struct wp_viewporter; +struct wp_fractional_scale_manager_v1; +struct wp_cursor_shape_manager_v1; +struct zwp_pointer_gestures_v1; +struct zwp_text_input_manager_v3; +struct zxdg_decoration_manager_v1; +struct zwp_tablet_manager_v2; +struct xdg_activation_v1; + +namespace tp { + + class WaylandConnection { + public: + WaylandConnection(); + ~WaylandConnection(); + + WaylandConnection(const WaylandConnection&) = delete; + WaylandConnection& operator=(const WaylandConnection&) = delete; + + [[nodiscard]] wl_display* display() const { return mDisplay; } + [[nodiscard]] wl_compositor* compositor() const { return mCompositor; } + [[nodiscard]] xdg_wm_base* windowManager() const { return mWindowManager; } + [[nodiscard]] wl_seat* seat() const { return mSeat; } + [[nodiscard]] wl_seat* bindSeat(const void* listener, void* data); + [[nodiscard]] wl_data_device_manager* dataDeviceManager() const { return mDataDeviceManager; } + [[nodiscard]] wp_viewporter* viewporter() const { return mViewporter; } + [[nodiscard]] wp_fractional_scale_manager_v1* fractionalScaleManager() const { return mFractionalScaleManager; } + [[nodiscard]] wp_cursor_shape_manager_v1* cursorShapeManager() const { return mCursorShapeManager; } + [[nodiscard]] zwp_pointer_gestures_v1* pointerGestures() const { return mPointerGestures; } + [[nodiscard]] zwp_text_input_manager_v3* textInputManager() const { return mTextInputManager; } + [[nodiscard]] zxdg_decoration_manager_v1* decorationManager() const { return mDecorationManager; } + [[nodiscard]] zwp_tablet_manager_v2* tabletManager() const { return mTabletManager; } + [[nodiscard]] xdg_activation_v1* activation() const { return mActivation; } + + bool dispatch(int timeoutMs); + void roundtrip(); + void flush(); + + private: + friend struct WaylandRegistryListener; + + void bindGlobal(wl_registry* registry, uint32_t name, const std::string& interface, uint32_t version); + + wl_display* mDisplay = nullptr; + wl_registry* mRegistry = nullptr; + wl_compositor* mCompositor = nullptr; + xdg_wm_base* mWindowManager = nullptr; + wl_seat* mSeat = nullptr; + uint32_t mSeatName = 0; + uint32_t mSeatVersion = 0; + wl_data_device_manager* mDataDeviceManager = nullptr; + wp_viewporter* mViewporter = nullptr; + wp_fractional_scale_manager_v1* mFractionalScaleManager = nullptr; + wp_cursor_shape_manager_v1* mCursorShapeManager = nullptr; + zwp_pointer_gestures_v1* mPointerGestures = nullptr; + zwp_text_input_manager_v3* mTextInputManager = nullptr; + zxdg_decoration_manager_v1* mDecorationManager = nullptr; + zwp_tablet_manager_v2* mTabletManager = nullptr; + xdg_activation_v1* mActivation = nullptr; + }; +} diff --git a/GraphicApplication/src/backends/wayland/private/WaylandKeyboard.cpp b/GraphicApplication/src/backends/wayland/private/WaylandKeyboard.cpp new file mode 100644 index 00000000..eadc7d30 --- /dev/null +++ b/GraphicApplication/src/backends/wayland/private/WaylandKeyboard.cpp @@ -0,0 +1,262 @@ +#include "WaylandKeyboard.hpp" + +#include "LinuxInputCodes.hpp" + +#include + +#include +#include +#include +#include + +namespace tp { + + namespace { + WaylandKeyboard& keyboard(void* data) { return *static_cast(data); } + + void keymap(void* data, wl_keyboard*, uint32_t format, int32_t descriptor, uint32_t size) { + keyboard(data).onKeymap(format, descriptor, size); + } + void keyEnter(void* data, wl_keyboard*, uint32_t serial, wl_surface*, wl_array* keys) { + keyboard(data).onEnter(serial, keys); + } + void keyLeave(void* data, wl_keyboard*, uint32_t, wl_surface*) { keyboard(data).onLeave(); } + void key(void* data, wl_keyboard*, uint32_t serial, uint32_t, uint32_t key, uint32_t state) { + keyboard(data).onKey(serial, key, state); + } + void modifiers( + void* data, wl_keyboard*, uint32_t, uint32_t depressed, uint32_t latched, uint32_t locked, uint32_t group + ) { + keyboard(data).onModifiers(depressed, latched, locked, group); + } + void repeatInfo(void* data, wl_keyboard*, int32_t rate, int32_t delay) { keyboard(data).onRepeatInfo(rate, delay); } + + const wl_keyboard_listener kKeyboardListener = { keymap, keyEnter, keyLeave, key, modifiers, repeatInfo }; + + timespec millisecondsToTimespec(int32_t milliseconds) { + return { milliseconds / 1000, (long) (milliseconds % 1000) * 1000000L }; + } + } + + WaylandKeyboard::WaylandKeyboard(WaylandSeatContext& seat, wl_keyboard* keyboard) : + mSeat(seat), + mKeyboard(keyboard) { + mContext = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + mRepeatTimer = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK); + + const char* locale = std::getenv("LC_ALL"); + if (!locale || !*locale) { + locale = std::getenv("LC_CTYPE"); + } + if (!locale || !*locale) { + locale = std::getenv("LANG"); + } + if (!locale || !*locale) { + locale = "C"; + } + mComposeTable = xkb_compose_table_new_from_locale(mContext, locale, XKB_COMPOSE_COMPILE_NO_FLAGS); + if (mComposeTable) { + mComposeState = xkb_compose_state_new(mComposeTable, XKB_COMPOSE_STATE_NO_FLAGS); + } + + wl_keyboard_add_listener(mKeyboard, &kKeyboardListener, this); + } + + WaylandKeyboard::~WaylandKeyboard() { + if (mRepeatTimer >= 0) { + close(mRepeatTimer); + } + if (mComposeState) { + xkb_compose_state_unref(mComposeState); + } + if (mComposeTable) { + xkb_compose_table_unref(mComposeTable); + } + if (mState) { + xkb_state_unref(mState); + } + if (mKeymap) { + xkb_keymap_unref(mKeymap); + } + if (mContext) { + xkb_context_unref(mContext); + } + wl_keyboard_release(mKeyboard); + } + + void WaylandKeyboard::onKeymap(uint32_t format, int descriptor, uint32_t size) { + if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { + close(descriptor); + return; + } + + void* mapped = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, descriptor, 0); + close(descriptor); + if (mapped == MAP_FAILED) { + return; + } + + xkb_keymap* keymap = xkb_keymap_new_from_string( + mContext, static_cast(mapped), XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS + ); + munmap(mapped, size); + if (!keymap) { + return; + } + + if (mState) { + xkb_state_unref(mState); + } + if (mKeymap) { + xkb_keymap_unref(mKeymap); + } + mKeymap = keymap; + mState = xkb_state_new(mKeymap); + } + + void WaylandKeyboard::onEnter(uint32_t serial, wl_array* keys) { + mSeat.recordSerial(serial); + mSeat.input().post(KeyboardFocusChanged{ true }); + + const auto* pressed = static_cast(keys->data); + const size_t count = keys->size / sizeof(uint32_t); + for (size_t index = 0; index < count; index++) { + if (const std::optional key = keyFromEvdev(pressed[index])) { + mSeat.input().post(KeyChanged{ *key, KeyAction::Press }); + } + } + } + + void WaylandKeyboard::onLeave() { + disarmRepeat(); + if (mComposeState) { + xkb_compose_state_reset(mComposeState); + } + mSeat.input().post(KeyboardFocusChanged{ false }); + } + + void WaylandKeyboard::onKey(uint32_t serial, uint32_t key, uint32_t state) { + mSeat.recordSerial(serial); + + if (state == WL_KEYBOARD_KEY_STATE_PRESSED) { + pressKey(key); + } else { + releaseKey(key); + } + } + + void WaylandKeyboard::pressKey(uint32_t key) { + if (const std::optional id = keyFromEvdev(key)) { + mSeat.input().post(KeyChanged{ *id, KeyAction::Press }); + } + + emitText(key); + + if (mKeymap && xkb_keymap_key_repeats(mKeymap, key + 8)) { + armRepeat(key); + } + } + + void WaylandKeyboard::releaseKey(uint32_t key) { + if (key == mRepeatKey) { + disarmRepeat(); + } + + if (const std::optional id = keyFromEvdev(key)) { + mSeat.input().post(KeyChanged{ *id, KeyAction::Release }); + } + } + + void WaylandKeyboard::emitText(uint32_t key) { + if (!mState) { + return; + } + + const xkb_keysym_t symbol = composed(xkb_state_key_get_one_sym(mState, key + 8)); + if (symbol == XKB_KEY_NoSymbol) { + return; + } + + char utf8[64] = {}; + if (mComposeState && xkb_compose_state_get_status(mComposeState) == XKB_COMPOSE_COMPOSED) { + xkb_compose_state_get_utf8(mComposeState, utf8, sizeof(utf8)); + xkb_compose_state_reset(mComposeState); + } else { + xkb_state_key_get_utf8(mState, key + 8, utf8, sizeof(utf8)); + } + + const unsigned char first = (unsigned char) utf8[0]; + if (first == 0 || first < 0x20 || first == 0x7f) { + return; + } + mSeat.input().post(TextEntered{ utf8 }); + } + + xkb_keysym_t WaylandKeyboard::composed(xkb_keysym_t symbol) { + if (!mComposeState || symbol == XKB_KEY_NoSymbol) { + return symbol; + } + if (xkb_compose_state_feed(mComposeState, symbol) != XKB_COMPOSE_FEED_ACCEPTED) { + return symbol; + } + + switch (xkb_compose_state_get_status(mComposeState)) { + case XKB_COMPOSE_COMPOSING: return XKB_KEY_NoSymbol; + case XKB_COMPOSE_CANCELLED: xkb_compose_state_reset(mComposeState); return XKB_KEY_NoSymbol; + case XKB_COMPOSE_COMPOSED: return xkb_compose_state_get_one_sym(mComposeState); + case XKB_COMPOSE_NOTHING: return symbol; + } + return symbol; + } + + void WaylandKeyboard::onModifiers(uint32_t depressed, uint32_t latched, uint32_t locked, uint32_t group) { + if (mState) { + xkb_state_update_mask(mState, depressed, latched, locked, 0, 0, group); + } + } + + void WaylandKeyboard::onRepeatInfo(int32_t rate, int32_t delay) { + mRepeatRate = rate; + mRepeatDelay = delay; + } + + void WaylandKeyboard::armRepeat(uint32_t key) { + if (mRepeatTimer < 0 || mRepeatRate <= 0) { + return; + } + + mRepeatKey = key; + itimerspec timer = {}; + timer.it_value = millisecondsToTimespec(mRepeatDelay); + timer.it_interval = millisecondsToTimespec(1000 / mRepeatRate); + timerfd_settime(mRepeatTimer, 0, &timer, nullptr); + } + + void WaylandKeyboard::disarmRepeat() { + mRepeatKey = 0; + if (mRepeatTimer < 0) { + return; + } + + itimerspec timer = {}; + timerfd_settime(mRepeatTimer, 0, &timer, nullptr); + } + + void WaylandKeyboard::onRepeatTimer() { + uint64_t expirations = 0; + if (read(mRepeatTimer, &expirations, sizeof(expirations)) != sizeof(expirations)) { + return; + } + if (mRepeatKey == 0) { + return; + } + + const std::optional id = keyFromEvdev(mRepeatKey); + for (uint64_t repeat = 0; repeat < expirations; repeat++) { + if (id) { + mSeat.input().post(KeyChanged{ *id, KeyAction::Repeat }); + } + emitText(mRepeatKey); + } + } +} diff --git a/GraphicApplication/src/backends/wayland/private/WaylandKeyboard.hpp b/GraphicApplication/src/backends/wayland/private/WaylandKeyboard.hpp new file mode 100644 index 00000000..24c4e092 --- /dev/null +++ b/GraphicApplication/src/backends/wayland/private/WaylandKeyboard.hpp @@ -0,0 +1,54 @@ +#pragma once + +#include "WaylandSeatContext.hpp" + +#include +#include + +#include + +struct wl_keyboard; +struct wl_array; + +namespace tp { + + // wl_keyboard with xkb keymap, compose sequences and client-side key repeat. + class WaylandKeyboard { + public: + WaylandKeyboard(WaylandSeatContext& seat, wl_keyboard* keyboard); + ~WaylandKeyboard(); + + WaylandKeyboard(const WaylandKeyboard&) = delete; + WaylandKeyboard& operator=(const WaylandKeyboard&) = delete; + + [[nodiscard]] int repeatTimerDescriptor() const { return mRepeatTimer; } + void onRepeatTimer(); + + void onKeymap(uint32_t format, int descriptor, uint32_t size); + void onEnter(uint32_t serial, wl_array* keys); + void onLeave(); + void onKey(uint32_t serial, uint32_t key, uint32_t state); + void onModifiers(uint32_t depressed, uint32_t latched, uint32_t locked, uint32_t group); + void onRepeatInfo(int32_t rate, int32_t delay); + + private: + void pressKey(uint32_t key); + void releaseKey(uint32_t key); + void emitText(uint32_t key); + void armRepeat(uint32_t key); + void disarmRepeat(); + [[nodiscard]] xkb_keysym_t composed(xkb_keysym_t symbol); + + WaylandSeatContext& mSeat; + wl_keyboard* mKeyboard; + xkb_context* mContext = nullptr; + xkb_keymap* mKeymap = nullptr; + xkb_state* mState = nullptr; + xkb_compose_table* mComposeTable = nullptr; + xkb_compose_state* mComposeState = nullptr; + int mRepeatTimer = -1; + int32_t mRepeatRate = 25; + int32_t mRepeatDelay = 600; + uint32_t mRepeatKey = 0; + }; +} diff --git a/GraphicApplication/src/backends/wayland/private/WaylandPointer.cpp b/GraphicApplication/src/backends/wayland/private/WaylandPointer.cpp new file mode 100644 index 00000000..2298ec15 --- /dev/null +++ b/GraphicApplication/src/backends/wayland/private/WaylandPointer.cpp @@ -0,0 +1,231 @@ +#include "WaylandPointer.hpp" + +#include "WaylandConnection.hpp" + +#include "cursor-shape-v1-client-protocol.h" +#include "pointer-gestures-unstable-v1-client-protocol.h" + +namespace tp { + + namespace { + constexpr halnf kWheelStep = 10; + + WaylandPointer& pointer(void* data) { return *static_cast(data); } + + void pointerEnter(void* data, wl_pointer*, uint32_t serial, wl_surface*, wl_fixed_t x, wl_fixed_t y) { + pointer(data).onEnter(serial, x, y); + } + void pointerLeave(void* data, wl_pointer*, uint32_t, wl_surface*) { pointer(data).onLeave(); } + void pointerMotion(void* data, wl_pointer*, uint32_t, wl_fixed_t x, wl_fixed_t y) { pointer(data).onMotion(x, y); } + void pointerButton(void* data, wl_pointer*, uint32_t serial, uint32_t, uint32_t button, uint32_t state) { + pointer(data).onButton(serial, button, state); + } + void pointerAxis(void* data, wl_pointer*, uint32_t, uint32_t axis, wl_fixed_t value) { + pointer(data).onAxis(axis, value); + } + void pointerFrame(void* data, wl_pointer*) { pointer(data).onFrame(); } + void pointerAxisSource(void* data, wl_pointer*, uint32_t source) { pointer(data).onAxisSource(source); } + void pointerAxisStop(void* data, wl_pointer*, uint32_t, uint32_t) { pointer(data).onAxisStop(); } + void pointerAxisDiscrete(void* data, wl_pointer*, uint32_t axis, int32_t steps) { + pointer(data).onAxisDiscrete(axis, steps); + } + void pointerAxisValue120(void*, wl_pointer*, uint32_t, int32_t) {} + void pointerAxisRelativeDirection(void*, wl_pointer*, uint32_t, uint32_t) {} + + const wl_pointer_listener kPointerListener = { + pointerEnter, + pointerLeave, + pointerMotion, + pointerButton, + pointerAxis, + pointerFrame, + pointerAxisSource, + pointerAxisStop, + pointerAxisDiscrete, + pointerAxisValue120, + pointerAxisRelativeDirection, + }; + + void pinchBegin(void* data, zwp_pointer_gesture_pinch_v1*, uint32_t, uint32_t, wl_surface*, uint32_t) { + pointer(data).onPinchBegin(); + } + void pinchUpdate( + void* data, + zwp_pointer_gesture_pinch_v1*, + uint32_t, + wl_fixed_t dx, + wl_fixed_t dy, + wl_fixed_t scale, + wl_fixed_t rotation + ) { + pointer(data).onPinchUpdate(dx, dy, scale, rotation); + } + void pinchEnd(void* data, zwp_pointer_gesture_pinch_v1*, uint32_t, uint32_t, int32_t) { + pointer(data).onPinchEnd(); + } + + const zwp_pointer_gesture_pinch_v1_listener kPinchListener = { pinchBegin, pinchUpdate, pinchEnd }; + } + + uint32_t cursorShapeCode(CursorShape shape) { + switch (shape) { + case CursorShape::Default: return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT; + case CursorShape::Pointer: return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER; + case CursorShape::Text: return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_TEXT; + case CursorShape::Move: return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_MOVE; + case CursorShape::Grab: return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_GRAB; + case CursorShape::Grabbing: return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_GRABBING; + case CursorShape::ResizeHorizontal: return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_EW_RESIZE; + case CursorShape::ResizeVertical: return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NS_RESIZE; + case CursorShape::ResizeNorthWestSouthEast: return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NWSE_RESIZE; + case CursorShape::ResizeNorthEastSouthWest: return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NESW_RESIZE; + case CursorShape::NotAllowed: return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NOT_ALLOWED; + case CursorShape::Crosshair: return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CROSSHAIR; + case CursorShape::Hidden: return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT; + } + return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT; + } + + WaylandPointer::WaylandPointer(WaylandSeatContext& seat, wl_pointer* pointer) : + mSeat(seat), + mPointer(pointer) { + wl_pointer_add_listener(mPointer, &kPointerListener, this); + + wp_cursor_shape_manager_v1* manager = seat.connection().cursorShapeManager(); + if (manager) { + mShapeDevice = wp_cursor_shape_manager_v1_get_pointer(manager, mPointer); + } + + zwp_pointer_gestures_v1* gestures = seat.connection().pointerGestures(); + if (gestures) { + mPinch = zwp_pointer_gestures_v1_get_pinch_gesture(gestures, mPointer); + zwp_pointer_gesture_pinch_v1_add_listener(mPinch, &kPinchListener, this); + } + } + + WaylandPointer::~WaylandPointer() { + if (mPinch) { + zwp_pointer_gesture_pinch_v1_destroy(mPinch); + } + if (mShapeDevice) { + wp_cursor_shape_device_v1_destroy(mShapeDevice); + } + wl_pointer_release(mPointer); + } + + void WaylandPointer::onPinchBegin() { + mSeat.surfacePointer().onPointerSurfacePinch(mPinchConverter.begin(mSurfacePosition)); + } + + void WaylandPointer::onPinchUpdate(wl_fixed_t dx, wl_fixed_t dy, wl_fixed_t scale, wl_fixed_t rotation) { + const Vec2F pan = { (halnf) wl_fixed_to_double(dx), (halnf) wl_fixed_to_double(dy) }; + const std::optional gesture = mPinchConverter.update( + mSurfacePosition, pan, (halnf) wl_fixed_to_double(scale), (halnf) wl_fixed_to_double(rotation) + ); + if (gesture) { + mSeat.surfacePointer().onPointerSurfacePinch(*gesture); + } + } + + void WaylandPointer::onPinchEnd() { + if (const std::optional gesture = mPinchConverter.end(mSurfacePosition)) { + mSeat.surfacePointer().onPointerSurfacePinch(*gesture); + } + } + + void WaylandPointer::onEnter(uint32_t serial, wl_fixed_t x, wl_fixed_t y) { + mEnterSerial = serial; + mInside = true; + mSurfacePosition = { (halnf) wl_fixed_to_double(x), (halnf) wl_fixed_to_double(y) }; + mShapeApplied = false; + mSeat.surfacePointer().onPointerSurfaceMove({ (halnf) wl_fixed_to_double(x), (halnf) wl_fixed_to_double(y) }); + applyCursor(); + } + + void WaylandPointer::onLeave() { + mInside = false; + mSeat.surfacePointer().onPointerSurfaceLeft(); + } + + void WaylandPointer::onMotion(wl_fixed_t x, wl_fixed_t y) { + mSurfacePosition = { (halnf) wl_fixed_to_double(x), (halnf) wl_fixed_to_double(y) }; + mSeat.input().post(PointerSampled{}); + mSeat.surfacePointer().onPointerSurfaceMove(mSurfacePosition); + } + + void WaylandPointer::onButton(uint32_t serial, uint32_t button, uint32_t state) { + mSeat.recordSerial(serial); + mSeat.input().post(PointerSampled{}); + mSeat.surfacePointer().onPointerSurfaceButton(button, state == WL_POINTER_BUTTON_STATE_PRESSED, serial); + } + + void WaylandPointer::onAxis(uint32_t axis, wl_fixed_t value) { + if (mDiscreteScroll) { + return; + } + + const halnf amount = (halnf) -wl_fixed_to_double(value) / kWheelStep; + if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) { + mPendingScroll.y += amount; + } else { + mPendingScroll.x += amount; + } + } + + void WaylandPointer::onAxisDiscrete(uint32_t axis, int32_t steps) { + mDiscreteScroll = true; + if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) { + mPendingScroll.y -= (halnf) steps; + } else { + mPendingScroll.x -= (halnf) steps; + } + } + + void WaylandPointer::onAxisSource(uint32_t source) { + const bool continuous = source == WL_POINTER_AXIS_SOURCE_FINGER || source == WL_POINTER_AXIS_SOURCE_CONTINUOUS; + mScrollSource = continuous ? ScrollSource::Touchpad : ScrollSource::Wheel; + } + + void WaylandPointer::onAxisStop() { mScrollStopped = true; } + + void WaylandPointer::onFrame() { + InputSink& input = mSeat.input(); + if (mPendingScroll.x != 0 || mPendingScroll.y != 0) { + input.post(Scrolled{ mPendingScroll, mScrollSource }); + } + if (mScrollStopped) { + input.post(ScrollEnded{}); + } + mPendingScroll = { 0, 0 }; + mDiscreteScroll = false; + mScrollStopped = false; + mScrollSource = ScrollSource::Wheel; + } + + void WaylandPointer::setCursor(CursorShape shape) { + if (shape == mShape && mShapeApplied) { + return; + } + + mShape = shape; + mShapeApplied = false; + applyCursor(); + } + + void WaylandPointer::applyCursor() { + if (!mInside || mShapeApplied) { + return; + } + if (mShape == CursorShape::Hidden) { + wl_pointer_set_cursor(mPointer, mEnterSerial, nullptr, 0, 0); + mShapeApplied = true; + return; + } + if (!mShapeDevice) { + return; + } + + wp_cursor_shape_device_v1_set_shape(mShapeDevice, mEnterSerial, cursorShapeCode(mShape)); + mShapeApplied = true; + } +} diff --git a/GraphicApplication/src/backends/wayland/private/WaylandPointer.hpp b/GraphicApplication/src/backends/wayland/private/WaylandPointer.hpp new file mode 100644 index 00000000..8c2a20cf --- /dev/null +++ b/GraphicApplication/src/backends/wayland/private/WaylandPointer.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include "PointerPinchConverter.hpp" +#include "WaylandSeatContext.hpp" +#include "WindowTypes.hpp" + +#include + +struct wp_cursor_shape_device_v1; +struct zwp_pointer_gesture_pinch_v1; + +namespace tp { + + // wl_pointer with cursor shapes, frame-grouped scrolling and touchpad pinches. + class WaylandPointer { + public: + WaylandPointer(WaylandSeatContext& seat, wl_pointer* pointer); + ~WaylandPointer(); + + WaylandPointer(const WaylandPointer&) = delete; + WaylandPointer& operator=(const WaylandPointer&) = delete; + + void setCursor(CursorShape shape); + + void onEnter(uint32_t serial, wl_fixed_t x, wl_fixed_t y); + void onLeave(); + void onMotion(wl_fixed_t x, wl_fixed_t y); + void onButton(uint32_t serial, uint32_t button, uint32_t state); + void onAxis(uint32_t axis, wl_fixed_t value); + void onAxisDiscrete(uint32_t axis, int32_t steps); + void onAxisSource(uint32_t source); + void onAxisStop(); + void onFrame(); + + void onPinchBegin(); + void onPinchUpdate(wl_fixed_t dx, wl_fixed_t dy, wl_fixed_t scale, wl_fixed_t rotation); + void onPinchEnd(); + + private: + void applyCursor(); + + WaylandSeatContext& mSeat; + wl_pointer* mPointer; + zwp_pointer_gesture_pinch_v1* mPinch = nullptr; + PointerPinchConverter mPinchConverter; + Vec2F mSurfacePosition = { 0, 0 }; + wp_cursor_shape_device_v1* mShapeDevice = nullptr; + uint32_t mEnterSerial = 0; + bool mInside = false; + CursorShape mShape = CursorShape::Default; + bool mShapeApplied = false; + Vec2F mPendingScroll = { 0, 0 }; + bool mDiscreteScroll = false; + ScrollSource mScrollSource = ScrollSource::Wheel; + bool mScrollStopped = false; + }; + + [[nodiscard]] uint32_t cursorShapeCode(CursorShape shape); +} diff --git a/GraphicApplication/src/backends/wayland/private/WaylandSeat.cpp b/GraphicApplication/src/backends/wayland/private/WaylandSeat.cpp new file mode 100644 index 00000000..c699787d --- /dev/null +++ b/GraphicApplication/src/backends/wayland/private/WaylandSeat.cpp @@ -0,0 +1,113 @@ +#include "WaylandSeat.hpp" + +#include "WaylandClipboard.hpp" +#include "WaylandConnection.hpp" +#include "WaylandKeyboard.hpp" +#include "WaylandPointer.hpp" +#include "WaylandTablet.hpp" +#include "WaylandTextInput.hpp" + +#include + +namespace tp { + + namespace { + void capabilities(void* data, wl_seat*, uint32_t capabilities) { + static_cast(data)->onCapabilities(capabilities); + } + + void name(void*, wl_seat*, const char*) {} + + const wl_seat_listener kSeatListener = { capabilities, name }; + } + + WaylandSeat::WaylandSeat( + WaylandConnection& connection, PlatformWindowHost& host, WaylandSurfacePointer& surfacePointer + ) : + mContext(connection, host, surfacePointer) { + if (!connection.bindSeat(&kSeatListener, this)) { + return; + } + + if (connection.dataDeviceManager()) { + mClipboard = std::make_unique(mContext, connection.dataDeviceManager(), connection.seat()); + } + if (connection.textInputManager()) { + mTextInput = std::make_unique(mContext, connection.textInputManager(), connection.seat()); + } + if (connection.tabletManager()) { + mTablet = std::make_unique(mContext, connection.tabletManager(), connection.seat()); + } + } + + WaylandSeat::~WaylandSeat() = default; + + void WaylandSeat::onCapabilities(uint32_t capabilities) { + const bool hasKeyboard = capabilities & WL_SEAT_CAPABILITY_KEYBOARD; + const bool hasPointer = capabilities & WL_SEAT_CAPABILITY_POINTER; + mHasTouch = capabilities & WL_SEAT_CAPABILITY_TOUCH; + wl_seat* seat = mContext.connection().seat(); + + if (hasKeyboard && !mKeyboard) { + mKeyboard = std::make_unique(mContext, wl_seat_get_keyboard(seat)); + } + if (!hasKeyboard) { + mKeyboard.reset(); + } + + if (hasPointer && !mPointer) { + mPointer = std::make_unique(mContext, wl_seat_get_pointer(seat)); + } + if (!hasPointer) { + mPointer.reset(); + } + } + + void WaylandSeat::collectPollDescriptors(std::vector& descriptors) const { + if (mKeyboard && mKeyboard->repeatTimerDescriptor() >= 0) { + descriptors.push_back({ mKeyboard->repeatTimerDescriptor(), POLLIN, 0 }); + } + if (mClipboard) { + mClipboard->collectPollDescriptors(descriptors); + } + } + + void WaylandSeat::onDescriptorsReady(const std::vector& descriptors) { + for (const pollfd& ready : descriptors) { + if (mKeyboard && ready.fd == mKeyboard->repeatTimerDescriptor() && (ready.revents & POLLIN)) { + mKeyboard->onRepeatTimer(); + } + } + if (mClipboard) { + mClipboard->onDescriptorsReady(descriptors); + } + } + + void WaylandSeat::setCursor(CursorShape shape) { + if (mPointer) { + mPointer->setCursor(shape); + } + } + + void WaylandSeat::setClipboardText(const std::string& text) { + if (mClipboard) { + mClipboard->setText(text); + } + } + + std::string WaylandSeat::clipboardText() const { return mClipboard ? mClipboard->text() : std::string(); } + + ClipboardImage WaylandSeat::clipboardImage() const { return mClipboard ? mClipboard->image() : ClipboardImage{}; } + + void WaylandSeat::setTextInputEnabled(bool enabled) { + if (mTextInput) { + mTextInput->setEnabled(enabled); + } + } + + void WaylandSeat::setTextInputRect(const RectF& surfaceRect) { + if (mTextInput) { + mTextInput->setCursorRect(surfaceRect); + } + } +} diff --git a/GraphicApplication/src/backends/wayland/private/WaylandSeat.hpp b/GraphicApplication/src/backends/wayland/private/WaylandSeat.hpp new file mode 100644 index 00000000..df8cd50c --- /dev/null +++ b/GraphicApplication/src/backends/wayland/private/WaylandSeat.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include "Rect.hpp" +#include "WaylandSeatContext.hpp" +#include "WindowTypes.hpp" + +#include + +#include +#include +#include +#include + +namespace tp { + + class WaylandClipboard; + class WaylandKeyboard; + class WaylandPointer; + class WaylandTablet; + class WaylandTextInput; + + // The wl_seat and the input devices it currently offers. + class WaylandSeat { + public: + WaylandSeat(WaylandConnection& connection, PlatformWindowHost& host, WaylandSurfacePointer& surfacePointer); + ~WaylandSeat(); + + WaylandSeat(const WaylandSeat&) = delete; + WaylandSeat& operator=(const WaylandSeat&) = delete; + + void collectPollDescriptors(std::vector& descriptors) const; + void onDescriptorsReady(const std::vector& descriptors); + + [[nodiscard]] uint32_t lastInputSerial() const { return mContext.lastInputSerial(); } + + void setCursor(CursorShape shape); + + void setClipboardText(const std::string& text); + [[nodiscard]] std::string clipboardText() const; + [[nodiscard]] ClipboardImage clipboardImage() const; + + void setTextInputEnabled(bool enabled); + void setTextInputRect(const RectF& surfaceRect); + + [[nodiscard]] bool hasPointer() const { return mPointer != nullptr; } + [[nodiscard]] bool hasKeyboard() const { return mKeyboard != nullptr; } + [[nodiscard]] bool hasTouch() const { return mHasTouch; } + [[nodiscard]] bool hasTablet() const { return mTablet != nullptr; } + + void onCapabilities(uint32_t capabilities); + + private: + WaylandSeatContext mContext; + std::unique_ptr mKeyboard; + std::unique_ptr mPointer; + std::unique_ptr mClipboard; + std::unique_ptr mTextInput; + std::unique_ptr mTablet; + bool mHasTouch = false; + }; +} diff --git a/GraphicApplication/src/backends/wayland/private/WaylandSeatContext.hpp b/GraphicApplication/src/backends/wayland/private/WaylandSeatContext.hpp new file mode 100644 index 00000000..bc7f9c11 --- /dev/null +++ b/GraphicApplication/src/backends/wayland/private/WaylandSeatContext.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include "PlatformWindow.hpp" +#include "TouchGesture.hpp" + +#include + +namespace tp { + + class WaylandConnection; + + // Pointer-like devices report in surface coordinates; the window maps them to content or decoration edges. + class WaylandSurfacePointer { + public: + virtual ~WaylandSurfacePointer() = default; + + virtual void onPointerSurfaceMove(Vec2F surfacePoint) = 0; + virtual void onPointerSurfaceButton(uint32_t linuxButton, bool pressed, uint32_t serial) = 0; + virtual void onPointerSurfaceLeft() = 0; + virtual void onPointerSurfacePinch(const TouchGesture& gesture) = 0; + }; + + // What every seat device shares: the connection, the raw input entry, surface pointer routing and the last serial. + class WaylandSeatContext { + public: + WaylandSeatContext(WaylandConnection& connection, PlatformWindowHost& host, WaylandSurfacePointer& surfacePointer) : + mConnection(connection), + mHost(host), + mSurfacePointer(surfacePointer) {} + + [[nodiscard]] WaylandConnection& connection() const { return mConnection; } + [[nodiscard]] InputSink& input() const { return mHost.rawInput(); } + [[nodiscard]] WaylandSurfacePointer& surfacePointer() const { return mSurfacePointer; } + + void recordSerial(uint32_t serial) { mLastInputSerial = serial; } + [[nodiscard]] uint32_t lastInputSerial() const { return mLastInputSerial; } + + private: + WaylandConnection& mConnection; + PlatformWindowHost& mHost; + WaylandSurfacePointer& mSurfacePointer; + uint32_t mLastInputSerial = 0; + }; +} diff --git a/GraphicApplication/src/backends/wayland/private/WaylandTablet.cpp b/GraphicApplication/src/backends/wayland/private/WaylandTablet.cpp new file mode 100644 index 00000000..1fd458c4 --- /dev/null +++ b/GraphicApplication/src/backends/wayland/private/WaylandTablet.cpp @@ -0,0 +1,210 @@ +#include "WaylandTablet.hpp" + +#include "WaylandConnection.hpp" + +#include "cursor-shape-v1-client-protocol.h" +#include "tablet-v2-client-protocol.h" + +#include + +#include + +#include + +namespace tp { + + namespace { + WaylandTablet& tablet(void* data) { return *static_cast(data); } + WaylandTablet::Tool& tool(void* data) { return *static_cast(data); } + + void tabletAdded(void* data, zwp_tablet_seat_v2*, zwp_tablet_v2* id) { tablet(data).onTabletAdded(id); } + void toolAdded(void* data, zwp_tablet_seat_v2*, zwp_tablet_tool_v2* id) { tablet(data).onToolAdded(id); } + void padAdded(void* data, zwp_tablet_seat_v2*, zwp_tablet_pad_v2* id) { tablet(data).onPadAdded(id); } + + void tabletName(void*, zwp_tablet_v2*, const char*) {} + void tabletId(void*, zwp_tablet_v2*, uint32_t, uint32_t) {} + void tabletPath(void*, zwp_tablet_v2*, const char*) {} + void tabletDone(void*, zwp_tablet_v2*) {} + void tabletRemoved(void*, zwp_tablet_v2*) {} + void tabletBustype(void*, zwp_tablet_v2*, uint32_t) {} + + void toolType(void* data, zwp_tablet_tool_v2*, uint32_t type) { tool(data).owner.onToolType(tool(data), type); } + void toolSerial(void*, zwp_tablet_tool_v2*, uint32_t, uint32_t) {} + void toolWacomId(void*, zwp_tablet_tool_v2*, uint32_t, uint32_t) {} + void toolCapability(void*, zwp_tablet_tool_v2*, uint32_t) {} + void toolDone(void*, zwp_tablet_tool_v2*) {} + void toolRemoved(void* data, zwp_tablet_tool_v2*) { tool(data).owner.onToolRemoved(tool(data)); } + void toolProximityIn(void* data, zwp_tablet_tool_v2*, uint32_t serial, zwp_tablet_v2*, wl_surface*) { + tool(data).owner.onProximityIn(tool(data), serial); + } + void toolProximityOut(void* data, zwp_tablet_tool_v2*) { tool(data).owner.onProximityOut(tool(data)); } + void toolDown(void* data, zwp_tablet_tool_v2*, uint32_t serial) { + tool(data).owner.recordSerial(serial); + tool(data).contactChange = 1; + } + void toolUp(void* data, zwp_tablet_tool_v2*) { tool(data).contactChange = -1; } + void toolMotion(void* data, zwp_tablet_tool_v2*, wl_fixed_t x, wl_fixed_t y) { + tool(data).position = { (halnf) wl_fixed_to_double(x), (halnf) wl_fixed_to_double(y) }; + tool(data).moved = true; + } + void toolPressure(void* data, zwp_tablet_tool_v2*, uint32_t pressure) { + tool(data).sample.pressure = (halnf) pressure / 65535.0f; + } + void toolDistance(void* data, zwp_tablet_tool_v2*, uint32_t distance) { + tool(data).sample.distance = (halnf) distance / 65535.0f; + } + void toolTilt(void* data, zwp_tablet_tool_v2*, wl_fixed_t x, wl_fixed_t y) { + tool(data).sample.tilt = { (halnf) wl_fixed_to_double(x), (halnf) wl_fixed_to_double(y) }; + } + void toolRotation(void* data, zwp_tablet_tool_v2*, wl_fixed_t degrees) { + tool(data).sample.rotation = (halnf) wl_fixed_to_double(degrees); + } + void toolSlider(void*, zwp_tablet_tool_v2*, int32_t) {} + void toolWheel(void*, zwp_tablet_tool_v2*, wl_fixed_t, int32_t) {} + void toolButton(void* data, zwp_tablet_tool_v2*, uint32_t serial, uint32_t button, uint32_t state) { + tool(data).owner.recordSerial(serial); + const bool pressed = state == ZWP_TABLET_TOOL_V2_BUTTON_STATE_PRESSED; + if (button == BTN_STYLUS) { + tool(data).linuxButtons.push_back({ BTN_RIGHT, pressed }); + } else if (button == BTN_STYLUS2) { + tool(data).linuxButtons.push_back({ BTN_MIDDLE, pressed }); + } + } + void toolFrame(void* data, zwp_tablet_tool_v2*, uint32_t time) { tool(data).owner.onFrame(tool(data), time); } + + void padGroup(void*, zwp_tablet_pad_v2*, zwp_tablet_pad_group_v2*) {} + void padPath(void*, zwp_tablet_pad_v2*, const char*) {} + void padButtons(void*, zwp_tablet_pad_v2*, uint32_t) {} + void padDone(void*, zwp_tablet_pad_v2*) {} + void padButton(void*, zwp_tablet_pad_v2*, uint32_t, uint32_t, uint32_t) {} + void padEnter(void*, zwp_tablet_pad_v2*, uint32_t, zwp_tablet_v2*, wl_surface*) {} + void padLeave(void*, zwp_tablet_pad_v2*, uint32_t, wl_surface*) {} + void padRemoved(void*, zwp_tablet_pad_v2*) {} + + const zwp_tablet_seat_v2_listener kTabletSeatListener = { + tabletAdded, + toolAdded, + padAdded, + }; + + const zwp_tablet_v2_listener kTabletListener = { + tabletName, tabletId, tabletPath, tabletDone, tabletRemoved, tabletBustype, + }; + + const zwp_tablet_tool_v2_listener kToolListener = { + toolType, toolSerial, toolWacomId, toolCapability, toolDone, toolRemoved, toolProximityIn, + toolProximityOut, toolDown, toolUp, toolMotion, toolPressure, toolDistance, toolTilt, + toolRotation, toolSlider, toolWheel, toolButton, toolFrame, + }; + + const zwp_tablet_pad_v2_listener kPadListener = { + padGroup, padPath, padButtons, padDone, padButton, padEnter, padLeave, padRemoved, + }; + } + + WaylandTablet::WaylandTablet(WaylandSeatContext& seat, zwp_tablet_manager_v2* manager, wl_seat* waylandSeat) : + mSeat(seat) { + mTabletSeat = zwp_tablet_manager_v2_get_tablet_seat(manager, waylandSeat); + zwp_tablet_seat_v2_add_listener(mTabletSeat, &kTabletSeatListener, this); + } + + WaylandTablet::~WaylandTablet() { + for (const auto& tool : mTools) { + if (tool->shapeDevice) { + wp_cursor_shape_device_v1_destroy(tool->shapeDevice); + } + zwp_tablet_tool_v2_destroy(tool->handle); + } + for (zwp_tablet_pad_v2* pad : mPads) { + zwp_tablet_pad_v2_destroy(pad); + } + for (zwp_tablet_v2* tablet : mTablets) { + zwp_tablet_v2_destroy(tablet); + } + zwp_tablet_seat_v2_destroy(mTabletSeat); + } + + void WaylandTablet::onTabletAdded(zwp_tablet_v2* tablet) { + mTablets.push_back(tablet); + zwp_tablet_v2_add_listener(tablet, &kTabletListener, this); + } + + void WaylandTablet::onPadAdded(zwp_tablet_pad_v2* pad) { + mPads.push_back(pad); + zwp_tablet_pad_v2_add_listener(pad, &kPadListener, this); + } + + void WaylandTablet::onToolAdded(zwp_tablet_tool_v2* handle) { + mTools.push_back(std::make_unique(Tool{ *this, handle })); + Tool& tool = *mTools.back(); + zwp_tablet_tool_v2_add_listener(handle, &kToolListener, &tool); + + wp_cursor_shape_manager_v1* manager = mSeat.connection().cursorShapeManager(); + if (manager) { + tool.shapeDevice = wp_cursor_shape_manager_v1_get_tablet_tool_v2(manager, handle); + } + } + + void WaylandTablet::onToolRemoved(Tool& tool) { + if (tool.inside) { + onProximityOut(tool); + } + if (tool.shapeDevice) { + wp_cursor_shape_device_v1_destroy(tool.shapeDevice); + } + zwp_tablet_tool_v2_destroy(tool.handle); + std::erase_if(mTools, [&tool](const std::unique_ptr& entry) { return entry.get() == &tool; }); + } + + void WaylandTablet::onToolType(Tool& tool, uint32_t type) { + tool.device = PointerDevice::Stylus; + tool.tip = StylusTip::Pen; + switch (type) { + case ZWP_TABLET_TOOL_V2_TYPE_ERASER: tool.tip = StylusTip::Eraser; break; + case ZWP_TABLET_TOOL_V2_TYPE_BRUSH: tool.tip = StylusTip::Brush; break; + case ZWP_TABLET_TOOL_V2_TYPE_PENCIL: tool.tip = StylusTip::Pencil; break; + case ZWP_TABLET_TOOL_V2_TYPE_AIRBRUSH: tool.tip = StylusTip::Airbrush; break; + case ZWP_TABLET_TOOL_V2_TYPE_FINGER: tool.device = PointerDevice::Finger; break; + case ZWP_TABLET_TOOL_V2_TYPE_MOUSE: + case ZWP_TABLET_TOOL_V2_TYPE_LENS: tool.device = PointerDevice::Mouse; break; + default: break; + } + tool.sample.device = tool.device; + tool.sample.tip = tool.tip; + } + + void WaylandTablet::onProximityIn(Tool& tool, uint32_t serial) { + tool.inside = true; + tool.sample = PointerSample{ tool.device, tool.tip, 0 }; + if (tool.shapeDevice) { + wp_cursor_shape_device_v1_set_shape(tool.shapeDevice, serial, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CROSSHAIR); + } + } + + void WaylandTablet::onProximityOut(Tool& tool) { + tool.inside = false; + tool.contactChange = -1; + onFrame(tool, tool.sample.timeMs); + mSeat.surfacePointer().onPointerSurfaceLeft(); + } + + void WaylandTablet::onFrame(Tool& tool, uint32_t time) { + tool.sample.timeMs = time; + mSeat.input().post(PointerSampled{ tool.sample }); + + if (tool.moved && tool.inside) { + mSeat.surfacePointer().onPointerSurfaceMove(tool.position); + } + tool.moved = false; + + if (tool.contactChange != 0) { + mSeat.surfacePointer().onPointerSurfaceButton(BTN_LEFT, tool.contactChange > 0, mSeat.lastInputSerial()); + tool.contactChange = 0; + } + + for (const auto& [linuxButton, pressed] : tool.linuxButtons) { + mSeat.surfacePointer().onPointerSurfaceButton(linuxButton, pressed, mSeat.lastInputSerial()); + } + tool.linuxButtons.clear(); + } +} diff --git a/GraphicApplication/src/backends/wayland/private/WaylandTablet.hpp b/GraphicApplication/src/backends/wayland/private/WaylandTablet.hpp new file mode 100644 index 00000000..32c00595 --- /dev/null +++ b/GraphicApplication/src/backends/wayland/private/WaylandTablet.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include "WaylandSeatContext.hpp" + +#include +#include +#include + +struct wl_seat; +struct wp_cursor_shape_device_v1; +struct zwp_tablet_manager_v2; +struct zwp_tablet_seat_v2; +struct zwp_tablet_v2; +struct zwp_tablet_tool_v2; +struct zwp_tablet_pad_v2; + +namespace tp { + + // Tablet tools (pens, erasers, ...) reported as pointer samples, motion and buttons. + class WaylandTablet { + public: + WaylandTablet(WaylandSeatContext& seat, zwp_tablet_manager_v2* manager, wl_seat* waylandSeat); + ~WaylandTablet(); + + WaylandTablet(const WaylandTablet&) = delete; + WaylandTablet& operator=(const WaylandTablet&) = delete; + + struct Tool { + WaylandTablet& owner; + zwp_tablet_tool_v2* handle = nullptr; + wp_cursor_shape_device_v1* shapeDevice = nullptr; + PointerSample sample; + PointerDevice device = PointerDevice::Stylus; + StylusTip tip = StylusTip::Pen; + Vec2F position = { 0, 0 }; + bool inside = false; + bool moved = false; + int contactChange = 0; + std::vector> linuxButtons; + }; + + void onTabletAdded(zwp_tablet_v2* tablet); + void onToolAdded(zwp_tablet_tool_v2* tool); + void onPadAdded(zwp_tablet_pad_v2* pad); + + void onToolType(Tool& tool, uint32_t type); + void onProximityIn(Tool& tool, uint32_t serial); + void onProximityOut(Tool& tool); + void onFrame(Tool& tool, uint32_t time); + void onToolRemoved(Tool& tool); + void recordSerial(uint32_t serial) { mSeat.recordSerial(serial); } + + private: + WaylandSeatContext& mSeat; + zwp_tablet_seat_v2* mTabletSeat = nullptr; + std::vector mTablets; + std::vector mPads; + std::vector> mTools; + }; +} diff --git a/GraphicApplication/src/backends/wayland/private/WaylandTextInput.cpp b/GraphicApplication/src/backends/wayland/private/WaylandTextInput.cpp new file mode 100644 index 00000000..5b358bc2 --- /dev/null +++ b/GraphicApplication/src/backends/wayland/private/WaylandTextInput.cpp @@ -0,0 +1,95 @@ +#include "WaylandTextInput.hpp" + +#include "text-input-unstable-v3-client-protocol.h" + +namespace tp { + + namespace { + WaylandTextInput& textInput(void* data) { return *static_cast(data); } + + void textEnter(void* data, zwp_text_input_v3*, wl_surface*) { textInput(data).onEnter(); } + void textLeave(void* data, zwp_text_input_v3*, wl_surface*) { textInput(data).onLeave(); } + void textPreedit(void*, zwp_text_input_v3*, const char*, int32_t, int32_t) {} + void textCommit(void* data, zwp_text_input_v3*, const char* text) { textInput(data).onCommitString(text); } + void textDeleteSurrounding(void*, zwp_text_input_v3*, uint32_t, uint32_t) {} + void textDone(void* data, zwp_text_input_v3*, uint32_t) { textInput(data).onDone(); } + + const zwp_text_input_v3_listener kTextInputListener = { + textEnter, textLeave, textPreedit, textCommit, textDeleteSurrounding, textDone, + }; + } + + WaylandTextInput::WaylandTextInput( + WaylandSeatContext& seat, zwp_text_input_manager_v3* manager, wl_seat* waylandSeat + ) : + mSeat(seat) { + mTextInput = zwp_text_input_manager_v3_get_text_input(manager, waylandSeat); + zwp_text_input_v3_add_listener(mTextInput, &kTextInputListener, this); + } + + WaylandTextInput::~WaylandTextInput() { + if (mTextInput) { + zwp_text_input_v3_destroy(mTextInput); + } + } + + void WaylandTextInput::setEnabled(bool enabled) { + if (mEnabled == enabled) { + return; + } + + mEnabled = enabled; + sendState(); + } + + void WaylandTextInput::setCursorRect(const RectF& surfaceRect) { + if (mCursorRect == surfaceRect) { + return; + } + + mCursorRect = surfaceRect; + if (mEnabled && mFocused) { + sendState(); + } + } + + void WaylandTextInput::onEnter() { + mFocused = true; + sendState(); + } + + void WaylandTextInput::onLeave() { + mFocused = false; + zwp_text_input_v3_disable(mTextInput); + zwp_text_input_v3_commit(mTextInput); + } + + void WaylandTextInput::onCommitString(const char* text) { mPendingCommit = text ? text : ""; } + + void WaylandTextInput::onDone() { + if (!mPendingCommit.empty()) { + mSeat.input().post(TextEntered{ mPendingCommit }); + } + mPendingCommit.clear(); + } + + void WaylandTextInput::sendState() { + if (!mFocused) { + return; + } + + if (mEnabled) { + zwp_text_input_v3_enable(mTextInput); + zwp_text_input_v3_set_content_type( + mTextInput, ZWP_TEXT_INPUT_V3_CONTENT_HINT_NONE, ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NORMAL + ); + zwp_text_input_v3_set_cursor_rectangle( + mTextInput, (int32_t) mCursorRect.x, (int32_t) mCursorRect.y, (int32_t) mCursorRect.z, (int32_t) mCursorRect.w + ); + } else { + zwp_text_input_v3_disable(mTextInput); + } + + zwp_text_input_v3_commit(mTextInput); + } +} diff --git a/GraphicApplication/src/backends/wayland/private/WaylandTextInput.hpp b/GraphicApplication/src/backends/wayland/private/WaylandTextInput.hpp new file mode 100644 index 00000000..b0008f73 --- /dev/null +++ b/GraphicApplication/src/backends/wayland/private/WaylandTextInput.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include "Rect.hpp" +#include "WaylandSeatContext.hpp" + +#include + +struct wl_seat; +struct zwp_text_input_manager_v3; +struct zwp_text_input_v3; + +namespace tp { + + // Input-method text through zwp_text_input_v3. + class WaylandTextInput { + public: + WaylandTextInput(WaylandSeatContext& seat, zwp_text_input_manager_v3* manager, wl_seat* waylandSeat); + ~WaylandTextInput(); + + WaylandTextInput(const WaylandTextInput&) = delete; + WaylandTextInput& operator=(const WaylandTextInput&) = delete; + + void setEnabled(bool enabled); + void setCursorRect(const RectF& surfaceRect); + + void onEnter(); + void onLeave(); + void onCommitString(const char* text); + void onDone(); + + private: + void sendState(); + + WaylandSeatContext& mSeat; + zwp_text_input_v3* mTextInput = nullptr; + bool mFocused = false; + bool mEnabled = false; + RectF mCursorRect = { 0, 0, 1, 1 }; + std::string mPendingCommit; + }; +} diff --git a/GraphicApplication/src/backends/wayland/private/WaylandWindow.cpp b/GraphicApplication/src/backends/wayland/private/WaylandWindow.cpp new file mode 100644 index 00000000..27795a79 --- /dev/null +++ b/GraphicApplication/src/backends/wayland/private/WaylandWindow.cpp @@ -0,0 +1,665 @@ +#include "WaylandWindow.hpp" + +#include + +#include "LinuxInputCodes.hpp" +#include "WaylandConnection.hpp" +#include "WaylandSeat.hpp" + +#include "Timing.hpp" + +#include "fractional-scale-v1-client-protocol.h" +#include "viewporter-client-protocol.h" +#include "xdg-activation-v1-client-protocol.h" +#include "xdg-decoration-unstable-v1-client-protocol.h" +#include "xdg-shell-client-protocol.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace tp { + + namespace { + constexpr halnf kFractionalScaleDenominator = 120; + constexpr halnf kMinimumContentWidth = 320; + constexpr halnf kMinimumContentHeight = 200; + constexpr int kConfigureWaitMs = 1000; + } + + struct WaylandWindow::Objects { + wl_surface* surface = nullptr; + xdg_surface* shellSurface = nullptr; + xdg_toplevel* toplevel = nullptr; + wp_viewport* viewport = nullptr; + wp_fractional_scale_v1* fractionalScale = nullptr; + zxdg_toplevel_decoration_v1* decoration = nullptr; + WaylandConfigure pending; + wl_callback* frameCallback = nullptr; + }; + + struct WaylandListeners { + static WaylandWindow& window(void* data) { return *static_cast(data); } + + static void surfaceConfigure(void* data, xdg_surface*, uint32_t serial) { + WaylandWindow& self = window(data); + WaylandConfigure configure = self.mObjects->pending; + configure.serial = serial; + self.mPendingConfigure = configure; + } + + static void toplevelConfigure(void* data, xdg_toplevel*, int32_t width, int32_t height, wl_array* states) { + WaylandConfigure configure; + configure.width = width; + configure.height = height; + + const auto* entries = static_cast(states->data); + const size_t count = states->size / sizeof(uint32_t); + for (size_t index = 0; index < count; index++) { + switch (entries[index]) { + case XDG_TOPLEVEL_STATE_MAXIMIZED: configure.maximized = true; break; + case XDG_TOPLEVEL_STATE_FULLSCREEN: configure.fullscreen = true; break; + case XDG_TOPLEVEL_STATE_ACTIVATED: configure.activated = true; break; + case XDG_TOPLEVEL_STATE_TILED_LEFT: + case XDG_TOPLEVEL_STATE_TILED_RIGHT: + case XDG_TOPLEVEL_STATE_TILED_TOP: + case XDG_TOPLEVEL_STATE_TILED_BOTTOM: configure.tiled = true; break; + default: break; + } + } + + window(data).mObjects->pending = configure; + } + + static void toplevelClose(void* data, xdg_toplevel*) { window(data).mCloseRequested = true; } + static void toplevelBounds(void*, xdg_toplevel*, int32_t, int32_t) {} + static void toplevelCapabilities(void*, xdg_toplevel*, wl_array*) {} + + static void preferredScale(void* data, wp_fractional_scale_v1*, uint32_t scale) { + window(data).applyScale((halnf) scale / kFractionalScaleDenominator); + } + + static void decorationConfigure(void*, zxdg_toplevel_decoration_v1*, uint32_t) {} + + static void frameDone(void* data, wl_callback* callback, uint32_t) { + WaylandWindow& self = window(data); + wl_callback_destroy(callback); + if (self.mObjects->frameCallback == callback) { + self.mObjects->frameCallback = nullptr; + } + } + }; + + namespace { + const xdg_surface_listener kShellSurfaceListener = { WaylandListeners::surfaceConfigure }; + + const xdg_toplevel_listener kToplevelListener = { + WaylandListeners::toplevelConfigure, + WaylandListeners::toplevelClose, + WaylandListeners::toplevelBounds, + WaylandListeners::toplevelCapabilities, + }; + + const wp_fractional_scale_v1_listener kFractionalScaleListener = { WaylandListeners::preferredScale }; + + const zxdg_toplevel_decoration_v1_listener kDecorationListener = { WaylandListeners::decorationConfigure }; + const wl_callback_listener kFrameListener = { WaylandListeners::frameDone }; + + uint32_t resizeEdgeCode(WindowEdge edge) { + switch (edge) { + case WindowEdge::Top: return XDG_TOPLEVEL_RESIZE_EDGE_TOP; + case WindowEdge::Bottom: return XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM; + case WindowEdge::Left: return XDG_TOPLEVEL_RESIZE_EDGE_LEFT; + case WindowEdge::Right: return XDG_TOPLEVEL_RESIZE_EDGE_RIGHT; + case WindowEdge::TopLeft: return XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT; + case WindowEdge::TopRight: return XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT; + case WindowEdge::BottomLeft: return XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT; + case WindowEdge::BottomRight: return XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT; + case WindowEdge::None: return XDG_TOPLEVEL_RESIZE_EDGE_NONE; + } + return XDG_TOPLEVEL_RESIZE_EDGE_NONE; + } + + uint32_t scaledLength(halnf length, halnf scale) { return (uint32_t) std::lround(length * scale); } + } + + WaylandWindow::WaylandWindow(PlatformWindowHost& host, Vec2F contentSize, const WindowIdentity& identity) : + mHost(host), + mConnection(std::make_unique()), + mObjects(std::make_unique()), + mWakeDescriptor(eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK)) { + mFrame.setContentSize(contentSize); + mRestoredContentSize = contentSize; + + WaylandConnection& connection = *mConnection; + Objects& objects = *mObjects; + + objects.surface = wl_compositor_create_surface(connection.compositor()); + objects.shellSurface = xdg_wm_base_get_xdg_surface(connection.windowManager(), objects.surface); + xdg_surface_add_listener(objects.shellSurface, &kShellSurfaceListener, this); + + objects.toplevel = xdg_surface_get_toplevel(objects.shellSurface); + xdg_toplevel_add_listener(objects.toplevel, &kToplevelListener, this); + xdg_toplevel_set_title(objects.toplevel, identity.title.c_str()); + xdg_toplevel_set_app_id(objects.toplevel, identity.appId.c_str()); + xdg_toplevel_set_min_size(objects.toplevel, (int32_t) kMinimumContentWidth, (int32_t) kMinimumContentHeight); + + if (connection.decorationManager()) { + objects.decoration = + zxdg_decoration_manager_v1_get_toplevel_decoration(connection.decorationManager(), objects.toplevel); + zxdg_toplevel_decoration_v1_add_listener(objects.decoration, &kDecorationListener, this); + zxdg_toplevel_decoration_v1_set_mode(objects.decoration, ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE); + } + + if (connection.viewporter()) { + objects.viewport = wp_viewporter_get_viewport(connection.viewporter(), objects.surface); + } + if (connection.fractionalScaleManager() && objects.viewport) { + objects.fractionalScale = + wp_fractional_scale_manager_v1_get_fractional_scale(connection.fractionalScaleManager(), objects.surface); + wp_fractional_scale_v1_add_listener(objects.fractionalScale, &kFractionalScaleListener, this); + } + + mSeat = std::make_unique(connection, mHost, *this); + + updateFrameGeometry(); + wl_surface_commit(objects.surface); + connection.flush(); + } + + WaylandWindow::~WaylandWindow() { + if (mWakeDescriptor >= 0) { + close(mWakeDescriptor); + } + mSeat.reset(); + + Objects& objects = *mObjects; + if (objects.frameCallback) { + wl_callback_destroy(objects.frameCallback); + } + if (objects.decoration) { + zxdg_toplevel_decoration_v1_destroy(objects.decoration); + } + if (objects.fractionalScale) { + wp_fractional_scale_v1_destroy(objects.fractionalScale); + } + if (objects.viewport) { + wp_viewport_destroy(objects.viewport); + } + if (objects.toplevel) { + xdg_toplevel_destroy(objects.toplevel); + } + if (objects.shellSurface) { + xdg_surface_destroy(objects.shellSurface); + } + if (objects.surface) { + wl_surface_destroy(objects.surface); + } + mConnection->flush(); + } + + VkSurfaceKHR WaylandWindow::createSurface(VkInstance instance) const { + VkWaylandSurfaceCreateInfoKHR createInfo{ + .sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR, + .display = mConnection->display(), + .surface = mObjects->surface, + }; + + VkSurfaceKHR surface = VK_NULL_HANDLE; + if (vkCreateWaylandSurfaceKHR(instance, &createInfo, nullptr, &surface) != VK_SUCCESS) { + return VK_NULL_HANDLE; + } + return surface; + } + + VkExtent2D WaylandWindow::framebufferExtent() const { + const Vec2F surface = mFrame.surfaceSize(); + return { scaledLength(surface.x, mScale), scaledLength(surface.y, mScale) }; + } + + void WaylandWindow::onTranslucencyResolved(bool translucent) { + mFrame.setTranslucent(translucent); + mGeometryDirty = true; + } + + void WaylandWindow::dispatchEvents(halnf timeoutMs) { + const bool waitsForever = timeoutMs < 0; + const time_ns deadline = get_time_ns() + (time_ns) (std::max(timeoutMs, 0.0f) * 1.0e6f); + + while (true) { + const halnf remainingMs = waitsForever ? -1.0f : (halnf) ((double) (deadline - get_time_ns()) / 1.0e6); + if (pollOnce(waitsForever ? -1 : std::max(0, (int) std::ceil(remainingMs)))) { + return; + } + if (!waitsForever && get_time_ns() >= deadline) { + return; + } + } + } + + void WaylandWindow::wake() { + if (mWakeDescriptor >= 0) { + eventfd_write(mWakeDescriptor, 1); + } + } + + bool WaylandWindow::pollOnce(int timeoutMs) { + wl_display* display = mConnection->display(); + + mConnection->flush(); + if (wl_display_dispatch_pending(display) > 0) { + return true; + } + + while (wl_display_prepare_read(display) != 0) { + if (wl_display_dispatch_pending(display) > 0) { + return true; + } + } + mConnection->flush(); + + std::vector descriptors = { + { wl_display_get_fd(display), POLLIN, 0 }, + { mWakeDescriptor, POLLIN, 0 }, + }; + mSeat->collectPollDescriptors(descriptors); + + const int ready = poll(descriptors.data(), (nfds_t) descriptors.size(), timeoutMs); + if (ready > 0 && (descriptors[0].revents & POLLIN)) { + if (wl_display_read_events(display) < 0) { + throw std::runtime_error("lost the Wayland connection"); + } + } else { + wl_display_cancel_read(display); + } + + bool handled = wl_display_dispatch_pending(display) > 0; + if (ready > 0 && (descriptors[1].revents & POLLIN)) { + eventfd_t ignored = 0; + eventfd_read(mWakeDescriptor, &ignored); + handled = true; + } + if (ready > 0) { + const std::vector seatDescriptors(descriptors.begin() + 2, descriptors.end()); + mSeat->onDescriptorsReady(seatDescriptors); + handled = handled || std::any_of(seatDescriptors.begin(), seatDescriptors.end(), [](const pollfd& entry) { + return entry.revents != 0; + }); + } + + mConnection->flush(); + return handled; + } + + void WaylandWindow::waitUntilConfigured() { + while (!mConfigured) { + if (!mConnection->dispatch(kConfigureWaitMs)) { + break; + } + applyPendingConfigure(); + } + } + + void WaylandWindow::applyPendingConfigure() { + if (!mPendingConfigure) { + return; + } + const WaylandConfigure configure = *mPendingConfigure; + mPendingConfigure.reset(); + + applyConfigure(configure); + xdg_surface_ack_configure(mObjects->shellSurface, configure.serial); + mConfigured = true; + notifyOwnerResized(); + } + + void WaylandWindow::applyConfigure(const WaylandConfigure& configure) { + mMaximized = configure.maximized || configure.fullscreen; + mFullScreen = configure.fullscreen; + mActivated = configure.activated; + + const bool tiled = mMaximized || configure.tiled; + if (tiled != mFrame.isTiled()) { + mFrame.setTiled(tiled); + mGeometryDirty = true; + } + + Vec2F contentSize = mFrame.contentSize(); + if (configure.width > 0 && configure.height > 0) { + contentSize = { (halnf) configure.width, (halnf) configure.height }; + } else if (!tiled) { + contentSize = mRestoredContentSize; + } + + contentSize.x = std::max(contentSize.x, kMinimumContentWidth); + contentSize.y = std::max(contentSize.y, kMinimumContentHeight); + if (!tiled) { + mRestoredContentSize = contentSize; + } + + if (contentSize != mFrame.contentSize()) { + mFrame.setContentSize(contentSize); + mGeometryDirty = true; + } + } + + void WaylandWindow::applyScale(halnf scale) { + if (scale <= 0 || scale == mScale) { + return; + } + + mScale = scale; + mGeometryDirty = true; + if (mConfigured) { + notifyOwnerResized(); + } + } + + void WaylandWindow::notifyOwnerResized() { mHost.onContentResized(); } + + void WaylandWindow::setAcceptsInput(bool accepts) { + mAcceptsInput = accepts; + mGeometryDirty = true; + updateFrameGeometry(); + wl_surface_commit(mObjects->surface); + mConnection->flush(); + } + + void WaylandWindow::commitFrameGeometry() { + if (mGeometryDirty) { + updateFrameGeometry(); + } + requestFrameCallback(); + } + + void WaylandWindow::requestFrameCallback() { + if (mObjects->frameCallback) { + return; + } + mObjects->frameCallback = wl_surface_frame(mObjects->surface); + wl_callback_add_listener(mObjects->frameCallback, &kFrameListener, this); + } + + bool WaylandWindow::isWaitingForFrame() const { return mObjects->frameCallback != nullptr; } + + void WaylandWindow::updateFrameGeometry() { + Objects& objects = *mObjects; + const Vec2F surface = mFrame.surfaceSize(); + const RectF content = mFrame.contentRect(); + const RectF input = mFrame.inputRect(); + + xdg_surface_set_window_geometry( + objects.shellSurface, (int32_t) content.x, (int32_t) content.y, (int32_t) content.z, (int32_t) content.w + ); + + wl_region* inputRegion = wl_compositor_create_region(mConnection->compositor()); + if (mAcceptsInput) { + wl_region_add(inputRegion, (int32_t) input.x, (int32_t) input.y, (int32_t) input.z, (int32_t) input.w); + } + wl_surface_set_input_region(objects.surface, inputRegion); + wl_region_destroy(inputRegion); + + if (!mFrame.hasShadow()) { + wl_region* opaqueRegion = wl_compositor_create_region(mConnection->compositor()); + wl_region_add(opaqueRegion, 0, 0, (int32_t) surface.x, (int32_t) surface.y); + wl_surface_set_opaque_region(objects.surface, opaqueRegion); + wl_region_destroy(opaqueRegion); + } else { + wl_surface_set_opaque_region(objects.surface, nullptr); + } + + if (objects.viewport) { + wp_viewport_set_destination(objects.viewport, (int32_t) surface.x, (int32_t) surface.y); + } else { + wl_surface_set_buffer_scale(objects.surface, (int32_t) std::lround(mScale)); + } + + mGeometryDirty = false; + } + + namespace { + struct ActivationToken { + std::string value; + bool done = false; + }; + + void activationTokenDone(void* data, xdg_activation_token_v1*, const char* token) { + auto& result = *static_cast(data); + result.value = token ? token : ""; + result.done = true; + } + + const xdg_activation_token_v1_listener kActivationTokenListener = { activationTokenDone }; + } + + std::string WaylandWindow::activationToken() { + xdg_activation_v1* activation = mConnection->activation(); + if (!activation) { + return {}; + } + + wl_display* display = mConnection->display(); + wl_event_queue* queue = wl_display_create_queue(display); + auto* wrapped = static_cast(wl_proxy_create_wrapper(activation)); + wl_proxy_set_queue(reinterpret_cast(wrapped), queue); + + ActivationToken result; + xdg_activation_token_v1* token = xdg_activation_v1_get_activation_token(wrapped); + xdg_activation_token_v1_add_listener(token, &kActivationTokenListener, &result); + if (mConnection->seat() && mSeat->lastInputSerial() != 0) { + xdg_activation_token_v1_set_serial(token, mSeat->lastInputSerial(), mConnection->seat()); + } + xdg_activation_token_v1_set_surface(token, mObjects->surface); + xdg_activation_token_v1_commit(token); + while (!result.done && wl_display_roundtrip_queue(display, queue) >= 0) { + } + + xdg_activation_token_v1_destroy(token); + wl_proxy_wrapper_destroy(wrapped); + wl_event_queue_destroy(queue); + return result.value; + } + + bool WaylandWindow::openUrl(const std::string& url) { + if (url.empty()) { + return false; + } + + std::vector environment; + for (char** entry = environ; *entry; entry++) { + const std::string variable = *entry; + if (variable.starts_with("XDG_ACTIVATION_TOKEN=") || variable.starts_with("DESKTOP_STARTUP_ID=")) { + continue; + } + environment.push_back(variable); + } + const std::string token = activationToken(); + if (!token.empty()) { + environment.push_back("XDG_ACTIVATION_TOKEN=" + token); + environment.push_back("DESKTOP_STARTUP_ID=" + token); + } + + std::vector environmentPointers; + for (std::string& variable : environment) { + environmentPointers.push_back(variable.data()); + } + environmentPointers.push_back(nullptr); + + std::string program = "xdg-open"; + std::string argument = url; + char* arguments[] = { program.data(), argument.data(), nullptr }; + + pid_t child = 0; + if (posix_spawnp(&child, program.c_str(), nullptr, nullptr, arguments, environmentPointers.data()) != 0) { + return false; + } + std::thread([child]() { waitpid(child, nullptr, 0); }).detach(); + return true; + } + + Vec2F WaylandWindow::toContent(Vec2F surfacePoint) const { + const RectF content = mFrame.contentRect(); + return { surfacePoint.x - content.x, surfacePoint.y - content.y }; + } + + void WaylandWindow::onPointerSurfaceMove(Vec2F surfacePoint) { + const bool contentHoldsPointer = mHeldButtons > 0; + const WindowEdge edge = contentHoldsPointer ? WindowEdge::None : mFrame.edgeAt(surfacePoint); + mHoveredEdge = edge; + + if (edge != WindowEdge::None) { + mSeat->setCursor(cursorForEdge(edge)); + if (mPointerOnContent) { + mPointerOnContent = false; + input().post(PointerLeft{}); + } + return; + } + + mSeat->setCursor(mClientCursor); + mPointerOnContent = true; + input().post(PointerMoved{ toContent(surfacePoint) }); + } + + // Touchpad pinches arrive at the pointer; the gesture is placed where the pointer is, in content coordinates. + void WaylandWindow::onPointerSurfacePinch(const TouchGesture& gesture) { + TouchGesture content = gesture; + content.center = toContent(gesture.center); + input().post(GestureChanged{ content }); + } + + void WaylandWindow::onPointerSurfaceLeft() { + mHeldButtons = 0; + mHoveredEdge = WindowEdge::None; + mPointerOnContent = false; + input().post(PointerLeft{}); + } + + void WaylandWindow::onPointerSurfaceButton(uint32_t linuxButton, bool pressed, uint32_t serial) { + if (pressed && mHoveredEdge != WindowEdge::None) { + if (linuxButton == BTN_LEFT) { + xdg_toplevel_resize(mObjects->toplevel, mConnection->seat(), serial, resizeEdgeCode(mHoveredEdge)); + } + return; + } + + const std::optional button = pointerButtonFromLinux(linuxButton); + if (!button) { + return; + } + + if (pressed) { + mHeldButtons++; + } else if (mHeldButtons > 0) { + mHeldButtons--; + } + input().post(PointerButtonChanged{ *button, pressed }); + } + + void WaylandWindow::minimize() { xdg_toplevel_set_minimized(mObjects->toplevel); } + + void WaylandWindow::toggleMaximize() { + if (mMaximized) { + xdg_toplevel_unset_maximized(mObjects->toplevel); + } else { + xdg_toplevel_set_maximized(mObjects->toplevel); + } + } + + void WaylandWindow::setFullScreen(bool fullScreen) { + if (fullScreen) { + xdg_toplevel_set_fullscreen(mObjects->toplevel, nullptr); + } else { + xdg_toplevel_unset_fullscreen(mObjects->toplevel); + } + } + + void WaylandWindow::beginMove() { + xdg_toplevel_move(mObjects->toplevel, mConnection->seat(), mSeat->lastInputSerial()); + } + + void WaylandWindow::beginResize(WindowEdge edge) { + if (edge == WindowEdge::None) { + return; + } + xdg_toplevel_resize(mObjects->toplevel, mConnection->seat(), mSeat->lastInputSerial(), resizeEdgeCode(edge)); + } + + void WaylandWindow::showWindowMenu(Vec2F contentPosition) { + const RectF content = mFrame.contentRect(); + xdg_toplevel_show_window_menu( + mObjects->toplevel, + mConnection->seat(), + mSeat->lastInputSerial(), + (int32_t) (contentPosition.x + content.x), + (int32_t) (contentPosition.y + content.y) + ); + } + + void WaylandWindow::setCursor(CursorShape shape) { + mClientCursor = shape; + if (mHoveredEdge == WindowEdge::None) { + mSeat->setCursor(shape); + } + } + + void WaylandWindow::setClipboardText(const std::string& text) { mSeat->setClipboardText(text); } + + std::string WaylandWindow::clipboardText() { return mSeat->clipboardText(); } + + ClipboardImage WaylandWindow::clipboardImage() { return mSeat->clipboardImage(); } + + void WaylandWindow::setTextInputEnabled(bool enabled) { mSeat->setTextInputEnabled(enabled); } + + void WaylandWindow::setTextInputRect(const RectF& contentRect) { + const RectF content = mFrame.contentRect(); + mSeat->setTextInputRect({ contentRect.x + content.x, contentRect.y + content.y, contentRect.z, contentRect.w }); + } + + PlatformInfo WaylandWindow::platformInfo() const { + PlatformInfo info; + info.system = OperatingSystem::Linux; + info.formFactor = FormFactor::Desktop; + info.contentScale = mScale; + info.screenSize = mFrame.contentSize(); + info.hasMouse = mSeat->hasPointer(); + info.hasTouch = mSeat->hasTouch(); + info.hasPen = mSeat->hasTablet(); + info.hasPhysicalKeyboard = mSeat->hasKeyboard(); + info.hasHoverPointer = info.hasMouse || info.hasPen; + return info; + } + + std::vector platformSurfaceExtensions() { + return { VK_KHR_SURFACE_EXTENSION_NAME, VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME }; + } + + // Resources are installed next to the executable, so a moved or installed binary still finds them. + std::filesystem::path platformResourceDirectory() { + std::error_code error; + const std::filesystem::path executable = std::filesystem::read_symlink("/proc/self/exe", error); + if (error) { + return "rsc"; + } + return executable.parent_path() / "rsc"; + } + + std::unique_ptr createPlatformWindow( + PlatformWindowHost& host, Vec2F contentSize, const WindowIdentity& identity, const PlatformContext& + ) { + return std::make_unique(host, contentSize, identity); + } +} diff --git a/GraphicApplication/src/backends/wayland/private/WaylandWindow.hpp b/GraphicApplication/src/backends/wayland/private/WaylandWindow.hpp new file mode 100644 index 00000000..63d338fd --- /dev/null +++ b/GraphicApplication/src/backends/wayland/private/WaylandWindow.hpp @@ -0,0 +1,122 @@ +#pragma once + +#include "DesktopWindowControls.hpp" +#include "PlatformWindow.hpp" +#include "WaylandSeatContext.hpp" + +#include +#include +#include +#include + +struct wl_display; + +namespace tp { + + class WaylandConnection; + class WaylandSeat; + + // One xdg_toplevel's pending configure; applied and acknowledged by the frame loop. + struct WaylandConfigure { + int32_t width = 0; + int32_t height = 0; + bool maximized = false; + bool fullscreen = false; + bool activated = false; + bool tiled = false; + uint32_t serial = 0; + }; + + class WaylandWindow final : public PlatformWindow, public DesktopWindowControls, public WaylandSurfacePointer { + public: + WaylandWindow(PlatformWindowHost& host, Vec2F contentSize, const WindowIdentity& identity); + ~WaylandWindow() override; + + WaylandWindow(const WaylandWindow&) = delete; + WaylandWindow& operator=(const WaylandWindow&) = delete; + + [[nodiscard]] VkSurfaceKHR createSurface(VkInstance instance) const override; + [[nodiscard]] VkExtent2D framebufferExtent() const override; + [[nodiscard]] bool wantsTranslucency() const override { return true; } + + void setAcceptsInput(bool accepts) override; + void onTranslucencyResolved(bool translucent) override; + + void dispatchEvents(halnf timeoutMs) override; + void wake() override; + void waitUntilConfigured() override; + [[nodiscard]] bool hasPendingConfigure() const override { return mPendingConfigure.has_value(); } + void applyPendingConfigure() override; + void commitFrameGeometry() override; + [[nodiscard]] bool isWaitingForFrame() const override; + + [[nodiscard]] bool shouldClose() const override { return mCloseRequested; } + void requestClose() override { mCloseRequested = true; } + + [[nodiscard]] const WindowFrame& frame() const override { return mFrame; } + [[nodiscard]] halnf scale() const override { return mScale; } + [[nodiscard]] bool isMaximized() const override { return mMaximized; } + [[nodiscard]] bool isFocused() const override { return mActivated; } + + void minimize() override; + [[nodiscard]] DesktopWindowControls* desktopControls() override { return this; } + void toggleMaximize() override; + void setFullScreen(bool fullScreen) override; + [[nodiscard]] bool isFullScreen() const override { return mFullScreen; } + void beginMove() override; + void beginResize(WindowEdge edge) override; + void showWindowMenu(Vec2F contentPosition) override; + + void setCursor(CursorShape shape) override; + void setClipboardText(const std::string& text) override; + [[nodiscard]] std::string clipboardText() override; + [[nodiscard]] ClipboardImage clipboardImage() override; + bool openUrl(const std::string& url) override; + void setTextInputEnabled(bool enabled) override; + void setTextInputRect(const RectF& contentRect) override; + + [[nodiscard]] PlatformInfo platformInfo() const override; + + private: + friend struct WaylandListeners; + + void applyConfigure(const WaylandConfigure& configure); + void applyScale(halnf scale); + void notifyOwnerResized(); + [[nodiscard]] bool pollOnce(int timeoutMs); + void updateFrameGeometry(); + void requestFrameCallback(); + + void onPointerSurfaceMove(Vec2F surfacePoint) override; + void onPointerSurfaceButton(uint32_t linuxButton, bool pressed, uint32_t serial) override; + void onPointerSurfaceLeft() override; + void onPointerSurfacePinch(const TouchGesture& gesture) override; + [[nodiscard]] InputSink& input() { return mHost.rawInput(); } + [[nodiscard]] Vec2F toContent(Vec2F surfacePoint) const; + [[nodiscard]] std::string activationToken(); + + PlatformWindowHost& mHost; + bool mAcceptsInput = true; + bool mPointerOnContent = false; + std::unique_ptr mConnection; + std::unique_ptr mSeat; + int mWakeDescriptor = -1; + + struct Objects; + std::unique_ptr mObjects; + + WindowFrame mFrame; + Vec2F mRestoredContentSize; + halnf mScale = 1; + bool mConfigured = false; + std::optional mPendingConfigure; + bool mCloseRequested = false; + bool mMaximized = false; + bool mFullScreen = false; + bool mActivated = false; + bool mGeometryDirty = true; + WindowEdge mHoveredEdge = WindowEdge::None; + int mHeldButtons = 0; + CursorShape mClientCursor = CursorShape::Default; + }; +} diff --git a/GraphicApplication/src/gpu/private/GpuDevice.cpp b/GraphicApplication/src/gpu/private/GpuDevice.cpp new file mode 100644 index 00000000..f70b5bf9 --- /dev/null +++ b/GraphicApplication/src/gpu/private/GpuDevice.cpp @@ -0,0 +1,226 @@ +#include "GpuDevice.hpp" + +#include "GpuError.hpp" + +#include +#include +#include +#include + +namespace tp { + + namespace { + constexpr std::array kDeviceExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME }; + constexpr const char* kValidationLayer = "VK_LAYER_KHRONOS_validation"; + + bool validationRequested() { + const char* value = std::getenv("MODULES_VULKAN_VALIDATION"); + return value && std::strcmp(value, "1") == 0; + } + + bool hasValidationLayer() { + uint32_t count = 0; + vkEnumerateInstanceLayerProperties(&count, nullptr); + std::vector layers(count); + vkEnumerateInstanceLayerProperties(&count, layers.data()); + for (const VkLayerProperties& layer : layers) { + if (std::strcmp(layer.layerName, kValidationLayer) == 0) { + return true; + } + } + return false; + } + + bool supportsDeviceExtensions(VkPhysicalDevice device) { + uint32_t count = 0; + vkEnumerateDeviceExtensionProperties(device, nullptr, &count, nullptr); + std::vector available(count); + vkEnumerateDeviceExtensionProperties(device, nullptr, &count, available.data()); + for (const char* required : kDeviceExtensions) { + const bool found = + std::any_of(available.begin(), available.end(), [required](const VkExtensionProperties& extension) { + return std::strcmp(required, extension.extensionName) == 0; + }); + if (!found) { + return false; + } + } + return true; + } + + uint32_t graphicsFamilyOf(VkPhysicalDevice device) { + uint32_t count = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &count, nullptr); + std::vector families(count); + vkGetPhysicalDeviceQueueFamilyProperties(device, &count, families.data()); + for (uint32_t index = 0; index < count; index++) { + if (families[index].queueFlags & VK_QUEUE_GRAPHICS_BIT) { + return index; + } + } + return UINT32_MAX; + } + } + + GpuDevice::GpuDevice(const std::vector& surfaceExtensions) { + createInstance(surfaceExtensions); + selectPhysicalDevice(); + createLogicalDevice(); + createCommandPool(); + } + + GpuDevice::~GpuDevice() { + waitIdle(); + if (mCommandPool != VK_NULL_HANDLE) { + vkDestroyCommandPool(mDevice, mCommandPool, nullptr); + } + if (mDevice != VK_NULL_HANDLE) { + vkDestroyDevice(mDevice, nullptr); + } + if (mInstance != VK_NULL_HANDLE) { + vkDestroyInstance(mInstance, nullptr); + } + } + + void GpuDevice::createInstance(const std::vector& surfaceExtensions) { + std::vector layers; + if (validationRequested() && hasValidationLayer()) { + layers.push_back(kValidationLayer); + } + + const VkApplicationInfo appInfo{ + .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, + .pApplicationName = "Modules", + .apiVersion = VK_API_VERSION_1_2, + }; + + const VkInstanceCreateInfo createInfo{ + .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + .pApplicationInfo = &appInfo, + .enabledLayerCount = (uint32_t) layers.size(), + .ppEnabledLayerNames = layers.data(), + .enabledExtensionCount = (uint32_t) surfaceExtensions.size(), + .ppEnabledExtensionNames = surfaceExtensions.data(), + }; + requireSuccess(vkCreateInstance(&createInfo, nullptr, &mInstance), "vkCreateInstance"); + } + + void GpuDevice::selectPhysicalDevice() { + uint32_t count = 0; + vkEnumeratePhysicalDevices(mInstance, &count, nullptr); + std::vector devices(count); + vkEnumeratePhysicalDevices(mInstance, &count, devices.data()); + + for (VkPhysicalDevice device : devices) { + const uint32_t graphics = graphicsFamilyOf(device); + if (graphics == UINT32_MAX || !supportsDeviceExtensions(device)) { + continue; + } + mPhysicalDevice = device; + mGraphicsQueueFamily = graphics; + vkGetPhysicalDeviceProperties(device, &mProperties); + return; + } + throw GpuError("selecting a graphics device with swapchain support", VK_ERROR_INITIALIZATION_FAILED); + } + + void GpuDevice::createLogicalDevice() { + const float priority = 1.0f; + const VkDeviceQueueCreateInfo queueInfo{ + .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, + .queueFamilyIndex = mGraphicsQueueFamily, + .queueCount = 1, + .pQueuePriorities = &priority, + }; + + VkPhysicalDeviceFeatures supported{}; + vkGetPhysicalDeviceFeatures(mPhysicalDevice, &supported); + VkPhysicalDeviceFeatures features{}; + features.geometryShader = supported.geometryShader; + + const VkDeviceCreateInfo createInfo{ + .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &queueInfo, + .enabledExtensionCount = (uint32_t) kDeviceExtensions.size(), + .ppEnabledExtensionNames = kDeviceExtensions.data(), + .pEnabledFeatures = &features, + }; + requireSuccess(vkCreateDevice(mPhysicalDevice, &createInfo, nullptr, &mDevice), "vkCreateDevice"); + vkGetDeviceQueue(mDevice, mGraphicsQueueFamily, 0, &mGraphicsQueue); + } + + void GpuDevice::createCommandPool() { + const VkCommandPoolCreateInfo poolInfo{ + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, + .queueFamilyIndex = mGraphicsQueueFamily, + }; + requireSuccess(vkCreateCommandPool(mDevice, &poolInfo, nullptr, &mCommandPool), "vkCreateCommandPool"); + } + + bool GpuDevice::canPresent(VkSurfaceKHR surface) const { + VkBool32 supported = VK_FALSE; + vkGetPhysicalDeviceSurfaceSupportKHR(mPhysicalDevice, mGraphicsQueueFamily, surface, &supported); + return supported == VK_TRUE; + } + + uint32_t GpuDevice::findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) const { + VkPhysicalDeviceMemoryProperties memory{}; + vkGetPhysicalDeviceMemoryProperties(mPhysicalDevice, &memory); + for (uint32_t index = 0; index < memory.memoryTypeCount; index++) { + const bool allowed = typeFilter & (1u << index); + if (allowed && (memory.memoryTypes[index].propertyFlags & properties) == properties) { + return index; + } + } + throw GpuError("finding a memory type", VK_ERROR_FEATURE_NOT_PRESENT); + } + + VkCommandBuffer GpuDevice::beginOneShotCommands() const { + const VkCommandBufferAllocateInfo allocateInfo{ + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .commandPool = mCommandPool, + .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, + .commandBufferCount = 1, + }; + VkCommandBuffer commandBuffer = VK_NULL_HANDLE; + requireSuccess(vkAllocateCommandBuffers(mDevice, &allocateInfo, &commandBuffer), "vkAllocateCommandBuffers"); + + const VkCommandBufferBeginInfo beginInfo{ + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, + }; + requireSuccess(vkBeginCommandBuffer(commandBuffer, &beginInfo), "vkBeginCommandBuffer"); + return commandBuffer; + } + + // Waits on a dedicated fence rather than the whole queue, so in-flight window frames keep running. + void GpuDevice::endOneShotCommands(VkCommandBuffer commandBuffer) const { + requireSuccess(vkEndCommandBuffer(commandBuffer), "vkEndCommandBuffer"); + + const VkFenceCreateInfo fenceInfo{ .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO }; + VkFence fence = VK_NULL_HANDLE; + requireSuccess(vkCreateFence(mDevice, &fenceInfo, nullptr, &fence), "vkCreateFence"); + + const VkSubmitInfo submitInfo{ + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .commandBufferCount = 1, + .pCommandBuffers = &commandBuffer, + }; + const VkResult submitted = vkQueueSubmit(mGraphicsQueue, 1, &submitInfo, fence); + if (submitted == VK_SUCCESS) { + vkWaitForFences(mDevice, 1, &fence, VK_TRUE, UINT64_MAX); + } + + vkDestroyFence(mDevice, fence, nullptr); + vkFreeCommandBuffers(mDevice, mCommandPool, 1, &commandBuffer); + requireSuccess(submitted, "vkQueueSubmit"); + } + + void GpuDevice::waitIdle() const { + if (mDevice != VK_NULL_HANDLE) { + vkDeviceWaitIdle(mDevice); + } + } +} diff --git a/GraphicApplication/src/gpu/private/GpuTimer.cpp b/GraphicApplication/src/gpu/private/GpuTimer.cpp new file mode 100644 index 00000000..b0428099 --- /dev/null +++ b/GraphicApplication/src/gpu/private/GpuTimer.cpp @@ -0,0 +1,96 @@ +#include "GpuTimer.hpp" + +#include + +namespace tp { + + GpuTimer::GpuTimer( + VkDevice device, VkPhysicalDevice physicalDevice, uint32_t queueFamily, Profiler& profiler, const char* name + ) : + mDevice(device), + mProfiler(profiler), + mSection(profiler.section(name)), + mPending(kSlots, false) { + VkPhysicalDeviceProperties properties{}; + vkGetPhysicalDeviceProperties(physicalDevice, &properties); + + uint32_t familyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &familyCount, nullptr); + std::vector families(familyCount); + vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &familyCount, families.data()); + + const uint32_t validBits = queueFamily < familyCount ? families[queueFamily].timestampValidBits : 0; + if (validBits == 0 || properties.limits.timestampPeriod <= 0.0f) { + return; + } + + mTickNs = properties.limits.timestampPeriod; + mValidMask = validBits >= 64 ? ~0ull : ((1ull << validBits) - 1ull); + + VkQueryPoolCreateInfo info{ + .sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO, + .queryType = VK_QUERY_TYPE_TIMESTAMP, + .queryCount = kSlots * 2, + }; + if (vkCreateQueryPool(mDevice, &info, nullptr, &mPool) != VK_SUCCESS) { + mPool = VK_NULL_HANDLE; + } + } + + GpuTimer::~GpuTimer() { + if (mPool != VK_NULL_HANDLE) { + vkDestroyQueryPool(mDevice, mPool, nullptr); + } + } + + void GpuTimer::begin(VkCommandBuffer commandBuffer) { + mOpenSlot = UINT32_MAX; + if (!isSupported() || !mProfiler.isEnabled()) { + return; + } + + collect(); + const uint32_t slot = mNextSlot; + if (mPending[slot]) { + return; + } + + vkCmdResetQueryPool(commandBuffer, mPool, slot * 2, 2); + vkCmdWriteTimestamp(commandBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, mPool, slot * 2); + mOpenSlot = slot; + } + + void GpuTimer::end(VkCommandBuffer commandBuffer) { + if (mOpenSlot == UINT32_MAX) { + return; + } + vkCmdWriteTimestamp(commandBuffer, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, mPool, mOpenSlot * 2 + 1); + mPending[mOpenSlot] = true; + mNextSlot = (mOpenSlot + 1) % kSlots; + mOpenSlot = UINT32_MAX; + } + + void GpuTimer::collect() { + if (!isSupported()) { + return; + } + for (uint32_t offset = 0; offset < kSlots; offset++) { + const uint32_t slot = (mNextSlot + offset) % kSlots; + if (!mPending[slot]) { + continue; + } + + uint64_t stamps[2] = {}; + const VkResult result = vkGetQueryPoolResults( + mDevice, mPool, slot * 2, 2, sizeof(stamps), stamps, sizeof(uint64_t), VK_QUERY_RESULT_64_BIT + ); + if (result != VK_SUCCESS) { + continue; + } + + mPending[slot] = false; + const uint64_t ticks = (stamps[1] - stamps[0]) & mValidMask; + mProfiler.addSectionTime(mSection, (double) ticks * mTickNs / 1.0e6); + } + } +} diff --git a/GraphicApplication/src/gpu/private/GpuTimer.hpp b/GraphicApplication/src/gpu/private/GpuTimer.hpp new file mode 100644 index 00000000..bc9c5732 --- /dev/null +++ b/GraphicApplication/src/gpu/private/GpuTimer.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include "Profiler.hpp" + +#include + +#include +#include + +namespace tp { + + // Measures GPU time of recorded command ranges with timestamp queries and reports it as a profiler section. + class GpuTimer { + public: + static constexpr uint32_t kSlots = 8; + + GpuTimer( + VkDevice device, VkPhysicalDevice physicalDevice, uint32_t queueFamily, Profiler& profiler, const char* name + ); + ~GpuTimer(); + + GpuTimer(const GpuTimer&) = delete; + GpuTimer& operator=(const GpuTimer&) = delete; + + void begin(VkCommandBuffer commandBuffer); + void end(VkCommandBuffer commandBuffer); + + private: + [[nodiscard]] bool isSupported() const { return mPool != VK_NULL_HANDLE; } + void collect(); + + VkDevice mDevice; + Profiler& mProfiler; + ProfileId mSection; + VkQueryPool mPool = VK_NULL_HANDLE; + double mTickNs = 1; + uint64_t mValidMask = ~0ull; + + std::vector mPending; + uint32_t mNextSlot = 0; + uint32_t mOpenSlot = UINT32_MAX; + }; +} diff --git a/GraphicApplication/src/gpu/private/RenderTargetRegistry.cpp b/GraphicApplication/src/gpu/private/RenderTargetRegistry.cpp new file mode 100644 index 00000000..2d8b56d2 --- /dev/null +++ b/GraphicApplication/src/gpu/private/RenderTargetRegistry.cpp @@ -0,0 +1,23 @@ +#include "RenderTargetRegistry.hpp" + +namespace tp { + + uint32_t RenderTargetRegistry::registerTarget(const RenderTargetEntry& entry) { + const uint32_t id = mNextId++; + mEntries[id] = entry; + return id; + } + + void RenderTargetRegistry::updateTarget(uint32_t id, const RenderTargetEntry& entry) { mEntries[id] = entry; } + + void RenderTargetRegistry::unregisterTarget(uint32_t id) { mEntries.erase(id); } + + bool RenderTargetRegistry::resolve(uint32_t id, RenderTargetEntry& out) const { + const auto found = mEntries.find(id); + if (found == mEntries.end()) { + return false; + } + out = found->second; + return true; + } +} diff --git a/GraphicApplication/src/gpu/private/WindowSurface.cpp b/GraphicApplication/src/gpu/private/WindowSurface.cpp new file mode 100644 index 00000000..dcf145c4 --- /dev/null +++ b/GraphicApplication/src/gpu/private/WindowSurface.cpp @@ -0,0 +1,571 @@ +#include "WindowSurface.hpp" + +#include "GpuError.hpp" +#include "GpuTimer.hpp" +#include "Profiler.hpp" + +#include +#include +#include + +namespace tp { + + namespace { + constexpr uint32_t kUndefinedExtent = std::numeric_limits::max(); + + VkExtent2D atLeastOnePixel(VkExtent2D extent) { + return { std::max(extent.width, 1u), std::max(extent.height, 1u) }; + } + + bool isEmpty(VkExtent2D extent) { return extent.width == 0 || extent.height == 0; } + + VkSurfaceCapabilitiesKHR capabilitiesOf(VkPhysicalDevice device, VkSurfaceKHR surface) { + VkSurfaceCapabilitiesKHR capabilities{}; + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &capabilities); + return capabilities; + } + } + + WindowSurface::WindowSurface(GpuDevice& gpu, VulkanSurfaceSource& source, Profiler& profiler) : + mGpu(gpu), + mDevice(gpu.device()), + mSource(source), + mProfiler(profiler), + mAcquireSection(profiler.section("frame.acquire")), + mSubmitSection(profiler.section("frame.submit")) { + createSurface(); + selectCompositeAlpha(); + createRenderPass(); + createFrameSlots(); + mFrameTimer = + std::make_unique(mDevice, gpu.physicalDevice(), gpu.graphicsQueueFamily(), profiler, "gpu.window"); + + const VkExtent2D extent = mSource.framebufferExtent(); + createSwapchain(atLeastOnePixel(extent)); + mRequestedExtent = extent; + } + + WindowSurface::~WindowSurface() { + mGpu.waitIdle(); + mFrameTimer.reset(); + destroySwapchain(); + destroyFrameSlots(); + if (mRenderPass != VK_NULL_HANDLE) { + vkDestroyRenderPass(mDevice, mRenderPass, nullptr); + } + if (mSurface != VK_NULL_HANDLE) { + vkDestroySurfaceKHR(mGpu.instance(), mSurface, nullptr); + } + } + + void WindowSurface::createSurface() { + mSurface = mSource.createSurface(mGpu.instance()); + if (mSurface == VK_NULL_HANDLE) { + throw GpuError("creating the window surface", VK_ERROR_SURFACE_LOST_KHR); + } + if (!mGpu.canPresent(mSurface)) { + throw GpuError("presenting to the window surface from the graphics queue", VK_ERROR_INCOMPATIBLE_DISPLAY_KHR); + } + } + + void WindowSurface::selectCompositeAlpha() { + const VkCompositeAlphaFlagsKHR supported = capabilitiesOf(mGpu.physicalDevice(), mSurface).supportedCompositeAlpha; + + mCompositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + if (!(supported & VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR)) { + for (VkCompositeAlphaFlagBitsKHR candidate : { VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR, + VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR, + VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR }) { + if (supported & candidate) { + mCompositeAlpha = candidate; + break; + } + } + } + if (mSource.wantsTranslucency() && (supported & VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR)) { + mCompositeAlpha = VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR; + mTranslucent = true; + } + + mSource.onTranslucencyResolved(mTranslucent); + } + + void WindowSurface::createRenderPass() { + uint32_t formatCount = 0; + vkGetPhysicalDeviceSurfaceFormatsKHR(mGpu.physicalDevice(), mSurface, &formatCount, nullptr); + std::vector formats(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(mGpu.physicalDevice(), mSurface, &formatCount, formats.data()); + if (formats.empty()) { + throw GpuError("querying surface formats", VK_ERROR_FORMAT_NOT_SUPPORTED); + } + + mSwapchainFormat = formats.front().format; + for (const VkSurfaceFormatKHR& format : formats) { + if (format.format == VK_FORMAT_B8G8R8A8_UNORM && format.colorSpace == VK_COLORSPACE_SRGB_NONLINEAR_KHR) { + mSwapchainFormat = format.format; + break; + } + } + mDepthStencilFormat = selectDepthStencilFormat(); + + const std::array attachments = { + VkAttachmentDescription{ + .format = mSwapchainFormat, + .samples = VK_SAMPLE_COUNT_1_BIT, + .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, + .storeOp = VK_ATTACHMENT_STORE_OP_STORE, + .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, + .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + }, + VkAttachmentDescription{ + .format = mDepthStencilFormat, + .samples = VK_SAMPLE_COUNT_1_BIT, + .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, + .storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, + .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, + .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + }, + }; + + const VkAttachmentReference colorReference{ 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }; + const VkAttachmentReference depthReference{ 1, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL }; + + const VkSubpassDescription subpass{ + .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, + .colorAttachmentCount = 1, + .pColorAttachments = &colorReference, + .pDepthStencilAttachment = &depthReference, + }; + + const VkSubpassDependency dependency{ + .srcSubpass = VK_SUBPASS_EXTERNAL, + .dstSubpass = 0, + .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT, + .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT, + .srcAccessMask = 0, + .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, + }; + + const VkRenderPassCreateInfo createInfo{ + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, + .attachmentCount = (uint32_t) attachments.size(), + .pAttachments = attachments.data(), + .subpassCount = 1, + .pSubpasses = &subpass, + .dependencyCount = 1, + .pDependencies = &dependency, + }; + requireSuccess(vkCreateRenderPass(mDevice, &createInfo, nullptr, &mRenderPass), "vkCreateRenderPass"); + } + + VkFormat WindowSurface::selectDepthStencilFormat() const { + for (VkFormat format : { VK_FORMAT_D24_UNORM_S8_UINT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D16_UNORM_S8_UINT }) { + VkFormatProperties properties{}; + vkGetPhysicalDeviceFormatProperties(mGpu.physicalDevice(), format, &properties); + if (properties.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) { + return format; + } + } + throw GpuError("selecting a depth-stencil format", VK_ERROR_FORMAT_NOT_SUPPORTED); + } + + void WindowSurface::createFrameSlots() { + VkCommandBuffer commandBuffers[kFramesInFlight] = {}; + const VkCommandBufferAllocateInfo allocateInfo{ + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .commandPool = mGpu.commandPool(), + .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, + .commandBufferCount = kFramesInFlight, + }; + requireSuccess(vkAllocateCommandBuffers(mDevice, &allocateInfo, commandBuffers), "vkAllocateCommandBuffers"); + + const VkSemaphoreCreateInfo semaphoreInfo{ .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO }; + const VkFenceCreateInfo fenceInfo{ + .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, + .flags = VK_FENCE_CREATE_SIGNALED_BIT, + }; + for (uint32_t index = 0; index < kFramesInFlight; index++) { + FrameSlot& slot = mFrameSlots[index]; + slot.commandBuffer = commandBuffers[index]; + requireSuccess(vkCreateSemaphore(mDevice, &semaphoreInfo, nullptr, &slot.imageAvailable), "vkCreateSemaphore"); + requireSuccess(vkCreateFence(mDevice, &fenceInfo, nullptr, &slot.inFlight), "vkCreateFence"); + } + } + + void WindowSurface::destroyFrameSlots() { + for (FrameSlot& slot : mFrameSlots) { + if (slot.imageAvailable != VK_NULL_HANDLE) { + vkDestroySemaphore(mDevice, slot.imageAvailable, nullptr); + } + if (slot.inFlight != VK_NULL_HANDLE) { + vkDestroyFence(mDevice, slot.inFlight, nullptr); + } + if (slot.commandBuffer != VK_NULL_HANDLE) { + vkFreeCommandBuffers(mDevice, mGpu.commandPool(), 1, &slot.commandBuffer); + } + slot = {}; + } + } + + // Mailbox replaces the queued frame instead of waiting behind it; FIFO is the guaranteed fallback. + VkPresentModeKHR WindowSurface::selectPresentMode() const { + uint32_t count = 0; + vkGetPhysicalDeviceSurfacePresentModesKHR(mGpu.physicalDevice(), mSurface, &count, nullptr); + std::vector modes(count); + vkGetPhysicalDeviceSurfacePresentModesKHR(mGpu.physicalDevice(), mSurface, &count, modes.data()); + const bool hasMailbox = std::find(modes.begin(), modes.end(), VK_PRESENT_MODE_MAILBOX_KHR) != modes.end(); + return hasMailbox ? VK_PRESENT_MODE_MAILBOX_KHR : VK_PRESENT_MODE_FIFO_KHR; + } + + void WindowSurface::createSwapchain(VkExtent2D requested, VkSwapchainKHR previous) { + const VkSurfaceCapabilitiesKHR capabilities = capabilitiesOf(mGpu.physicalDevice(), mSurface); + + mSwapchainExtent = capabilities.currentExtent; + mSurfaceTransform = capabilities.currentTransform; + mSurfaceExtent = capabilities.currentExtent; + + VkSurfaceTransformFlagBitsKHR transform = capabilities.currentTransform; + const bool canRenderUnrotated = capabilities.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; + if (transform != VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR && canRenderUnrotated) { + transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; + const bool quarterTurn = capabilities.currentTransform & + (VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR | VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR); + if (quarterTurn && mSwapchainExtent.width != kUndefinedExtent) { + std::swap(mSwapchainExtent.width, mSwapchainExtent.height); + } + } + if (mSwapchainExtent.width == kUndefinedExtent) { + mSwapchainExtent.width = + std::clamp(requested.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + mSwapchainExtent.height = + std::clamp(requested.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + } + + const VkPresentModeKHR presentMode = selectPresentMode(); + uint32_t imageCount = capabilities.minImageCount + (presentMode == VK_PRESENT_MODE_MAILBOX_KHR ? 1 : 0); + if (capabilities.maxImageCount > 0) { + imageCount = std::min(imageCount, capabilities.maxImageCount); + } + + const VkSwapchainCreateInfoKHR createInfo{ + .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, + .surface = mSurface, + .minImageCount = imageCount, + .imageFormat = mSwapchainFormat, + .imageColorSpace = VK_COLORSPACE_SRGB_NONLINEAR_KHR, + .imageExtent = mSwapchainExtent, + .imageArrayLayers = 1, + .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT, + .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE, + .preTransform = transform, + .compositeAlpha = mCompositeAlpha, + .presentMode = presentMode, + .clipped = VK_TRUE, + .oldSwapchain = previous, + }; + const VkResult created = vkCreateSwapchainKHR(mDevice, &createInfo, nullptr, &mSwapchain); + if (previous != VK_NULL_HANDLE) { + vkDestroySwapchainKHR(mDevice, previous, nullptr); + } + requireSuccess(created, "vkCreateSwapchainKHR"); + + createSwapchainImages(); + } + + // Each image owns its render-finished semaphore: presentation may still hold it after its frame slot is reused. + void WindowSurface::createSwapchainImages() { + uint32_t count = 0; + vkGetSwapchainImagesKHR(mDevice, mSwapchain, &count, nullptr); + mSwapchainImages.resize(count); + vkGetSwapchainImagesKHR(mDevice, mSwapchain, &count, mSwapchainImages.data()); + + createDepthStencil(); + + const VkSemaphoreCreateInfo semaphoreInfo{ .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO }; + mSwapchainImageViews.resize(count); + mSwapchainFramebuffers.resize(count); + mRenderFinishedSemaphores.resize(count); + for (uint32_t index = 0; index < count; index++) { + const VkImageViewCreateInfo viewInfo{ + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .image = mSwapchainImages[index], + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = mSwapchainFormat, + .subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }, + }; + requireSuccess(vkCreateImageView(mDevice, &viewInfo, nullptr, &mSwapchainImageViews[index]), "vkCreateImageView"); + + const std::array attachments = { mSwapchainImageViews[index], mDepthStencilView }; + const VkFramebufferCreateInfo framebufferInfo{ + .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, + .renderPass = mRenderPass, + .attachmentCount = (uint32_t) attachments.size(), + .pAttachments = attachments.data(), + .width = mSwapchainExtent.width, + .height = mSwapchainExtent.height, + .layers = 1, + }; + requireSuccess( + vkCreateFramebuffer(mDevice, &framebufferInfo, nullptr, &mSwapchainFramebuffers[index]), "vkCreateFramebuffer" + ); + requireSuccess( + vkCreateSemaphore(mDevice, &semaphoreInfo, nullptr, &mRenderFinishedSemaphores[index]), "vkCreateSemaphore" + ); + } + } + + void WindowSurface::createDepthStencil() { + const VkImageCreateInfo imageInfo{ + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .imageType = VK_IMAGE_TYPE_2D, + .format = mDepthStencilFormat, + .extent = { mSwapchainExtent.width, mSwapchainExtent.height, 1 }, + .mipLevels = 1, + .arrayLayers = 1, + .samples = VK_SAMPLE_COUNT_1_BIT, + .tiling = VK_IMAGE_TILING_OPTIMAL, + .usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + }; + requireSuccess(vkCreateImage(mDevice, &imageInfo, nullptr, &mDepthStencilImage), "vkCreateImage"); + + VkMemoryRequirements requirements{}; + vkGetImageMemoryRequirements(mDevice, mDepthStencilImage, &requirements); + const VkMemoryAllocateInfo allocateInfo{ + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .allocationSize = requirements.size, + .memoryTypeIndex = mGpu.findMemoryType(requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT), + }; + requireSuccess(vkAllocateMemory(mDevice, &allocateInfo, nullptr, &mDepthStencilMemory), "vkAllocateMemory"); + requireSuccess(vkBindImageMemory(mDevice, mDepthStencilImage, mDepthStencilMemory, 0), "vkBindImageMemory"); + + const VkImageViewCreateInfo viewInfo{ + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .image = mDepthStencilImage, + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = mDepthStencilFormat, + .subresourceRange = { VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT, 0, 1, 0, 1 }, + }; + requireSuccess(vkCreateImageView(mDevice, &viewInfo, nullptr, &mDepthStencilView), "vkCreateImageView"); + } + + void WindowSurface::destroyDepthStencil() { + if (mDepthStencilView != VK_NULL_HANDLE) { + vkDestroyImageView(mDevice, mDepthStencilView, nullptr); + } + if (mDepthStencilImage != VK_NULL_HANDLE) { + vkDestroyImage(mDevice, mDepthStencilImage, nullptr); + } + if (mDepthStencilMemory != VK_NULL_HANDLE) { + vkFreeMemory(mDevice, mDepthStencilMemory, nullptr); + } + mDepthStencilView = VK_NULL_HANDLE; + mDepthStencilImage = VK_NULL_HANDLE; + mDepthStencilMemory = VK_NULL_HANDLE; + } + + void WindowSurface::destroySwapchainResources() { + destroyDepthStencil(); + for (VkFramebuffer framebuffer : mSwapchainFramebuffers) { + vkDestroyFramebuffer(mDevice, framebuffer, nullptr); + } + for (VkImageView view : mSwapchainImageViews) { + vkDestroyImageView(mDevice, view, nullptr); + } + for (VkSemaphore semaphore : mRenderFinishedSemaphores) { + vkDestroySemaphore(mDevice, semaphore, nullptr); + } + mSwapchainFramebuffers.clear(); + mSwapchainImageViews.clear(); + mRenderFinishedSemaphores.clear(); + mSwapchainImages.clear(); + } + + void WindowSurface::destroySwapchain() { + destroySwapchainResources(); + if (mSwapchain != VK_NULL_HANDLE) { + vkDestroySwapchainKHR(mDevice, mSwapchain, nullptr); + } + mSwapchain = VK_NULL_HANDLE; + } + + void WindowSurface::waitForFramesInFlight() { + VkFence fences[kFramesInFlight] = {}; + for (uint32_t index = 0; index < kFramesInFlight; index++) { + fences[index] = mFrameSlots[index].inFlight; + } + vkWaitForFences(mDevice, kFramesInFlight, fences, VK_TRUE, UINT64_MAX); + } + + // Only frames using the old views must finish; the old swapchain is handed over so presentation has no gap. + void WindowSurface::recreateSwapchain(VkExtent2D requested) { + waitForFramesInFlight(); + vkQueueWaitIdle(mGpu.graphicsQueue()); + destroySwapchainResources(); + + VkSwapchainKHR previous = mSwapchain; + mSwapchain = VK_NULL_HANDLE; + createSwapchain(atLeastOnePixel(requested), previous); + mRequestedExtent = requested; + mSwapchainRecreations++; + } + + bool WindowSurface::swapchainMatches(VkExtent2D extent) const { + return mRequestedExtent.width == extent.width && mRequestedExtent.height == extent.height; + } + + bool WindowSurface::surfaceChanged() const { + VkSurfaceCapabilitiesKHR capabilities{}; + if (vkGetPhysicalDeviceSurfaceCapabilitiesKHR(mGpu.physicalDevice(), mSurface, &capabilities) != VK_SUCCESS) { + return true; + } + return capabilities.currentTransform != mSurfaceTransform || + capabilities.currentExtent.width != mSurfaceExtent.width || + capabilities.currentExtent.height != mSurfaceExtent.height; + } + + std::optional WindowSurface::beginFrame() { + const VkExtent2D extent = mSource.framebufferExtent(); + if (isEmpty(extent) || mSurface == VK_NULL_HANDLE) { + return std::nullopt; + } + if (!swapchainMatches(extent)) { + recreateSwapchain(extent); + } + + FrameSlot& slot = mFrameSlots[mCurrentSlot]; + VkResult acquired = VK_SUCCESS; + { + const ProfileScope scope(mProfiler, mAcquireSection); + vkWaitForFences(mDevice, 1, &slot.inFlight, VK_TRUE, UINT64_MAX); + acquired = + vkAcquireNextImageKHR(mDevice, mSwapchain, UINT64_MAX, slot.imageAvailable, VK_NULL_HANDLE, &mAcquiredImage); + } + if (acquired == VK_ERROR_OUT_OF_DATE_KHR) { + recreateSwapchain(extent); + return std::nullopt; + } + if (acquired != VK_SUBOPTIMAL_KHR) { + requireSuccess(acquired, "vkAcquireNextImageKHR"); + } + + requireSuccess(vkResetFences(mDevice, 1, &slot.inFlight), "vkResetFences"); + recordFrameStart(slot.commandBuffer, mAcquiredImage); + mFrameActive = true; + return GpuFrame{ slot.commandBuffer, mCurrentSlot, mSwapchainExtent }; + } + + void WindowSurface::recordFrameStart(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + requireSuccess(vkResetCommandBuffer(commandBuffer, 0), "vkResetCommandBuffer"); + const VkCommandBufferBeginInfo beginInfo{ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; + requireSuccess(vkBeginCommandBuffer(commandBuffer, &beginInfo), "vkBeginCommandBuffer"); + mFrameTimer->begin(commandBuffer); + + std::array clearValues{}; + clearValues[0].color = { { 0.0f, 0.0f, 0.0f, mTranslucent ? 0.0f : 1.0f } }; + clearValues[1].depthStencil = { 1.0f, 0 }; + + const VkRenderPassBeginInfo renderPassInfo{ + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, + .renderPass = mRenderPass, + .framebuffer = mSwapchainFramebuffers[imageIndex], + .renderArea = { .offset = { 0, 0 }, .extent = mSwapchainExtent }, + .clearValueCount = (uint32_t) clearValues.size(), + .pClearValues = clearValues.data(), + }; + vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + + const VkViewport viewport{ + .x = 0.0f, + .y = 0.0f, + .width = (float) mSwapchainExtent.width, + .height = (float) mSwapchainExtent.height, + .minDepth = 0.0f, + .maxDepth = 1.0f, + }; + vkCmdSetViewport(commandBuffer, 0, 1, &viewport); + + const VkRect2D scissor{ { 0, 0 }, mSwapchainExtent }; + vkCmdSetScissor(commandBuffer, 0, 1, &scissor); + } + + void WindowSurface::endFrame() { + if (!mFrameActive) { + return; + } + mFrameActive = false; + + const ProfileScope scope(mProfiler, mSubmitSection); + FrameSlot& slot = mFrameSlots[mCurrentSlot]; + vkCmdEndRenderPass(slot.commandBuffer); + mFrameTimer->end(slot.commandBuffer); + requireSuccess(vkEndCommandBuffer(slot.commandBuffer), "vkEndCommandBuffer"); + + VkSemaphore renderFinished = mRenderFinishedSemaphores[mAcquiredImage]; + const VkPipelineStageFlags waitStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + const VkSubmitInfo submitInfo{ + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .waitSemaphoreCount = 1, + .pWaitSemaphores = &slot.imageAvailable, + .pWaitDstStageMask = &waitStage, + .commandBufferCount = 1, + .pCommandBuffers = &slot.commandBuffer, + .signalSemaphoreCount = 1, + .pSignalSemaphores = &renderFinished, + }; + requireSuccess(vkQueueSubmit(mGpu.graphicsQueue(), 1, &submitInfo, slot.inFlight), "vkQueueSubmit"); + + const VkPresentInfoKHR presentInfo{ + .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, + .waitSemaphoreCount = 1, + .pWaitSemaphores = &renderFinished, + .swapchainCount = 1, + .pSwapchains = &mSwapchain, + .pImageIndices = &mAcquiredImage, + }; + const VkResult presented = vkQueuePresentKHR(mGpu.graphicsQueue(), &presentInfo); + mCurrentSlot = (mCurrentSlot + 1) % kFramesInFlight; + + const bool outOfDate = presented == VK_ERROR_OUT_OF_DATE_KHR; + if (outOfDate || (presented == VK_SUBOPTIMAL_KHR && surfaceChanged())) { + recreateSwapchain(mSource.framebufferExtent()); + return; + } + if (presented != VK_SUBOPTIMAL_KHR) { + requireSuccess(presented, "vkQueuePresentKHR"); + } + } + + void WindowSurface::syncToSource() { + const VkExtent2D extent = mSource.framebufferExtent(); + if (isEmpty(extent) || mSurface == VK_NULL_HANDLE) { + return; + } + recreateSwapchain(extent); + } + + void WindowSurface::releaseSurface() { + if (mSurface == VK_NULL_HANDLE) { + return; + } + mGpu.waitIdle(); + destroySwapchain(); + mSwapchainExtent = { 0, 0 }; + vkDestroySurfaceKHR(mGpu.instance(), mSurface, nullptr); + mSurface = VK_NULL_HANDLE; + } + + void WindowSurface::recreateSurface() { + if (mSurface != VK_NULL_HANDLE) { + return; + } + createSurface(); + const VkExtent2D extent = mSource.framebufferExtent(); + createSwapchain(atLeastOnePixel(extent)); + mRequestedExtent = extent; + } +} diff --git a/GraphicApplication/src/gpu/public/GpuDevice.hpp b/GraphicApplication/src/gpu/public/GpuDevice.hpp new file mode 100644 index 00000000..82e61dcf --- /dev/null +++ b/GraphicApplication/src/gpu/public/GpuDevice.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include "RenderTargetRegistry.hpp" + +#include + +#include + +namespace tp { + + // The Vulkan instance, device and graphics queue shared by every window of the process. + class GpuDevice { + public: + explicit GpuDevice(const std::vector& surfaceExtensions); + ~GpuDevice(); + + GpuDevice(const GpuDevice&) = delete; + GpuDevice& operator=(const GpuDevice&) = delete; + + [[nodiscard]] VkInstance instance() const { return mInstance; } + [[nodiscard]] VkPhysicalDevice physicalDevice() const { return mPhysicalDevice; } + [[nodiscard]] VkDevice device() const { return mDevice; } + [[nodiscard]] VkQueue graphicsQueue() const { return mGraphicsQueue; } + [[nodiscard]] uint32_t graphicsQueueFamily() const { return mGraphicsQueueFamily; } + [[nodiscard]] VkCommandPool commandPool() const { return mCommandPool; } + [[nodiscard]] const char* deviceName() const { return mProperties.deviceName; } + + [[nodiscard]] bool canPresent(VkSurfaceKHR surface) const; + [[nodiscard]] uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) const; + + [[nodiscard]] VkCommandBuffer beginOneShotCommands() const; + void endOneShotCommands(VkCommandBuffer commandBuffer) const; + + void waitIdle() const; + + [[nodiscard]] RenderTargetRegistry& renderTargets() { return mRenderTargets; } + + private: + void createInstance(const std::vector& surfaceExtensions); + void selectPhysicalDevice(); + void createLogicalDevice(); + void createCommandPool(); + + VkInstance mInstance = VK_NULL_HANDLE; + VkPhysicalDevice mPhysicalDevice = VK_NULL_HANDLE; + VkPhysicalDeviceProperties mProperties{}; + VkDevice mDevice = VK_NULL_HANDLE; + uint32_t mGraphicsQueueFamily = UINT32_MAX; + VkQueue mGraphicsQueue = VK_NULL_HANDLE; + VkCommandPool mCommandPool = VK_NULL_HANDLE; + RenderTargetRegistry mRenderTargets; + }; +} diff --git a/GraphicApplication/src/gpu/public/GpuError.hpp b/GraphicApplication/src/gpu/public/GpuError.hpp new file mode 100644 index 00000000..554ebee3 --- /dev/null +++ b/GraphicApplication/src/gpu/public/GpuError.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include + +#include +#include + +namespace tp { + + // A Vulkan call failed in a way the renderer cannot recover from. + class GpuError : public std::runtime_error { + public: + GpuError(const char* operation, VkResult result) : + std::runtime_error(std::string(operation) + " failed: VkResult " + std::to_string((int) result)), + mResult(result) {} + + [[nodiscard]] VkResult result() const { return mResult; } + + private: + VkResult mResult; + }; + + inline void requireSuccess(VkResult result, const char* operation) { + if (result != VK_SUCCESS) { + throw GpuError(operation, result); + } + } +} diff --git a/GraphicApplication/src/gpu/public/RenderTargetRegistry.hpp b/GraphicApplication/src/gpu/public/RenderTargetRegistry.hpp new file mode 100644 index 00000000..aa22d56f --- /dev/null +++ b/GraphicApplication/src/gpu/public/RenderTargetRegistry.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include +#include + +namespace tp { + + struct RenderTargetEntry { + VkImageView view = VK_NULL_HANDLE; + VkSampler sampler = VK_NULL_HANDLE; + int width = 0; + int height = 0; + VkImage image = VK_NULL_HANDLE; + }; + + class RenderTargetRegistry { + public: + uint32_t registerTarget(const RenderTargetEntry& entry); + void updateTarget(uint32_t id, const RenderTargetEntry& entry); + void unregisterTarget(uint32_t id); + [[nodiscard]] bool resolve(uint32_t id, RenderTargetEntry& out) const; + + private: + uint32_t mNextId = 1; + std::unordered_map mEntries; + }; +} diff --git a/GraphicApplication/src/gpu/public/WindowSurface.hpp b/GraphicApplication/src/gpu/public/WindowSurface.hpp new file mode 100644 index 00000000..46832c15 --- /dev/null +++ b/GraphicApplication/src/gpu/public/WindowSurface.hpp @@ -0,0 +1,125 @@ +#pragma once + +#include "GpuDevice.hpp" + +#include + +#include +#include +#include +#include + +namespace tp { + + class GpuTimer; + class Profiler; + + // The native window a WindowSurface presents into. + class VulkanSurfaceSource { + public: + virtual ~VulkanSurfaceSource() = default; + + [[nodiscard]] virtual VkSurfaceKHR createSurface(VkInstance instance) const = 0; + [[nodiscard]] virtual VkExtent2D framebufferExtent() const = 0; + [[nodiscard]] virtual bool wantsTranslucency() const = 0; + virtual void onTranslucencyResolved(bool translucent) = 0; + }; + + // The frame being recorded: renderers record into commandBuffer and key per-frame resources by slot. + struct GpuFrame { + VkCommandBuffer commandBuffer = VK_NULL_HANDLE; + uint32_t slot = 0; + VkExtent2D extent{}; + }; + + // Presentation of one window: surface, swapchain, render pass and frame synchronisation. + class WindowSurface { + public: + static constexpr uint32_t kFramesInFlight = 2; + + WindowSurface(GpuDevice& gpu, VulkanSurfaceSource& source, Profiler& profiler); + ~WindowSurface(); + + WindowSurface(const WindowSurface&) = delete; + WindowSurface& operator=(const WindowSurface&) = delete; + + [[nodiscard]] GpuDevice& gpu() const { return mGpu; } + [[nodiscard]] VkRenderPass renderPass() const { return mRenderPass; } + [[nodiscard]] uint32_t imageCount() const { return (uint32_t) mSwapchainImages.size(); } + [[nodiscard]] bool isTranslucent() const { return mTranslucent; } + [[nodiscard]] bool hasSurface() const { return mSurface != VK_NULL_HANDLE; } + [[nodiscard]] uint64_t swapchainRecreations() const { return mSwapchainRecreations; } + + [[nodiscard]] std::optional beginFrame(); + void endFrame(); + + void syncToSource(); + void releaseSurface(); + void recreateSurface(); + + private: + struct FrameSlot { + VkCommandBuffer commandBuffer = VK_NULL_HANDLE; + VkSemaphore imageAvailable = VK_NULL_HANDLE; + VkFence inFlight = VK_NULL_HANDLE; + }; + + void createSurface(); + void selectCompositeAlpha(); + void createRenderPass(); + void createFrameSlots(); + void destroyFrameSlots(); + + [[nodiscard]] VkPresentModeKHR selectPresentMode() const; + [[nodiscard]] VkFormat selectDepthStencilFormat() const; + [[nodiscard]] bool surfaceChanged() const; + [[nodiscard]] bool swapchainMatches(VkExtent2D extent) const; + + void createSwapchain(VkExtent2D requested, VkSwapchainKHR previous = VK_NULL_HANDLE); + void createSwapchainImages(); + void createDepthStencil(); + void destroyDepthStencil(); + void destroySwapchainResources(); + void destroySwapchain(); + void recreateSwapchain(VkExtent2D requested); + void waitForFramesInFlight(); + void recordFrameStart(VkCommandBuffer commandBuffer, uint32_t imageIndex); + + GpuDevice& mGpu; + VkDevice mDevice; + VulkanSurfaceSource& mSource; + Profiler& mProfiler; + + VkSurfaceKHR mSurface = VK_NULL_HANDLE; + VkCompositeAlphaFlagBitsKHR mCompositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + bool mTranslucent = false; + VkSurfaceTransformFlagBitsKHR mSurfaceTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; + VkExtent2D mSurfaceExtent{ 0, 0 }; + VkExtent2D mRequestedExtent{ 0, 0 }; + + VkFormat mSwapchainFormat{}; + VkFormat mDepthStencilFormat{}; + VkRenderPass mRenderPass = VK_NULL_HANDLE; + + VkSwapchainKHR mSwapchain = VK_NULL_HANDLE; + VkExtent2D mSwapchainExtent{}; + std::vector mSwapchainImages; + std::vector mSwapchainImageViews; + std::vector mSwapchainFramebuffers; + std::vector mRenderFinishedSemaphores; + uint64_t mSwapchainRecreations = 0; + + VkImage mDepthStencilImage = VK_NULL_HANDLE; + VkDeviceMemory mDepthStencilMemory = VK_NULL_HANDLE; + VkImageView mDepthStencilView = VK_NULL_HANDLE; + + FrameSlot mFrameSlots[kFramesInFlight]; + uint32_t mCurrentSlot = 0; + uint32_t mAcquiredImage = 0; + bool mFrameActive = false; + + std::unique_ptr mFrameTimer; + uint32_t mAcquireSection = 0; + uint32_t mSubmitSection = 0; + }; +} diff --git a/GraphicApplication/src/window/private/Window.cpp b/GraphicApplication/src/window/private/Window.cpp new file mode 100644 index 00000000..ba992cf9 --- /dev/null +++ b/GraphicApplication/src/window/private/Window.cpp @@ -0,0 +1,64 @@ +#include "Window.hpp" + +#include "WindowContext.hpp" + +using namespace tp; + +WindowContext::WindowContext(GpuDevice& gpu, Profiler& profiler, const WindowOptions& options) { + platform = createPlatformWindow(*this, options.contentSize, options.identity, options.platform); + surface = std::make_unique(gpu, *platform, profiler); +} + +WindowContext::~WindowContext() { + surface.reset(); + platform.reset(); +} + +InputSink& WindowContext::rawInput() { + if (!platformInputEnabled) { + return mutedInput; + } + return coalescer; +} + +void WindowContext::onSurfaceAvailable() { + if (surface) { + surface->recreateSurface(); + } +} + +void WindowContext::onSurfaceLost() { + if (surface) { + surface->releaseSurface(); + } +} + +std::unique_ptr Window::createGpuDevice() { + return std::make_unique(platformSurfaceExtensions()); +} + +std::filesystem::path Window::platformResourceDirectory() { return tp::platformResourceDirectory(); } + +Window::Window(GpuDevice& gpu, Profiler& profiler, const WindowOptions& options) : + mContext(std::make_unique(gpu, profiler, options)), + mEventLoop(*mContext), + mInput(*mContext), + mControls(*mContext), + mServices(*mContext) {} + +Window::~Window() = default; + +WindowSurface& Window::surface() { return *mContext->surface; } + +Vec2F Window::contentSize() const { return mContext->platform->frame().contentSize(); } + +Vec2F Window::framebufferSize() const { + const VkExtent2D extent = mContext->platform->framebufferExtent(); + return { (halnf) extent.width, (halnf) extent.height }; +} + +halnf Window::contentScale() const { return mContext->platform->scale(); } + +const WindowFrame& Window::frame() const { return mContext->platform->frame(); } + +PlatformInfo Window::platformInfo() const { return mContext->platform->platformInfo(); } diff --git a/GraphicApplication/src/window/private/WindowContext.hpp b/GraphicApplication/src/window/private/WindowContext.hpp new file mode 100644 index 00000000..0e042bab --- /dev/null +++ b/GraphicApplication/src/window/private/WindowContext.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include "InputCoalescer.hpp" +#include "InputState.hpp" +#include "PlatformWindow.hpp" +#include "Window.hpp" +#include "WindowSurface.hpp" + +#include + +namespace tp { + + // Everything one window owns; the public roles are views onto it. + class WindowContext final : public PlatformWindowHost { + public: + WindowContext(GpuDevice& gpu, Profiler& profiler, const WindowOptions& options); + ~WindowContext() override; + + WindowContext(const WindowContext&) = delete; + WindowContext& operator=(const WindowContext&) = delete; + + [[nodiscard]] InputSink& rawInput() override; + void onContentResized() override { contentResized = true; } + void onSurfaceAvailable() override; + void onSurfaceLost() override; + + InputCoalescer coalescer; + InputQueue inputQueue; + DiscardingInputSink mutedInput; + bool platformInputEnabled = true; + bool contentResized = false; + + std::unique_ptr platform; + std::unique_ptr surface; + }; +} diff --git a/GraphicApplication/src/window/private/WindowControls.cpp b/GraphicApplication/src/window/private/WindowControls.cpp new file mode 100644 index 00000000..028cfecb --- /dev/null +++ b/GraphicApplication/src/window/private/WindowControls.cpp @@ -0,0 +1,19 @@ +#include "WindowControls.hpp" + +#include "WindowContext.hpp" + +using namespace tp; + +bool WindowControls::shouldClose() const { return mContext.platform->shouldClose(); } + +void WindowControls::requestClose() { mContext.platform->requestClose(); } + +void WindowControls::minimize() { mContext.platform->minimize(); } + +bool WindowControls::isMaximized() const { return mContext.platform->isMaximized(); } + +bool WindowControls::isFullScreen() const { return mContext.platform->isFullScreen(); } + +bool WindowControls::isFocused() const { return mContext.platform->isFocused(); } + +DesktopWindowControls* WindowControls::desktop() { return mContext.platform->desktopControls(); } diff --git a/GraphicApplication/src/window/private/WindowEventLoop.cpp b/GraphicApplication/src/window/private/WindowEventLoop.cpp new file mode 100644 index 00000000..79300d60 --- /dev/null +++ b/GraphicApplication/src/window/private/WindowEventLoop.cpp @@ -0,0 +1,45 @@ +#include "WindowEventLoop.hpp" + +#include "WindowContext.hpp" + +#include + +using namespace tp; + +void WindowEventLoop::processEvents(bool wait) { mContext.platform->dispatchEvents(wait ? -1.0f : 0.0f); } + +void WindowEventLoop::processEventsFor(halnf timeoutMs) { + mContext.platform->dispatchEvents(std::max(timeoutMs, 0.0f)); +} + +void WindowEventLoop::wake() { mContext.platform->wake(); } + +bool WindowEventLoop::isWaitingForFrame() const { return mContext.platform->isWaitingForFrame(); } + +bool WindowEventLoop::hasPendingWork() const { + return mContext.contentResized || !mContext.coalescer.isEmpty() || mContext.platform->hasPendingConfigure(); +} + +uint64_t WindowEventLoop::swapchainRecreations() const { return mContext.surface->swapchainRecreations(); } + +void WindowEventLoop::waitForReady() { + mContext.platform->waitUntilConfigured(); + mContext.surface->syncToSource(); +} + +size_t WindowEventLoop::advanceFrame() { + mContext.platform->applyPendingConfigure(); + mContext.coalescer.deliver(mContext.inputQueue); + + const size_t eventCount = mContext.inputQueue.pendingEventCount(); + mContext.inputQueue.applyPendingEvents(); + mContext.contentResized = false; + return eventCount; +} + +std::optional WindowEventLoop::frameBegin() { return mContext.surface->beginFrame(); } + +void WindowEventLoop::frameEnd() { + mContext.platform->commitFrameGeometry(); + mContext.surface->endFrame(); +} diff --git a/GraphicApplication/src/window/private/WindowInput.cpp b/GraphicApplication/src/window/private/WindowInput.cpp new file mode 100644 index 00000000..cad6d7f3 --- /dev/null +++ b/GraphicApplication/src/window/private/WindowInput.cpp @@ -0,0 +1,27 @@ +#include "WindowInput.hpp" + +#include "WindowContext.hpp" + +using namespace tp; + +const InputState& WindowInput::state() const { return mContext.inputQueue.state(); } + +InputSink& WindowInput::injectInput() { return mContext.coalescer; } + +void WindowInput::setRawInputTap(InputSink* tap) { mContext.coalescer.setTap(tap); } + +void WindowInput::setDeliveryPolicies(const InputDeliveryPolicies& policies) { + mContext.coalescer.setPolicies(policies); +} + +const CoalescingStatistics& WindowInput::statistics() const { return mContext.coalescer.statistics(); } + +void WindowInput::setPlatformInputEnabled(bool enabled) { + if (enabled == mContext.platformInputEnabled) { + return; + } + mContext.platformInputEnabled = enabled; + mContext.platform->setAcceptsInput(enabled); +} + +bool WindowInput::isPlatformInputEnabled() const { return mContext.platformInputEnabled; } diff --git a/GraphicApplication/src/window/private/WindowServices.cpp b/GraphicApplication/src/window/private/WindowServices.cpp new file mode 100644 index 00000000..cf53bc02 --- /dev/null +++ b/GraphicApplication/src/window/private/WindowServices.cpp @@ -0,0 +1,19 @@ +#include "WindowServices.hpp" + +#include "WindowContext.hpp" + +using namespace tp; + +void WindowServices::setCursor(CursorShape shape) { mContext.platform->setCursor(shape); } + +void WindowServices::setClipboardText(const std::string& text) { mContext.platform->setClipboardText(text); } + +std::string WindowServices::clipboardText() { return mContext.platform->clipboardText(); } + +ClipboardImage WindowServices::clipboardImage() { return mContext.platform->clipboardImage(); } + +bool WindowServices::openUrl(const std::string& url) { return mContext.platform->openUrl(url); } + +void WindowServices::setTextInputEnabled(bool enabled) { mContext.platform->setTextInputEnabled(enabled); } + +void WindowServices::setTextInputRect(const RectF& contentRect) { mContext.platform->setTextInputRect(contentRect); } diff --git a/GraphicApplication/src/window/public/Window.hpp b/GraphicApplication/src/window/public/Window.hpp new file mode 100644 index 00000000..e9762663 --- /dev/null +++ b/GraphicApplication/src/window/public/Window.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include "GpuDevice.hpp" +#include "InputState.hpp" +#include "PlatformContext.hpp" +#include "PlatformInfo.hpp" +#include "WindowControls.hpp" +#include "WindowEventLoop.hpp" +#include "WindowFrame.hpp" +#include "WindowInput.hpp" +#include "WindowServices.hpp" +#include "WindowSurface.hpp" +#include "WindowTypes.hpp" + +#include +#include + +namespace tp { + + class Profiler; + class WindowContext; + + struct WindowOptions { + Vec2F contentSize = { 1500, 900 }; + WindowIdentity identity; + PlatformContext platform; + }; + + class Window { + public: + // The GPU device with the surface extensions this platform's windows need; share it between windows. + [[nodiscard]] static std::unique_ptr createGpuDevice(); + [[nodiscard]] static std::filesystem::path platformResourceDirectory(); + + Window(GpuDevice& gpu, Profiler& profiler, const WindowOptions& options); + ~Window(); + + Window(const Window&) = delete; + Window& operator=(const Window&) = delete; + + [[nodiscard]] WindowEventLoop& loop() { return mEventLoop; } + [[nodiscard]] WindowInput& input() { return mInput; } + [[nodiscard]] WindowControls& controls() { return mControls; } + [[nodiscard]] WindowServices& services() { return mServices; } + [[nodiscard]] WindowSurface& surface(); + + [[nodiscard]] Vec2F contentSize() const; + [[nodiscard]] Vec2F framebufferSize() const; + [[nodiscard]] halnf contentScale() const; + [[nodiscard]] const WindowFrame& frame() const; + [[nodiscard]] PlatformInfo platformInfo() const; + + private: + std::unique_ptr mContext; + WindowEventLoop mEventLoop; + WindowInput mInput; + WindowControls mControls; + WindowServices mServices; + }; +} diff --git a/GraphicApplication/src/window/public/WindowControls.hpp b/GraphicApplication/src/window/public/WindowControls.hpp new file mode 100644 index 00000000..49ff8797 --- /dev/null +++ b/GraphicApplication/src/window/public/WindowControls.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include "DesktopWindowControls.hpp" + +namespace tp { + + class WindowContext; + + class WindowControls { + public: + [[nodiscard]] bool shouldClose() const; + void requestClose(); + void minimize(); + [[nodiscard]] bool isMaximized() const; + [[nodiscard]] bool isFullScreen() const; + [[nodiscard]] bool isFocused() const; + + // Moving, resizing and maximizing; null where the platform has no desktop window manager. + [[nodiscard]] DesktopWindowControls* desktop(); + + private: + friend class Window; + explicit WindowControls(WindowContext& context) : + mContext(context) {} + + WindowContext& mContext; + }; +} diff --git a/GraphicApplication/src/window/public/WindowEventLoop.hpp b/GraphicApplication/src/window/public/WindowEventLoop.hpp new file mode 100644 index 00000000..a2657e01 --- /dev/null +++ b/GraphicApplication/src/window/public/WindowEventLoop.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include "Vec.hpp" +#include "WindowSurface.hpp" + +#include +#include +#include + +namespace tp { + + class WindowContext; + + class WindowEventLoop { + public: + void processEvents(bool wait = true); + void processEventsFor(halnf timeoutMs); + void wake(); + [[nodiscard]] bool isWaitingForFrame() const; + [[nodiscard]] bool hasPendingWork() const; + [[nodiscard]] uint64_t swapchainRecreations() const; + void waitForReady(); + + // Applies pending window geometry and moves collected input into input().state(); returns the event count. + size_t advanceFrame(); + [[nodiscard]] std::optional frameBegin(); + void frameEnd(); + + private: + friend class Window; + explicit WindowEventLoop(WindowContext& context) : + mContext(context) {} + + WindowContext& mContext; + }; +} diff --git a/GraphicApplication/src/window/public/WindowInput.hpp b/GraphicApplication/src/window/public/WindowInput.hpp new file mode 100644 index 00000000..a70cb91d --- /dev/null +++ b/GraphicApplication/src/window/public/WindowInput.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "InputCoalescer.hpp" +#include "InputState.hpp" + +namespace tp { + + class WindowContext; + + class WindowInput { + public: + [[nodiscard]] const InputState& state() const; + [[nodiscard]] InputSink& injectInput(); + void setRawInputTap(InputSink* tap); + void setDeliveryPolicies(const InputDeliveryPolicies& policies); + [[nodiscard]] const CoalescingStatistics& statistics() const; + void setPlatformInputEnabled(bool enabled); + [[nodiscard]] bool isPlatformInputEnabled() const; + + private: + friend class Window; + explicit WindowInput(WindowContext& context) : + mContext(context) {} + + WindowContext& mContext; + }; +} diff --git a/GraphicApplication/src/window/public/WindowServices.hpp b/GraphicApplication/src/window/public/WindowServices.hpp new file mode 100644 index 00000000..3e43b017 --- /dev/null +++ b/GraphicApplication/src/window/public/WindowServices.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include "Rect.hpp" +#include "WindowTypes.hpp" + +#include + +namespace tp { + class WindowContext; + + class WindowServices { + public: + void setCursor(CursorShape shape); + void setClipboardText(const std::string& text); + [[nodiscard]] std::string clipboardText(); + [[nodiscard]] ClipboardImage clipboardImage(); + bool openUrl(const std::string& url); + void setTextInputEnabled(bool enabled); + void setTextInputRect(const RectF& contentRect); + + private: + friend class Window; + explicit WindowServices(WindowContext& context) : + mContext(context) {} + + WindowContext& mContext; + }; +} diff --git a/GraphicApplication/tests/LinuxInputCodesTests.cpp b/GraphicApplication/tests/LinuxInputCodesTests.cpp new file mode 100644 index 00000000..e4ded266 --- /dev/null +++ b/GraphicApplication/tests/LinuxInputCodesTests.cpp @@ -0,0 +1,39 @@ +#include "UnitTest++/UnitTest++.h" + +#include "LinuxInputCodes.hpp" + +#include + +using namespace tp; + +SUITE(LinuxInputCodes) { + TEST(LettersAndDigitsKeepTheirKeys) { + CHECK(keyFromEvdev(KEY_A) == Key::A); + CHECK(keyFromEvdev(KEY_Z) == Key::Z); + CHECK(keyFromEvdev(KEY_0) == Key::Digit0); + CHECK(keyFromEvdev(KEY_9) == Key::Digit9); + } + + TEST(NavigationAndEditingKeysAreMapped) { + CHECK(keyFromEvdev(KEY_ESC) == Key::Escape); + CHECK(keyFromEvdev(KEY_ENTER) == Key::Enter); + CHECK(keyFromEvdev(KEY_BACKSPACE) == Key::Backspace); + CHECK(keyFromEvdev(KEY_LEFT) == Key::Left); + CHECK(keyFromEvdev(KEY_PAGEDOWN) == Key::PageDown); + } + + TEST(ModifiersKeepSides) { + CHECK(keyFromEvdev(KEY_LEFTCTRL) == Key::LeftControl); + CHECK(keyFromEvdev(KEY_RIGHTSHIFT) == Key::RightShift); + CHECK(keyFromEvdev(KEY_LEFTALT) == Key::LeftAlt); + } + + TEST(UnknownKeysAreIgnored) { CHECK(!keyFromEvdev(KEY_MAX).has_value()); } + + TEST(LinuxButtonsMapToPointerButtons) { + CHECK(pointerButtonFromLinux(BTN_LEFT) == PointerButton::Left); + CHECK(pointerButtonFromLinux(BTN_RIGHT) == PointerButton::Right); + CHECK(pointerButtonFromLinux(BTN_MIDDLE) == PointerButton::Middle); + CHECK(!pointerButtonFromLinux(BTN_TOUCH).has_value()); + } +} diff --git a/GraphicApplication/tests/PlatformInfoTests.cpp b/GraphicApplication/tests/PlatformInfoTests.cpp new file mode 100644 index 00000000..c2f2e4dd --- /dev/null +++ b/GraphicApplication/tests/PlatformInfoTests.cpp @@ -0,0 +1,47 @@ +#include "UnitTest++/UnitTest++.h" + +#include "PlatformInfo.hpp" + +using namespace tp; + +SUITE(PlatformInfo) { + TEST(DefaultsDescribeAnUnknownPlatform) { + const PlatformInfo info; + + CHECK(info.system == OperatingSystem::Unknown); + CHECK(info.formFactor == FormFactor::Unknown); + CHECK(!info.isTouchFirst()); + } + + TEST(TabletsAndPhonesAreTouchFirst) { + PlatformInfo info; + + info.formFactor = FormFactor::Tablet; + CHECK(info.isTouchFirst()); + info.formFactor = FormFactor::Phone; + CHECK(info.isTouchFirst()); + info.formFactor = FormFactor::Desktop; + CHECK(!info.isTouchFirst()); + } + + TEST(DescriptionNamesSystemFormFactorAndCapabilities) { + PlatformInfo info; + info.system = OperatingSystem::Android; + info.systemVersion = "16"; + info.formFactor = FormFactor::Tablet; + info.deviceName = "Google Pixel Tablet"; + info.contentScale = 2; + info.screenSize = { 1280, 800 }; + info.hasTouch = true; + info.hasPen = true; + + const std::string text = describe(info); + + CHECK(text.find("Android 16") != std::string::npos); + CHECK(text.find("tablet Google Pixel Tablet") != std::string::npos); + CHECK(text.find("scale 2.00") != std::string::npos); + CHECK(text.find("screen 1280x800") != std::string::npos); + CHECK(text.find("touch pen") != std::string::npos); + CHECK(text.find("mouse") == std::string::npos); + } +} diff --git a/GraphicApplication/tests/PointerPinchConverterTests.cpp b/GraphicApplication/tests/PointerPinchConverterTests.cpp new file mode 100644 index 00000000..645594c1 --- /dev/null +++ b/GraphicApplication/tests/PointerPinchConverterTests.cpp @@ -0,0 +1,57 @@ +#include "UnitTest++/UnitTest++.h" + +#include "PointerPinchConverter.hpp" + +SUITE(PointerPinchConverter) { + TEST(TotalScaleBecomesIncrementalAndMultipliesBack) { + tp::PointerPinchConverter converter; + const tp::TouchGesture begin = converter.begin({ 10, 20 }); + CHECK(begin.phase == tp::GesturePhase::Begin); + CHECK(begin.center == tp::Vec2F(10.0f, 20.0f)); + + tp::halnf product = 1; + for (tp::halnf total : { 1.2f, 1.5f, 1.5f, 0.9f }) { + const std::optional update = converter.update({ 10, 20 }, { 1, 2 }, total, 0); + REQUIRE CHECK(update.has_value()); + CHECK(update->pan == tp::Vec2F(1.0f, 2.0f)); + product *= update->scale; + CHECK_CLOSE(total, product, 0.0001f); + } + + const std::optional end = converter.end({ 10, 20 }); + REQUIRE CHECK(end.has_value()); + CHECK(end->phase == tp::GesturePhase::End); + } + + TEST(RotationIsConvertedToRadians) { + tp::PointerPinchConverter converter; + (void) converter.begin({ 0, 0 }); + const std::optional update = converter.update({ 0, 0 }, { 0, 0 }, 1, 90); + REQUIRE CHECK(update.has_value()); + CHECK_CLOSE(1.5707963f, update->rotation, 0.0001f); + } + + TEST(UpdatesOutsideAGestureAreIgnoredAndNewGestureRestartsScale) { + tp::PointerPinchConverter converter; + CHECK(!converter.update({ 0, 0 }, { 0, 0 }, 2, 0).has_value()); + CHECK(!converter.end({ 0, 0 }).has_value()); + + (void) converter.begin({ 0, 0 }); + (void) converter.update({ 0, 0 }, { 0, 0 }, 2, 0); + (void) converter.end({ 0, 0 }); + (void) converter.begin({ 0, 0 }); + const std::optional fresh = converter.update({ 0, 0 }, { 0, 0 }, 1.25f, 0); + REQUIRE CHECK(fresh.has_value()); + CHECK_CLOSE(1.25f, fresh->scale, 0.0001f); + } + + TEST(ZeroScaleFromCompositorIsTreatedAsNoChange) { + tp::PointerPinchConverter converter; + (void) converter.begin({ 0, 0 }); + const std::optional zero = converter.update({ 0, 0 }, { 0, 0 }, 0, 0); + REQUIRE CHECK(zero.has_value()); + CHECK_CLOSE(1, zero->scale, 0.0001f); + const std::optional next = converter.update({ 0, 0 }, { 0, 0 }, 2, 0); + CHECK_CLOSE(2, next->scale, 0.0001f); + } +} diff --git a/GraphicApplication/tests/RenderTargetRegistryTests.cpp b/GraphicApplication/tests/RenderTargetRegistryTests.cpp new file mode 100644 index 00000000..4ac457c2 --- /dev/null +++ b/GraphicApplication/tests/RenderTargetRegistryTests.cpp @@ -0,0 +1,39 @@ +#include "RenderTargetRegistry.hpp" + +#include "UnitTest++/UnitTest++.h" + +using namespace tp; + +SUITE(RenderTargetRegistry) { + TEST(InstancesKeepIndependentTargets) { + RenderTargetRegistry first; + RenderTargetRegistry second; + RenderTargetEntry firstTarget; + firstTarget.width = 640; + firstTarget.height = 480; + RenderTargetEntry secondTarget; + secondTarget.width = 320; + secondTarget.height = 200; + + const uint32_t firstId = first.registerTarget(firstTarget); + const uint32_t secondId = second.registerTarget(secondTarget); + CHECK_EQUAL(firstId, secondId); + + RenderTargetEntry resolved; + REQUIRE CHECK(first.resolve(firstId, resolved)); + CHECK_EQUAL(640, resolved.width); + REQUIRE CHECK(second.resolve(secondId, resolved)); + CHECK_EQUAL(320, resolved.width); + + firstTarget.width = 800; + first.updateTarget(firstId, firstTarget); + REQUIRE CHECK(first.resolve(firstId, resolved)); + CHECK_EQUAL(800, resolved.width); + REQUIRE CHECK(second.resolve(secondId, resolved)); + CHECK_EQUAL(320, resolved.width); + + first.unregisterTarget(firstId); + CHECK(!first.resolve(firstId, resolved)); + CHECK(second.resolve(secondId, resolved)); + } +} diff --git a/GraphicApplication/tests/WindowFrameTests.cpp b/GraphicApplication/tests/WindowFrameTests.cpp new file mode 100644 index 00000000..e2970322 --- /dev/null +++ b/GraphicApplication/tests/WindowFrameTests.cpp @@ -0,0 +1,106 @@ +#include "UnitTest++/UnitTest++.h" + +#include "WindowFrame.hpp" + +using namespace tp; + +namespace { + WindowFrame floatingFrame() { + WindowFrame frame; + frame.setContentSize({ 800, 600 }); + return frame; + } +} + +SUITE(WindowFrameGeometry) { + TEST(FloatingTranslucentFrameReservesShadowMargin) { + const WindowFrame frame = floatingFrame(); + const halnf margin = WindowFrame::kShadowMargin; + + CHECK(frame.hasShadow()); + CHECK_CLOSE(800 + margin * 2, frame.surfaceSize().x, 0.001f); + CHECK_CLOSE(600 + margin * 2, frame.surfaceSize().y, 0.001f); + CHECK_CLOSE(margin, frame.contentRect().x, 0.001f); + CHECK_CLOSE(WindowFrame::kCornerRadius, frame.cornerRadius(), 0.001f); + } + + TEST(TiledFrameDropsShadowAndCorners) { + WindowFrame frame = floatingFrame(); + frame.setTiled(true); + + CHECK(!frame.hasShadow()); + CHECK_CLOSE(0, frame.margin(), 0.001f); + CHECK_CLOSE(0, frame.cornerRadius(), 0.001f); + CHECK_CLOSE(800, frame.surfaceSize().x, 0.001f); + } + + TEST(OpaqueSurfaceDropsShadowButKeepsResizeEdges) { + WindowFrame frame = floatingFrame(); + frame.setTranslucent(false); + + CHECK(!frame.hasShadow()); + CHECK(frame.edgeAt({ 1, 300 }) == WindowEdge::Left); + CHECK(frame.edgeAt({ 400, 300 }) == WindowEdge::None); + } + + TEST(InputRegionCoversContentPlusOuterResizeBorder) { + const WindowFrame frame = floatingFrame(); + const RectF input = frame.inputRect(); + + CHECK_CLOSE(WindowFrame::kShadowMargin - WindowFrame::kResizeBorder, input.x, 0.001f); + CHECK_CLOSE(800 + WindowFrame::kResizeBorder * 2, input.z, 0.001f); + } +} + +SUITE(WindowFrameEdges) { + TEST(ShadowOutsideResizeBorderIsNotInteractive) { + const WindowFrame frame = floatingFrame(); + CHECK(frame.edgeAt({ 1, 1 }) == WindowEdge::None); + } + + TEST(SidesResolveToSingleEdges) { + const WindowFrame frame = floatingFrame(); + const RectF content = frame.contentRect(); + const Vec2F middle = { content.x + content.z / 2, content.y + content.w / 2 }; + + CHECK(frame.edgeAt({ content.x - 2, middle.y }) == WindowEdge::Left); + CHECK(frame.edgeAt({ content.x + content.z + 2, middle.y }) == WindowEdge::Right); + CHECK(frame.edgeAt({ middle.x, content.y - 2 }) == WindowEdge::Top); + CHECK(frame.edgeAt({ middle.x, content.y + content.w + 2 }) == WindowEdge::Bottom); + } + + TEST(InnerResizeStripStaysThin) { + const WindowFrame frame = floatingFrame(); + const RectF content = frame.contentRect(); + + CHECK(frame.edgeAt({ content.x + 1, content.y + 200 }) == WindowEdge::Left); + CHECK(frame.edgeAt({ content.x + 6, content.y + 200 }) == WindowEdge::None); + } + + TEST(CornersWinOverSides) { + const WindowFrame frame = floatingFrame(); + const RectF content = frame.contentRect(); + + CHECK(frame.edgeAt({ content.x - 2, content.y + 4 }) == WindowEdge::TopLeft); + CHECK(frame.edgeAt({ content.x + 4, content.y - 2 }) == WindowEdge::TopLeft); + CHECK(frame.edgeAt({ content.x + content.z + 2, content.y + content.w - 4 }) == WindowEdge::BottomRight); + CHECK(frame.edgeAt({ content.x + content.z - 4, content.y - 2 }) == WindowEdge::TopRight); + CHECK(frame.edgeAt({ content.x - 2, content.y + content.w - 4 }) == WindowEdge::BottomLeft); + } + + TEST(TiledFrameHasNoResizeEdges) { + WindowFrame frame = floatingFrame(); + frame.setTiled(true); + + CHECK(frame.edgeAt({ 0, 0 }) == WindowEdge::None); + CHECK(frame.edgeAt({ 1, 300 }) == WindowEdge::None); + } + + TEST(EdgeCursorsMatchAxes) { + CHECK(cursorForEdge(WindowEdge::Left) == CursorShape::ResizeHorizontal); + CHECK(cursorForEdge(WindowEdge::Bottom) == CursorShape::ResizeVertical); + CHECK(cursorForEdge(WindowEdge::TopLeft) == CursorShape::ResizeNorthWestSouthEast); + CHECK(cursorForEdge(WindowEdge::BottomLeft) == CursorShape::ResizeNorthEastSouthWest); + CHECK(cursorForEdge(WindowEdge::None) == CursorShape::Default); + } +} -- 2.49.1 From 92de3007e97ae8cb9cba6fa3860ca207e9bbb98d Mon Sep 17 00:00:00 2001 From: agent Date: Thu, 29 Oct 2026 05:24:06 +0300 Subject: [PATCH 4/5] GraphicApplication: NanoVG canvas, window decorations and ImGui debug UI canvas/ is a generic drawing interface on one NanoVG Vulkan context per window surface. NanoVG reads the recording command buffer from the GpuFrame handed to drawBegin(). It offers only generic primitives: shapes, gradients, paths with holes, composite operations, typed text styles (TextStyle, TextAlign, FontFace) and typed ImageHandles for files, pixels and registered render targets. Origin and clipping are scoped: SavedState, Translation and Placement restore them, and every primitive honours the origin. Text uses bundled faces (regular, bold, mono, math) with symbol, special-character and emoji fallbacks, and a Cyrillic cut of the matching weight last in each face's chain; tests check every bundled face loads and covers the characters found in UI sources and the Russian alphabet. rsc/FONTS.md records where each font comes from. decorations/ owns client-side decorations as one concept: WindowDecorations paints the base, rounded corners, outline and shadow of the same WindowFrame the backend uses for edge hit-testing. debug_ui/ owns the process ImGui and ImPlot contexts. DebugGUI renders through the ImGui Vulkan backend with its own platform layer fed from InputState, or runs headless so code that calls ImGui directly still works when the interface is hidden. --- GraphicApplication/rsc/Emoji.ttf | Bin 0 -> 418804 bytes GraphicApplication/rsc/FONTS.md | 56 +++ GraphicApplication/rsc/Font.ttf | Bin 0 -> 42624 bytes GraphicApplication/rsc/FontBold.ttf | Bin 0 -> 44936 bytes GraphicApplication/rsc/FontCyrillic.ttf | Bin 0 -> 18424 bytes GraphicApplication/rsc/FontCyrillicBold.ttf | Bin 0 -> 18404 bytes GraphicApplication/rsc/FontMath.ttf | Bin 0 -> 198952 bytes GraphicApplication/rsc/FontMono.ttf | Bin 0 -> 273900 bytes GraphicApplication/rsc/FontSpecials.ttf | Bin 0 -> 5548 bytes GraphicApplication/rsc/FontSymbols.ttf | Bin 0 -> 93064 bytes .../rsc/licenses/Cantarell-OFL.txt | 93 ++++ .../rsc/licenses/JetBrainsMono-OFL.txt | 93 ++++ GraphicApplication/rsc/licenses/Noto-OFL.txt | 93 ++++ .../src/canvas/private/Canvas.cpp | 433 ++++++++++++++++++ .../src/canvas/private/CanvasContext.hpp | 24 + .../src/canvas/private/CanvasFontLoader.cpp | 89 ++++ .../src/canvas/private/CanvasFontLoader.hpp | 20 + .../src/canvas/private/CanvasFonts.hpp | 47 ++ .../src/canvas/private/CanvasImages.cpp | 70 +++ .../src/canvas/public/Canvas.hpp | 183 ++++++++ .../src/debug_ui/private/DebugGUI.cpp | 126 +++++ .../src/debug_ui/private/ImGuiPlatform.cpp | 280 +++++++++++ .../src/debug_ui/private/ImGuiPlatform.hpp | 43 ++ .../src/debug_ui/public/DebugGUI.hpp | 35 ++ .../decorations/private/WindowDecorations.cpp | 55 +++ .../decorations/public/WindowDecorations.hpp | 36 ++ .../tests/FontCoverageTests.cpp | 196 ++++++++ 27 files changed, 1972 insertions(+) create mode 100644 GraphicApplication/rsc/Emoji.ttf create mode 100644 GraphicApplication/rsc/FONTS.md create mode 100644 GraphicApplication/rsc/Font.ttf create mode 100644 GraphicApplication/rsc/FontBold.ttf create mode 100644 GraphicApplication/rsc/FontCyrillic.ttf create mode 100644 GraphicApplication/rsc/FontCyrillicBold.ttf create mode 100644 GraphicApplication/rsc/FontMath.ttf create mode 100644 GraphicApplication/rsc/FontMono.ttf create mode 100644 GraphicApplication/rsc/FontSpecials.ttf create mode 100644 GraphicApplication/rsc/FontSymbols.ttf create mode 100644 GraphicApplication/rsc/licenses/Cantarell-OFL.txt create mode 100644 GraphicApplication/rsc/licenses/JetBrainsMono-OFL.txt create mode 100644 GraphicApplication/rsc/licenses/Noto-OFL.txt create mode 100644 GraphicApplication/src/canvas/private/Canvas.cpp create mode 100644 GraphicApplication/src/canvas/private/CanvasContext.hpp create mode 100644 GraphicApplication/src/canvas/private/CanvasFontLoader.cpp create mode 100644 GraphicApplication/src/canvas/private/CanvasFontLoader.hpp create mode 100644 GraphicApplication/src/canvas/private/CanvasFonts.hpp create mode 100644 GraphicApplication/src/canvas/private/CanvasImages.cpp create mode 100644 GraphicApplication/src/canvas/public/Canvas.hpp create mode 100644 GraphicApplication/src/debug_ui/private/DebugGUI.cpp create mode 100644 GraphicApplication/src/debug_ui/private/ImGuiPlatform.cpp create mode 100644 GraphicApplication/src/debug_ui/private/ImGuiPlatform.hpp create mode 100644 GraphicApplication/src/debug_ui/public/DebugGUI.hpp create mode 100644 GraphicApplication/src/decorations/private/WindowDecorations.cpp create mode 100644 GraphicApplication/src/decorations/public/WindowDecorations.hpp create mode 100644 GraphicApplication/tests/FontCoverageTests.cpp diff --git a/GraphicApplication/rsc/Emoji.ttf b/GraphicApplication/rsc/Emoji.ttf new file mode 100644 index 0000000000000000000000000000000000000000..19b7badf4afe64dae28cd64fc506b95291bb8b03 GIT binary patch literal 418804 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�p8T)6I% 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
3=AUYLi1zm#9`5f{OeSj(h#op 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 z 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%0AQG2*(c4piwpZ>}`(B$9Nbq8?(LeGt;(KF}V)dMT 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 literal 0 HcmV?d00001 diff --git a/GraphicApplication/rsc/FontCyrillic.ttf b/GraphicApplication/rsc/FontCyrillic.ttf new file mode 100644 index 0000000000000000000000000000000000000000..312e7a9c69044abfd8c9b0580ced14b2000847ef GIT binary patch literal 18424 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 literal 0 HcmV?d00001 diff --git a/GraphicApplication/rsc/FontMono.ttf b/GraphicApplication/rsc/FontMono.ttf new file mode 100644 index 0000000000000000000000000000000000000000..dff66cc50702c75abd025dcf49f62a4dcc2d72de GIT binary patch literal 273900 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

    FHUzr>AGnB$>%J$xQa0Y-A-N1V~sy3?Yvl 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 literal 0 HcmV?d00001 diff --git a/GraphicApplication/rsc/licenses/Cantarell-OFL.txt b/GraphicApplication/rsc/licenses/Cantarell-OFL.txt new file mode 100644 index 00000000..ada24255 --- /dev/null +++ b/GraphicApplication/rsc/licenses/Cantarell-OFL.txt @@ -0,0 +1,93 @@ +Copyright 2009 The Cantarell Project Authors (https://github.com/davelab6/cantarell) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/GraphicApplication/rsc/licenses/JetBrainsMono-OFL.txt b/GraphicApplication/rsc/licenses/JetBrainsMono-OFL.txt new file mode 100644 index 00000000..8bee4148 --- /dev/null +++ b/GraphicApplication/rsc/licenses/JetBrainsMono-OFL.txt @@ -0,0 +1,93 @@ +Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/GraphicApplication/rsc/licenses/Noto-OFL.txt b/GraphicApplication/rsc/licenses/Noto-OFL.txt new file mode 100644 index 00000000..2b3b408a --- /dev/null +++ b/GraphicApplication/rsc/licenses/Noto-OFL.txt @@ -0,0 +1,93 @@ +Copyright 2022 The Noto Project Authors (https://github.com/notofonts/symbols, https://github.com/notofonts/math, https://github.com/notofonts/latin-greek-cyrillic) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/GraphicApplication/src/canvas/private/Canvas.cpp b/GraphicApplication/src/canvas/private/Canvas.cpp new file mode 100644 index 00000000..88008b93 --- /dev/null +++ b/GraphicApplication/src/canvas/private/Canvas.cpp @@ -0,0 +1,433 @@ +#include "CanvasContext.hpp" + +#include "GpuError.hpp" +#include "Profiler.hpp" + +#include + +#include + +using namespace tp; + +namespace { + constexpr halnf kItalicSkew = -0.21f; + + bool isItalic(FontFace face) { return face == FontFace::Italic || face == FontFace::BoldItalic; } + + void addRect(NVGcontext* vg, const RectF& rect, halnf round) { + if (round == 0) { + nvgRect(vg, rect.x, rect.y, rect.z, rect.w); + } else { + nvgRoundedRect(vg, rect.x, rect.y, rect.z, rect.w, round); + } + } + + void stroke(NVGcontext* vg, const RGBA& color, halnf width) { + nvgStrokeWidth(vg, width); + nvgStrokeColor(vg, toNVGColor(color)); + nvgStroke(vg); + } + + void fill(NVGcontext* vg, const RGBA& color) { + nvgFillColor(vg, toNVGColor(color)); + nvgFill(vg); + } + + NVGpaint linearPaint(NVGcontext* vg, const Canvas::LinearGradient& gradient, Vec2F origin) { + const Vec2F from = gradient.from + origin; + const Vec2F to = gradient.to + origin; + return nvgLinearGradient( + vg, from.x, from.y, to.x, to.y, toNVGColor(gradient.fromColor), toNVGColor(gradient.toColor) + ); + } + + int nvgCompositeOperationOf(Canvas::CompositeOperation operation) { + switch (operation) { + case Canvas::CompositeOperation::SourceOver: return NVG_SOURCE_OVER; + case Canvas::CompositeOperation::DestinationOut: return NVG_DESTINATION_OUT; + case Canvas::CompositeOperation::DestinationOver: return NVG_DESTINATION_OVER; + } + return NVG_SOURCE_OVER; + } + + int nvgWindingOf(Canvas::PathWinding winding) { return winding == Canvas::PathWinding::Hole ? NVG_HOLE : NVG_SOLID; } +} + +Canvas::Canvas(WindowSurface& surface, Profiler& profiler, const std::filesystem::path& resourceDirectory) : + mContext(std::make_unique()) { + mContext->surface = &surface; + mContext->profiler = &profiler; + mContext->flushSection = profiler.section("frame.ui_flush"); + + const GpuDevice& gpu = surface.gpu(); + VKNVGCreateInfo createInfo{}; + createInfo.gpu = gpu.physicalDevice(); + createInfo.device = gpu.device(); + createInfo.renderpass = surface.renderPass(); + createInfo.cmdBuffer = mContext->frameCommandBuffers; + createInfo.frameCount = WindowSurface::kFramesInFlight; + createInfo.currentFrame = &mContext->frameSlot; + createInfo.commandPool = gpu.commandPool(); + createInfo.graphicsQueueFamilyIndex = gpu.graphicsQueueFamily(); + + mContext->vg = nvgCreateVk(&createInfo, NVG_ANTIALIAS | NVG_STENCIL_STROKES, gpu.graphicsQueue()); + if (!mContext->vg) { + throw GpuError("creating the NanoVG context", VK_ERROR_INITIALIZATION_FAILED); + } + mContext->fonts = loadCanvasFonts(mContext->vg, resourceDirectory); +} + +Canvas::~Canvas() { + mContext->surface->gpu().waitIdle(); + nvgDeleteVk(mContext->vg); +} + +void Canvas::drawBegin(const GpuFrame& frame, const WindowFrame& windowFrame) { + mContext->frameCommandBuffers[frame.slot] = frame.commandBuffer; + mContext->frameSlot = frame.slot; + + const Vec2F surfaceSize = windowFrame.surfaceSize(); + const halnf devicePixelRatio = surfaceSize.x > 0 ? (halnf) frame.extent.width / surfaceSize.x : 1.0f; + nvgBeginFrame(mContext->vg, surfaceSize.x, surfaceSize.y, devicePixelRatio); + nvgTranslate(mContext->vg, windowFrame.margin(), windowFrame.margin()); + + mOrigin = { 0, 0 }; + mClips.clear(); + mSavedFrames.clear(); +} + +void Canvas::drawEnd() { + const ProfileScope scope(*mContext->profiler, mContext->flushSection); + nvgEndFrame(mContext->vg); +} + +void Canvas::save() { + nvgSave(mContext->vg); + mSavedFrames.append({ mOrigin, mClips.size() }); +} + +void Canvas::restore() { + DEBUG_ASSERT(mSavedFrames.size()); + const SavedFrame saved = mSavedFrames.last(); + mSavedFrames.pop(); + + while (mClips.size() > saved.clipDepth) { + mClips.pop(); + } + mOrigin = saved.origin; + nvgRestore(mContext->vg); + applyClip(); +} + +void Canvas::setCompositeOperation(CompositeOperation operation) { + nvgGlobalCompositeOperation(mContext->vg, nvgCompositeOperationOf(operation)); +} + +void Canvas::beginPath() { nvgBeginPath(mContext->vg); } + +void Canvas::addPathRect(RectF rect, halnf round) { + rect.pos += mOrigin; + addRect(mContext->vg, rect, round); +} + +void Canvas::setPathWinding(PathWinding winding) { nvgPathWinding(mContext->vg, nvgWindingOf(winding)); } + +void Canvas::fillPath(const RGBA& color) { fill(mContext->vg, color); } + +void Canvas::fillPath(const BoxGradient& gradient) { + const RectF box = { gradient.box.pos + mOrigin, gradient.box.size }; + const NVGpaint paint = nvgBoxGradient( + mContext->vg, + box.x, + box.y, + box.z, + box.w, + gradient.radius, + gradient.feather, + toNVGColor(gradient.innerColor), + toNVGColor(gradient.outerColor) + ); + nvgFillPaint(mContext->vg, paint); + nvgFill(mContext->vg); +} + +void Canvas::rect(RectF rect, const RGBA& color, halnf round) { + rect.pos += mOrigin; + nvgBeginPath(mContext->vg); + addRect(mContext->vg, rect, round); + fill(mContext->vg, color); +} + +void Canvas::gradientRect(RectF rect, const RGBA& from, const RGBA& to, bool horizontal, halnf round) { + rect.pos += mOrigin; + NVGcontext* vg = mContext->vg; + const halnf endX = horizontal ? rect.x + rect.z : rect.x; + const halnf endY = horizontal ? rect.y : rect.y + rect.w; + const NVGpaint paint = nvgLinearGradient(vg, rect.x, rect.y, endX, endY, toNVGColor(from), toNVGColor(to)); + nvgBeginPath(vg); + addRect(vg, rect, round); + nvgFillPaint(vg, paint); + nvgFill(vg); +} + +void Canvas::debugCross(RectF rect, const RGBA& color) { + rect.pos += mOrigin; + NVGcontext* vg = mContext->vg; + nvgBeginPath(vg); + nvgMoveTo(vg, rect.p1().x, rect.p1().y); + nvgLineTo(vg, rect.p3().x, rect.p3().y); + nvgMoveTo(vg, rect.p2().x, rect.p2().y); + nvgLineTo(vg, rect.p4().x, rect.p4().y); + stroke(vg, color, 2); +} + +void Canvas::frame(RectF rect, const RGBA& color) { + rect.pos += mOrigin; + NVGcontext* vg = mContext->vg; + nvgBeginPath(vg); + nvgRect(vg, rect.x, rect.y, rect.z, rect.w); + stroke(vg, color, 2); +} + +void Canvas::outline(RectF rect, const RGBA& color, halnf round, halnf width) { + rect.pos += mOrigin; + const halnf inset = width / 2; + nvgBeginPath(mContext->vg); + nvgRoundedRect( + mContext->vg, rect.x + inset, rect.y + inset, rect.z - width, rect.w - width, round > inset ? round - inset : 0 + ); + stroke(mContext->vg, color, width); +} + +void Canvas::circleOutline(Vec2F center, halnf radius, const RGBA& color, halnf width) { + center += mOrigin; + nvgBeginPath(mContext->vg); + nvgCircle(mContext->vg, center.x, center.y, radius - width / 2); + stroke(mContext->vg, color, width); +} + +void Canvas::polyline(const Vec2F* points, int count, const RGBA& color, halnf width) { + if (count < 2) { + return; + } + + nvgBeginPath(mContext->vg); + nvgMoveTo(mContext->vg, points[0].x + mOrigin.x, points[0].y + mOrigin.y); + for (int index = 1; index < count; index++) { + nvgLineTo(mContext->vg, points[index].x + mOrigin.x, points[index].y + mOrigin.y); + } + nvgLineCap(mContext->vg, NVG_ROUND); + nvgLineJoin(mContext->vg, NVG_ROUND); + stroke(mContext->vg, color, width); +} + +void Canvas::polygon(const Vec2F* points, int count, const RGBA& color) { + if (count < 3) { + return; + } + + nvgBeginPath(mContext->vg); + nvgMoveTo(mContext->vg, points[0].x + mOrigin.x, points[0].y + mOrigin.y); + for (int index = 1; index < count; index++) { + nvgLineTo(mContext->vg, points[index].x + mOrigin.x, points[index].y + mOrigin.y); + } + nvgClosePath(mContext->vg); + fill(mContext->vg, color); +} + +void Canvas::polygon(const Vec2F* points, int count, const LinearGradient& gradient) { + if (count < 3) { + return; + } + + nvgBeginPath(mContext->vg); + nvgMoveTo(mContext->vg, points[0].x + mOrigin.x, points[0].y + mOrigin.y); + for (int index = 1; index < count; index++) { + nvgLineTo(mContext->vg, points[index].x + mOrigin.x, points[index].y + mOrigin.y); + } + nvgClosePath(mContext->vg); + nvgFillPaint(mContext->vg, linearPaint(mContext->vg, gradient, mOrigin)); + nvgFill(mContext->vg); +} + +void Canvas::ringSegment( + Vec2F center, halnf innerRadius, halnf outerRadius, halnf fromAngle, halnf toAngle, const LinearGradient& gradient +) { + center += mOrigin; + + nvgBeginPath(mContext->vg); + nvgArc(mContext->vg, center.x, center.y, innerRadius, fromAngle, toAngle, NVG_CW); + nvgArc(mContext->vg, center.x, center.y, outerRadius, toAngle, fromAngle, NVG_CCW); + nvgClosePath(mContext->vg); + nvgFillPaint(mContext->vg, linearPaint(mContext->vg, gradient, mOrigin)); + nvgFill(mContext->vg); +} + +void Canvas::radialGlow( + Vec2F center, halnf innerRadius, halnf outerRadius, const RGBA& innerColor, const RGBA& outerColor +) { + center += mOrigin; + + const NVGpaint paint = nvgRadialGradient( + mContext->vg, center.x, center.y, innerRadius, outerRadius, toNVGColor(innerColor), toNVGColor(outerColor) + ); + + nvgBeginPath(mContext->vg); + nvgCircle(mContext->vg, center.x, center.y, outerRadius); + nvgFillPaint(mContext->vg, paint); + nvgFill(mContext->vg); +} + +void Canvas::line(Vec2F start, Vec2F end, const RGBA& color, halnf thickness) { + start += mOrigin; + end += mOrigin; + nvgBeginPath(mContext->vg); + nvgMoveTo(mContext->vg, start.x, start.y); + nvgLineTo(mContext->vg, end.x, end.y); + stroke(mContext->vg, color, thickness); +} + +void Canvas::circle(Vec2F center, halnf radius, const RGBA& color) { + center += mOrigin; + nvgBeginPath(mContext->vg); + nvgCircle(mContext->vg, center.x, center.y, radius); + fill(mContext->vg, color); +} + +void Canvas::clipTo(const RectF& rect) { + const RectF placed = { rect.pos + mOrigin, rect.size }; + RectF clip = placed; + if (mClips.size()) { + mClips.last().calcIntersection(placed, clip); + } + mClips.append(clip); + applyClip(); +} + +void Canvas::applyClip() { + if (!mClips.size()) { + nvgResetScissor(mContext->vg); + return; + } + const RectF& clip = mClips.last(); + nvgScissor(mContext->vg, clip.x, clip.y, clip.z, clip.w); +} + +bool Canvas::isClippedAway() const { return mClips.size() && mClips.last().size.length2() <= EPSILON; } + +bool Canvas::isOutsideClip(const RectF& rect) const { + if (!mClips.size()) { + return false; + } + const RectF placed = { rect.pos + mOrigin, rect.size }; + const RectF& clip = mClips.last(); + return placed.x >= clip.x + clip.z || clip.x >= placed.x + placed.z || placed.y >= clip.y + clip.w || + clip.y >= placed.y + placed.w; +} + +void Canvas::text(const char* string, const RectF& area, const TextStyle& style) { + const halnf padding = style.padding; + const RectF padded = { area.x + padding, area.y + padding, area.z - padding * 2, area.w - padding * 2 }; + if (isOutsideClip(padded)) { + return; + } + + const SavedState saved(*this); + clipTo(padded); + const RectF rect = { padded.pos + mOrigin, padded.size }; + + NVGcontext* vg = mContext->vg; + nvgFontSize(vg, style.size); + nvgFontFaceId(vg, mContext->fonts.idFor(style.face)); + nvgFillColor(vg, toNVGColor(style.color)); + + halnf anchorX = rect.x; + halnf anchorY = rect.y; + int alignment = 0; + switch (style.align.horizontal) { + case HorizontalAlign::Left: alignment |= NVG_ALIGN_LEFT; break; + case HorizontalAlign::Center: + alignment |= NVG_ALIGN_CENTER; + anchorX += rect.z * 0.5f; + break; + case HorizontalAlign::Right: + alignment |= NVG_ALIGN_RIGHT; + anchorX += rect.z; + break; + } + switch (style.align.vertical) { + case VerticalAlign::Top: alignment |= NVG_ALIGN_TOP; break; + case VerticalAlign::Center: + alignment |= NVG_ALIGN_MIDDLE; + anchorY += rect.w * 0.5f; + break; + case VerticalAlign::Bottom: + alignment |= NVG_ALIGN_BOTTOM; + anchorY += rect.w; + break; + } + nvgTextAlign(vg, alignment); + + if (!isItalic(style.face)) { + nvgText(vg, anchorX, anchorY, string, nullptr); + return; + } + nvgTranslate(vg, anchorX, anchorY); + nvgSkewX(vg, kItalicSkew); + nvgText(vg, 0, 0, string, nullptr); +} + +void Canvas::rotatedText(const char* string, Vec2F center, halnf size, halnf angle, const RGBA& color) { + center += mOrigin; + + nvgSave(mContext->vg); + nvgTranslate(mContext->vg, center.x, center.y); + nvgRotate(mContext->vg, angle); + nvgFontSize(mContext->vg, size); + nvgFontFaceId(mContext->vg, mContext->fonts.regular); + nvgTextAlign(mContext->vg, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE); + nvgFillColor(mContext->vg, toNVGColor(color)); + nvgText(mContext->vg, 0, 0, string, nullptr); + nvgRestore(mContext->vg); +} + +halnf Canvas::textWidth(const char* string, halnf size, FontFace face) { + nvgFontSize(mContext->vg, size); + nvgFontFaceId(mContext->vg, mContext->fonts.idFor(face)); + return nvgTextBounds(mContext->vg, 0, 0, string, nullptr, nullptr); +} + +void Canvas::prefixWidths(const char* string, size_t length, halnf size, FontFace face, halnf* widths) { + nvgFontSize(mContext->vg, size); + nvgFontFaceId(mContext->vg, mContext->fonts.idFor(face)); + nvgTextAlign(mContext->vg, NVG_ALIGN_LEFT | NVG_ALIGN_TOP); + + std::vector glyphs(length); + const int count = nvgTextGlyphPositions(mContext->vg, 0, 0, string, string + length, glyphs.data(), (int) length); + + size_t byte = 0; + for (int index = 0; index < count; index++) { + const size_t glyphStart = (size_t) (glyphs[(size_t) index].str - string); + const halnf pen = glyphs[(size_t) index].x; + for (; byte <= glyphStart && byte < length; byte++) { + widths[byte] = pen; + } + } + const halnf total = nvgTextBounds(mContext->vg, 0, 0, string, string + length, nullptr); + for (; byte < length; byte++) { + widths[byte] = total; + } + widths[length] = total; +} + +void Canvas::drawImage(RectF rect, ImageHandle image, halnf angle, halnf alpha, halnf rounding) { + if (!image.isValid()) { + return; + } + rect.pos += mOrigin; + const NVGpaint paint = nvgImagePattern(mContext->vg, rect.x, rect.y, rect.z, rect.w, angle, image.mId, alpha); + nvgBeginPath(mContext->vg); + nvgRoundedRect(mContext->vg, rect.x, rect.y, rect.z, rect.w, rounding); + nvgFillPaint(mContext->vg, paint); + nvgFill(mContext->vg); +} diff --git a/GraphicApplication/src/canvas/private/CanvasContext.hpp b/GraphicApplication/src/canvas/private/CanvasContext.hpp new file mode 100644 index 00000000..5dc527b9 --- /dev/null +++ b/GraphicApplication/src/canvas/private/CanvasContext.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include "Canvas.hpp" +#include "CanvasFontLoader.hpp" + +#include + +namespace tp { + + class Canvas::Context { + public: + NVGcontext* vg{}; + WindowSurface* surface{}; + Profiler* profiler{}; + uint32_t flushSection = 0; + CanvasFontIds fonts; + + // NanoVG reads the recording command buffer and frame slot through these pointers on every flush. + VkCommandBuffer frameCommandBuffers[WindowSurface::kFramesInFlight] = {}; + uint32_t frameSlot = 0; + }; + + inline NVGcolor toNVGColor(const RGBA& color) { return { color.r, color.g, color.b, color.a }; } +} diff --git a/GraphicApplication/src/canvas/private/CanvasFontLoader.cpp b/GraphicApplication/src/canvas/private/CanvasFontLoader.cpp new file mode 100644 index 00000000..482489a9 --- /dev/null +++ b/GraphicApplication/src/canvas/private/CanvasFontLoader.cpp @@ -0,0 +1,89 @@ +#include "CanvasFontLoader.hpp" +#include "CanvasFonts.hpp" + +#include + +#include +#include +#include +#include +#include + +using namespace tp; + +namespace { + bool isBold(FontFace face) { return face == FontFace::Bold || face == FontFace::BoldItalic; } + + class LoadedFallbackFonts { + public: + LoadedFallbackFonts(NVGcontext* vg, const std::filesystem::path& resourceDirectory) { + for (const FontFile& font : kFallbackFonts) { + const std::string path = (resourceDirectory / font.path).string(); + mLoaded.emplace_back(font.name, nvgCreateFont(vg, font.name, path.c_str())); + } + } + + template + [[nodiscard]] std::vector idsInChainOrder(const std::array& chain) const { + std::vector ids; + for (const FontFile& font : chain) { + const int id = idOf(font.name); + if (id != -1) { + ids.push_back(id); + } + } + return ids; + } + + private: + [[nodiscard]] int idOf(const char* name) const { + for (const auto& [loaded, id] : mLoaded) { + if (std::strcmp(loaded, name) == 0) { + return id; + } + } + return -1; + } + + std::vector> mLoaded; + }; + + int loadTextFont( + NVGcontext* vg, + const FontFile& font, + const std::vector& fallbacks, + int missing, + const std::filesystem::path& resourceDirectory + ) { + const std::string path = (resourceDirectory / font.path).string(); + const int id = nvgCreateFont(vg, font.name, path.c_str()); + if (id == -1) { + return missing; + } + for (const int fallback : fallbacks) { + nvgAddFallbackFontId(vg, id, fallback); + } + return id; + } +} + +int CanvasFontIds::idFor(FontFace face) const { + if (face == FontFace::Monospace) { + return monospace; + } + return isBold(face) ? bold : regular; +} + +CanvasFontIds tp::loadCanvasFonts(NVGcontext* vg, const std::filesystem::path& resourceDirectory) { + const LoadedFallbackFonts fallbacks(vg, resourceDirectory); + + CanvasFontIds fonts; + fonts.regular = loadTextFont(vg, kRegularFont, fallbacks.idsInChainOrder(kRegularFallbacks), -1, resourceDirectory); + if (fonts.regular == -1) { + throw std::runtime_error("cannot load " + (resourceDirectory / kRegularFont.path).string()); + } + fonts.bold = loadTextFont(vg, kBoldFont, fallbacks.idsInChainOrder(kBoldFallbacks), fonts.regular, resourceDirectory); + fonts.monospace = + loadTextFont(vg, kMonospaceFont, fallbacks.idsInChainOrder(kMonospaceFallbacks), fonts.regular, resourceDirectory); + return fonts; +} diff --git a/GraphicApplication/src/canvas/private/CanvasFontLoader.hpp b/GraphicApplication/src/canvas/private/CanvasFontLoader.hpp new file mode 100644 index 00000000..30b82a79 --- /dev/null +++ b/GraphicApplication/src/canvas/private/CanvasFontLoader.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include "Canvas.hpp" + +#include + +struct NVGcontext; + +namespace tp { + + struct CanvasFontIds { + int regular = -1; + int bold = -1; + int monospace = -1; + + [[nodiscard]] int idFor(FontFace face) const; + }; + + CanvasFontIds loadCanvasFonts(NVGcontext* vg, const std::filesystem::path& resourceDirectory); +} diff --git a/GraphicApplication/src/canvas/private/CanvasFonts.hpp b/GraphicApplication/src/canvas/private/CanvasFonts.hpp new file mode 100644 index 00000000..2cb2f625 --- /dev/null +++ b/GraphicApplication/src/canvas/private/CanvasFonts.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include +#include + +namespace tp { + + struct FontFile { + const char* name; + const char* path; + }; + + inline constexpr FontFile kRegularFont = { "default", "Font.ttf" }; + inline constexpr FontFile kBoldFont = { "bold", "FontBold.ttf" }; + inline constexpr FontFile kMonospaceFont = { "monospace", "FontMono.ttf" }; + + inline constexpr std::array kTextFonts = { kRegularFont, kBoldFont, kMonospaceFont }; + + inline constexpr FontFile kEmojiFont = { "emoji", "Emoji.ttf" }; + inline constexpr FontFile kSymbolsFont = { "symbols", "FontSymbols.ttf" }; + inline constexpr FontFile kMathFont = { "math", "FontMath.ttf" }; + inline constexpr FontFile kSpecialsFont = { "specials", "FontSpecials.ttf" }; + + inline constexpr FontFile kCyrillicRegularFont = { "cyrillic", "FontCyrillic.ttf" }; + inline constexpr FontFile kCyrillicBoldFont = { "cyrillic-bold", "FontCyrillicBold.ttf" }; + + inline constexpr std::array kFallbackFonts = { + kEmojiFont, kSymbolsFont, kMathFont, kSpecialsFont, kCyrillicRegularFont, kCyrillicBoldFont, + }; + + inline constexpr std::array kSymbolFallbacksFirst = { kEmojiFont, kSymbolsFont, kMathFont, kSpecialsFont }; + + template + constexpr std::array + symbolsThenCyrillic(const std::array& symbols, const FontFile& cyrillicOfFaceWeight) { + std::array chain{}; + for (std::size_t index = 0; index < Size; index++) { + chain[index] = symbols[index]; + } + chain[Size] = cyrillicOfFaceWeight; + return chain; + } + + inline constexpr std::array kRegularFallbacks = symbolsThenCyrillic(kSymbolFallbacksFirst, kCyrillicRegularFont); + inline constexpr std::array kBoldFallbacks = symbolsThenCyrillic(kSymbolFallbacksFirst, kCyrillicBoldFont); + inline constexpr std::array kMonospaceFallbacks = symbolsThenCyrillic(kSymbolFallbacksFirst, kCyrillicRegularFont); +} diff --git a/GraphicApplication/src/canvas/private/CanvasImages.cpp b/GraphicApplication/src/canvas/private/CanvasImages.cpp new file mode 100644 index 00000000..f5e88bc6 --- /dev/null +++ b/GraphicApplication/src/canvas/private/CanvasImages.cpp @@ -0,0 +1,70 @@ +#include "CanvasContext.hpp" + +#include "RenderTargetRegistry.hpp" + +#include + +using namespace tp; + +ImageHandle Canvas::createImageFromRenderTarget(uint32_t renderTargetId) { + RenderTargetEntry entry; + if (!mContext->surface->gpu().renderTargets().resolve(renderTargetId, entry)) { + return {}; + } + const int image = nvgCreateImageFromHandleVk( + mContext->vg, + entry.image, + entry.view, + entry.sampler, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + entry.width, + entry.height, + 0 + ); + return ImageHandle(image); +} + +void Canvas::updateImageFromRenderTarget(ImageHandle image, uint32_t renderTargetId) { + RenderTargetEntry entry; + if (!image.isValid() || !mContext->surface->gpu().renderTargets().resolve(renderTargetId, entry)) { + return; + } + nvgUpdateImageFromHandleVk( + mContext->vg, + image.mId, + entry.image, + entry.view, + entry.sampler, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + entry.width, + entry.height + ); +} + +ImageHandle Canvas::createImageFromFile(const char* path) { return ImageHandle(nvgCreateImage(mContext->vg, path, 0)); } + +ImageHandle Canvas::createImageFromMemoryRGBA(int width, int height, const unsigned char* rgba) { + return ImageHandle(nvgCreateImageRGBA(mContext->vg, width, height, 0, rgba)); +} + +void Canvas::updateImageRGBA(ImageHandle image, const unsigned char* rgba) { + if (image.isValid()) { + nvgUpdateImage(mContext->vg, image.mId, rgba); + } +} + +Vec2F Canvas::imageSize(ImageHandle image) { + if (!image.isValid()) { + return { 0, 0 }; + } + int width = 0; + int height = 0; + nvgImageSize(mContext->vg, image.mId, &width, &height); + return { (halnf) width, (halnf) height }; +} + +void Canvas::deleteImage(ImageHandle image) { + if (image.isValid()) { + nvgDeleteImage(mContext->vg, image.mId); + } +} diff --git a/GraphicApplication/src/canvas/public/Canvas.hpp b/GraphicApplication/src/canvas/public/Canvas.hpp new file mode 100644 index 00000000..00110671 --- /dev/null +++ b/GraphicApplication/src/canvas/public/Canvas.hpp @@ -0,0 +1,183 @@ +#pragma once + +#include "Color.hpp" +#include "Rect.hpp" +#include "WindowFrame.hpp" +#include "WindowSurface.hpp" + +#include "Buffer.hpp" + +#include +#include + +namespace tp { + + class Profiler; + + enum class HorizontalAlign { Left, Center, Right }; + + enum class VerticalAlign { Top, Center, Bottom }; + + struct TextAlign { + HorizontalAlign horizontal = HorizontalAlign::Left; + VerticalAlign vertical = VerticalAlign::Center; + + static constexpr TextAlign leftCenter() { return { HorizontalAlign::Left, VerticalAlign::Center }; } + static constexpr TextAlign centered() { return { HorizontalAlign::Center, VerticalAlign::Center }; } + }; + + enum class FontFace { Regular, Bold, Italic, BoldItalic, Monospace }; + + struct TextStyle { + halnf size = 16; + RGBA color = RGBA(1, 1, 1, 1); + TextAlign align; + halnf padding = 0; + FontFace face = FontFace::Regular; + }; + + class ImageHandle { + public: + ImageHandle() = default; + + [[nodiscard]] bool isValid() const { return mId > 0; } + [[nodiscard]] bool operator==(const ImageHandle& other) const = default; + + private: + friend class Canvas; + explicit ImageHandle(int id) : + mId(id) {} + + int mId = 0; + }; + + // 2D vector drawing for one window surface. Coordinates are relative to the current placement origin, which + // starts at the content origin; placements and clips are scoped. + class Canvas { + class Context; + + public: + Canvas(WindowSurface& surface, Profiler& profiler, const std::filesystem::path& resourceDirectory); + ~Canvas(); + + Canvas(const Canvas&) = delete; + Canvas& operator=(const Canvas&) = delete; + + void drawBegin(const GpuFrame& frame, const WindowFrame& windowFrame); + void drawEnd(); + + enum class CompositeOperation { SourceOver, DestinationOut, DestinationOver }; + + enum class PathWinding { Solid, Hole }; + + struct LinearGradient { + Vec2F from; + Vec2F to; + RGBA fromColor; + RGBA toColor; + }; + + struct BoxGradient { + RectF box; + halnf radius = 0; + halnf feather = 0; + RGBA innerColor; + RGBA outerColor; + }; + + // Restores origin, clipping and composite operation when the scope ends. + class SavedState { + public: + explicit SavedState(Canvas& canvas) : + mCanvas(canvas) { + mCanvas.save(); + } + + ~SavedState() { mCanvas.restore(); } + + SavedState(const SavedState&) = delete; + SavedState& operator=(const SavedState&) = delete; + + protected: + Canvas& mCanvas; + }; + + // Moves the origin by an offset from the current origin until destroyed. + class Translation : public SavedState { + public: + Translation(Canvas& canvas, Vec2F offset) : + SavedState(canvas) { + mCanvas.translateOrigin(offset); + } + }; + + // Places the origin at a point relative to the current origin and clips to an area there, until destroyed. + class Placement : public SavedState { + public: + Placement(Canvas& canvas, Vec2F origin, Vec2F clipSize) : + SavedState(canvas) { + mCanvas.translateOrigin(origin); + mCanvas.clipTo({ { 0, 0 }, clipSize }); + } + }; + + [[nodiscard]] Vec2F origin() const { return mOrigin; } + [[nodiscard]] bool isClippedAway() const; + [[nodiscard]] bool isOutsideClip(const RectF& rect) const; + + void setCompositeOperation(CompositeOperation operation); + + void beginPath(); + void addPathRect(RectF rect, halnf round = 0); + void setPathWinding(PathWinding winding); + void fillPath(const RGBA& color); + void fillPath(const BoxGradient& gradient); + + void rect(RectF rect, const RGBA& color, halnf round = 0); + void gradientRect(RectF rect, const RGBA& from, const RGBA& to, bool horizontal, halnf round = 0); + void frame(RectF rect, const RGBA& color); + void debugCross(RectF rect, const RGBA& color); + void outline(RectF rect, const RGBA& color, halnf round, halnf width); + void circle(Vec2F center, halnf radius, const RGBA& color); + void circleOutline(Vec2F center, halnf radius, const RGBA& color, halnf width); + void line(Vec2F start, Vec2F end, const RGBA& color, halnf thickness); + void polyline(const Vec2F* points, int count, const RGBA& color, halnf width); + void polygon(const Vec2F* points, int count, const RGBA& color); + void polygon(const Vec2F* points, int count, const LinearGradient& gradient); + void ringSegment( + Vec2F center, halnf innerRadius, halnf outerRadius, halnf fromAngle, halnf toAngle, const LinearGradient& gradient + ); + void radialGlow(Vec2F center, halnf innerRadius, halnf outerRadius, const RGBA& innerColor, const RGBA& outerColor); + + void text(const char* text, const RectF& area, const TextStyle& style); + void rotatedText(const char* text, Vec2F center, halnf size, halnf angle, const RGBA& color); + [[nodiscard]] halnf textWidth(const char* text, halnf size, FontFace face = FontFace::Regular); + void prefixWidths(const char* text, size_t length, halnf size, FontFace face, halnf* widths); + + [[nodiscard]] ImageHandle createImageFromRenderTarget(uint32_t renderTargetId); + void updateImageFromRenderTarget(ImageHandle image, uint32_t renderTargetId); + [[nodiscard]] ImageHandle createImageFromFile(const char* path); + [[nodiscard]] ImageHandle createImageFromMemoryRGBA(int width, int height, const unsigned char* rgba); + void updateImageRGBA(ImageHandle image, const unsigned char* rgba); + [[nodiscard]] Vec2F imageSize(ImageHandle image); + void deleteImage(ImageHandle image); + void drawImage(RectF rect, ImageHandle image, halnf angle = 0, halnf alpha = 1, halnf rounding = 0); + + private: + struct SavedFrame { + Vec2F origin; + size_t clipDepth = 0; + }; + + void save(); + void restore(); + void translateOrigin(Vec2F offset) { mOrigin += offset; } + void clipTo(const RectF& rect); + void applyClip(); + + std::unique_ptr mContext; + Buffer mClips; + Buffer mSavedFrames; + Vec2F mOrigin = { 0, 0 }; + }; +} diff --git a/GraphicApplication/src/debug_ui/private/DebugGUI.cpp b/GraphicApplication/src/debug_ui/private/DebugGUI.cpp new file mode 100644 index 00000000..79531f28 --- /dev/null +++ b/GraphicApplication/src/debug_ui/private/DebugGUI.cpp @@ -0,0 +1,126 @@ +#include "DebugGUI.hpp" + +#include "GpuError.hpp" +#include "ImGuiPlatform.hpp" + +#include +#include + +#include "implot.h" + +#include + +namespace tp { + namespace { + constexpr float kFontSize = 18.f; + constexpr float kHeadlessFrameSeconds = 1.0f / 60.0f; + + VkDescriptorPool createDescriptorPool(VkDevice device) { + const std::array poolSizes = { + VkDescriptorPoolSize{ VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 64 }, + }; + const VkDescriptorPoolCreateInfo poolInfo{ + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, + .flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, + .maxSets = 64, + .poolSizeCount = (uint32_t) poolSizes.size(), + .pPoolSizes = poolSizes.data(), + }; + VkDescriptorPool pool = VK_NULL_HANDLE; + requireSuccess(vkCreateDescriptorPool(device, &poolInfo, nullptr, &pool), "vkCreateDescriptorPool"); + return pool; + } + } + + class DebugGUI::Context { + public: + Context(Window& window, DebugInterface mode) : + window(window), + mode(mode) {} + + Window& window; + DebugInterface mode; + ImGuiContext* imgui = nullptr; + ImPlotContext* implot = nullptr; + VkDescriptorPool descriptorPool = VK_NULL_HANDLE; + std::unique_ptr platform; + + [[nodiscard]] bool isVisible() const { return mode == DebugInterface::Visible; } + }; +} + +using namespace tp; + +DebugGUI::DebugGUI(Window& window, const std::filesystem::path& resourceDirectory, DebugInterface mode) : + mContext(std::make_unique(window, mode)) { + IMGUI_CHECKVERSION(); + mContext->imgui = ImGui::CreateContext(); + mContext->implot = ImPlot::CreateContext(); + + ImGuiIO& io = ImGui::GetIO(); + io.ConfigInputTrickleEventQueue = false; + io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; + io.IniFilename = nullptr; + const std::string fontPath = (resourceDirectory / "Font.ttf").string(); + io.Fonts->AddFontFromFileTTF(fontPath.c_str(), kFontSize); + + if (!mContext->isVisible()) { + io.Fonts->Build(); + return; + } + + WindowSurface& surface = window.surface(); + const GpuDevice& gpu = surface.gpu(); + mContext->platform = std::make_unique(window); + mContext->descriptorPool = createDescriptorPool(gpu.device()); + + ImGui_ImplVulkan_InitInfo initInfo{}; + initInfo.Instance = gpu.instance(); + initInfo.PhysicalDevice = gpu.physicalDevice(); + initInfo.Device = gpu.device(); + initInfo.QueueFamily = gpu.graphicsQueueFamily(); + initInfo.Queue = gpu.graphicsQueue(); + initInfo.DescriptorPool = mContext->descriptorPool; + initInfo.RenderPass = surface.renderPass(); + initInfo.MinImageCount = WindowSurface::kFramesInFlight; + initInfo.ImageCount = std::max(surface.imageCount(), WindowSurface::kFramesInFlight); + initInfo.MSAASamples = VK_SAMPLE_COUNT_1_BIT; + ImGui_ImplVulkan_Init(&initInfo); +} + +DebugGUI::~DebugGUI() { + if (mContext->isVisible()) { + mContext->window.surface().gpu().waitIdle(); + ImGui_ImplVulkan_Shutdown(); + mContext->platform.reset(); + vkDestroyDescriptorPool(mContext->window.surface().gpu().device(), mContext->descriptorPool, nullptr); + } + ImPlot::DestroyContext(mContext->implot); + ImGui::DestroyContext(mContext->imgui); +} + +DebugInterface DebugGUI::mode() const { return mContext->mode; } + +void DebugGUI::drawBegin() { + if (mContext->isVisible()) { + ImGui_ImplVulkan_NewFrame(); + mContext->platform->newFrame(); + } else { + const Vec2F size = mContext->window.contentSize(); + ImGuiIO& io = ImGui::GetIO(); + io.DisplaySize = ImVec2(std::max(size.x, 1.0f), std::max(size.y, 1.0f)); + io.DeltaTime = kHeadlessFrameSeconds; + } + ImGui::NewFrame(); +} + +void DebugGUI::drawEnd(const GpuFrame& frame) { + ImGui::Render(); + if (!mContext->isVisible()) { + return; + } + mContext->platform->endFrame(); + ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), frame.commandBuffer); +} + +void DebugGUI::showValue(const char* label, halnf value) { ImGui::Text("%s: %f", label, value); } diff --git a/GraphicApplication/src/debug_ui/private/ImGuiPlatform.cpp b/GraphicApplication/src/debug_ui/private/ImGuiPlatform.cpp new file mode 100644 index 00000000..6dae034c --- /dev/null +++ b/GraphicApplication/src/debug_ui/private/ImGuiPlatform.cpp @@ -0,0 +1,280 @@ +#include "ImGuiPlatform.hpp" + +#include "Timing.hpp" +#include "Window.hpp" + +#include + +#include +#include + +using namespace tp; + +namespace { + constexpr double kFirstFrameSeconds = 1.0 / 60.0; + + ImGuiKey imguiKey(Key key) { + static const std::unordered_map keys = { + { Key::Tab, ImGuiKey_Tab }, + { Key::Left, ImGuiKey_LeftArrow }, + { Key::Right, ImGuiKey_RightArrow }, + { Key::Up, ImGuiKey_UpArrow }, + { Key::Down, ImGuiKey_DownArrow }, + { Key::PageUp, ImGuiKey_PageUp }, + { Key::PageDown, ImGuiKey_PageDown }, + { Key::Home, ImGuiKey_Home }, + { Key::End, ImGuiKey_End }, + { Key::Insert, ImGuiKey_Insert }, + { Key::Delete, ImGuiKey_Delete }, + { Key::Backspace, ImGuiKey_Backspace }, + { Key::Space, ImGuiKey_Space }, + { Key::Enter, ImGuiKey_Enter }, + { Key::Escape, ImGuiKey_Escape }, + { Key::LeftControl, ImGuiKey_LeftCtrl }, + { Key::LeftShift, ImGuiKey_LeftShift }, + { Key::LeftAlt, ImGuiKey_LeftAlt }, + { Key::LeftSuper, ImGuiKey_LeftSuper }, + { Key::RightControl, ImGuiKey_RightCtrl }, + { Key::RightShift, ImGuiKey_RightShift }, + { Key::RightAlt, ImGuiKey_RightAlt }, + { Key::RightSuper, ImGuiKey_RightSuper }, + { Key::Menu, ImGuiKey_Menu }, + { Key::Apostrophe, ImGuiKey_Apostrophe }, + { Key::Comma, ImGuiKey_Comma }, + { Key::Minus, ImGuiKey_Minus }, + { Key::Period, ImGuiKey_Period }, + { Key::Slash, ImGuiKey_Slash }, + { Key::Semicolon, ImGuiKey_Semicolon }, + { Key::Equal, ImGuiKey_Equal }, + { Key::LeftBracket, ImGuiKey_LeftBracket }, + { Key::Backslash, ImGuiKey_Backslash }, + { Key::RightBracket, ImGuiKey_RightBracket }, + { Key::GraveAccent, ImGuiKey_GraveAccent }, + { Key::CapsLock, ImGuiKey_CapsLock }, + { Key::ScrollLock, ImGuiKey_ScrollLock }, + { Key::NumLock, ImGuiKey_NumLock }, + { Key::PrintScreen, ImGuiKey_PrintScreen }, + { Key::Pause, ImGuiKey_Pause }, + { Key::KeypadDecimal, ImGuiKey_KeypadDecimal }, + { Key::KeypadDivide, ImGuiKey_KeypadDivide }, + { Key::KeypadMultiply, ImGuiKey_KeypadMultiply }, + { Key::KeypadSubtract, ImGuiKey_KeypadSubtract }, + { Key::KeypadAdd, ImGuiKey_KeypadAdd }, + { Key::KeypadEnter, ImGuiKey_KeypadEnter }, + { Key::KeypadEqual, ImGuiKey_KeypadEqual }, + }; + + const auto found = keys.find(key); + if (found != keys.end()) { + return found->second; + } + + const auto code = (int) key; + if (code >= (int) Key::A && code <= (int) Key::Z) { + return (ImGuiKey) (ImGuiKey_A + code - (int) Key::A); + } + if (code >= (int) Key::Digit0 && code <= (int) Key::Digit9) { + return (ImGuiKey) (ImGuiKey_0 + code - (int) Key::Digit0); + } + if (code >= (int) Key::F1 && code <= (int) Key::F24) { + return (ImGuiKey) (ImGuiKey_F1 + code - (int) Key::F1); + } + if (code >= (int) Key::Keypad0 && code <= (int) Key::Keypad9) { + return (ImGuiKey) (ImGuiKey_Keypad0 + code - (int) Key::Keypad0); + } + return ImGuiKey_None; + } + + ImGuiKey imguiModifier(Key key) { + switch (key) { + case Key::LeftControl: + case Key::RightControl: return ImGuiMod_Ctrl; + case Key::LeftShift: + case Key::RightShift: return ImGuiMod_Shift; + case Key::LeftAlt: + case Key::RightAlt: return ImGuiMod_Alt; + case Key::LeftSuper: + case Key::RightSuper: return ImGuiMod_Super; + default: return ImGuiKey_None; + } + } + + bool isRightModifier(Key key) { + return key == Key::RightControl || key == Key::RightShift || key == Key::RightAlt || key == Key::RightSuper; + } + + int modifierIndex(ImGuiKey modifier) { + switch (modifier) { + case ImGuiMod_Ctrl: return 0; + case ImGuiMod_Shift: return 1; + case ImGuiMod_Alt: return 2; + default: return 3; + } + } + + int imguiMouseButton(PointerButton button) { + switch (button) { + case PointerButton::Left: return ImGuiMouseButton_Left; + case PointerButton::Right: return ImGuiMouseButton_Right; + case PointerButton::Middle: return ImGuiMouseButton_Middle; + case PointerButton::Back: return 3; + case PointerButton::Forward: return 4; + } + return -1; + } + + CursorShape cursorShape(ImGuiMouseCursor cursor) { + switch (cursor) { + case ImGuiMouseCursor_TextInput: return CursorShape::Text; + case ImGuiMouseCursor_ResizeAll: return CursorShape::Move; + case ImGuiMouseCursor_ResizeNS: return CursorShape::ResizeVertical; + case ImGuiMouseCursor_ResizeEW: return CursorShape::ResizeHorizontal; + case ImGuiMouseCursor_ResizeNESW: return CursorShape::ResizeNorthEastSouthWest; + case ImGuiMouseCursor_ResizeNWSE: return CursorShape::ResizeNorthWestSouthEast; + case ImGuiMouseCursor_Hand: return CursorShape::Pointer; + case ImGuiMouseCursor_NotAllowed: return CursorShape::NotAllowed; + default: return CursorShape::Default; + } + } +} + +ImGuiPlatform::ImGuiPlatform(Window& window) : + mWindow(window) { + ImGuiIO& io = ImGui::GetIO(); + io.BackendPlatformName = "graphic_application"; + io.BackendPlatformUserData = this; + io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; + io.SetClipboardTextFn = &ImGuiPlatform::writeClipboard; + io.GetClipboardTextFn = &ImGuiPlatform::readClipboard; + io.ClipboardUserData = this; + io.SetPlatformImeDataFn = &ImGuiPlatform::placeInputMethod; +} + +ImGuiPlatform::~ImGuiPlatform() { + ImGuiIO& io = ImGui::GetIO(); + io.BackendPlatformName = nullptr; + io.BackendPlatformUserData = nullptr; + io.SetClipboardTextFn = nullptr; + io.GetClipboardTextFn = nullptr; + io.SetPlatformImeDataFn = nullptr; +} + +void ImGuiPlatform::newFrame() { + consumeInput(mWindow.input().state()); + + ImGuiIO& io = ImGui::GetIO(); + const Vec2F size = mWindow.contentSize(); + const Vec2F framebuffer = mWindow.framebufferSize(); + const Vec2F surface = mWindow.frame().surfaceSize(); + const halnf scale = surface.x > 0 ? framebuffer.x / surface.x : 1; + + io.DisplaySize = ImVec2(size.x, size.y); + io.DisplayFramebufferScale = ImVec2(scale, scale); + + const double now = (double) get_time_ns() / 1.0e9; + io.DeltaTime = mPreviousTime > 0 && now > mPreviousTime ? (float) (now - mPreviousTime) : (float) kFirstFrameSeconds; + mPreviousTime = now; + + updateCursor(); +} + +void ImGuiPlatform::endFrame() { + ImDrawData* drawData = ImGui::GetDrawData(); + if (!drawData) { + return; + } + + const halnf margin = mWindow.frame().margin(); + drawData->DisplayPos = ImVec2(-margin, -margin); + drawData->DisplaySize = ImVec2(mWindow.frame().surfaceSize().x, mWindow.frame().surfaceSize().y); + + const bool wantsText = ImGui::GetIO().WantTextInput; + if (wantsText != mTextInputWanted) { + mTextInputWanted = wantsText; + mWindow.services().setTextInputEnabled(wantsText); + } +} + +void ImGuiPlatform::consumeInput(const InputState& input) { + ImGuiIO& io = ImGui::GetIO(); + for (const InputEvent& event : input.events()) { + std::visit( + InputEventVisitor{ + [&io](const PointerMoved& moved) { io.AddMousePosEvent(moved.position.x, moved.position.y); }, + [&io](const PointerLeft&) { io.AddMousePosEvent(-FLT_MAX, -FLT_MAX); }, + [&io](const PointerButtonChanged& changed) { + io.AddMouseButtonEvent(imguiMouseButton(changed.button), changed.pressed); + }, + [&io](const Scrolled& scrolled) { io.AddMouseWheelEvent(scrolled.delta.x, scrolled.delta.y); }, + [this](const KeyChanged& changed) { onKey(changed.key, changed.action != KeyAction::Release); }, + [&io](const TextEntered& text) { io.AddInputCharactersUTF8(text.utf8.c_str()); }, + [&io](const KeyboardFocusChanged& focus) { io.AddFocusEvent(focus.focused); }, + [](const PointerSampled&) {}, + [](const ScrollEnded&) {}, + [](const GestureChanged&) {}, + }, + event + ); + } +} + +void ImGuiPlatform::onKey(Key key, bool pressed) { + updateModifier(key, pressed); + + const ImGuiKey imgui = imguiKey(key); + if (imgui != ImGuiKey_None) { + ImGui::GetIO().AddKeyEvent(imgui, pressed); + } +} + +void ImGuiPlatform::updateModifier(Key key, bool pressed) { + const ImGuiKey modifier = imguiModifier(key); + if (modifier == ImGuiKey_None) { + return; + } + + const size_t side = isRightModifier(key) ? 1 : 0; + mModifierSides[(size_t) modifierIndex(modifier)][side] = pressed; + + const bool down = + mModifierSides[(size_t) modifierIndex(modifier)][0] || mModifierSides[(size_t) modifierIndex(modifier)][1]; + ImGui::GetIO().AddKeyEvent(modifier, down); +} + +void ImGuiPlatform::updateCursor() { + ImGuiIO& io = ImGui::GetIO(); + if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange) { + return; + } + if (!io.WantCaptureMouse) { + mCursor = -2; + return; + } + + const ImGuiMouseCursor cursor = ImGui::GetMouseCursor(); + if (cursor == mCursor) { + return; + } + + mCursor = cursor; + mWindow.services().setCursor(cursorShape(cursor)); +} + +const char* ImGuiPlatform::readClipboard(void* userData) { + auto* self = static_cast(userData); + self->mClipboard = self->mWindow.services().clipboardText(); + return self->mClipboard.c_str(); +} + +void ImGuiPlatform::writeClipboard(void* userData, const char* text) { + static_cast(userData)->mWindow.services().setClipboardText(text ? text : ""); +} + +void ImGuiPlatform::placeInputMethod(ImGuiViewport*, ImGuiPlatformImeData* data) { + auto* self = static_cast(ImGui::GetIO().BackendPlatformUserData); + if (!self || !data->WantVisible) { + return; + } + + self->mWindow.services().setTextInputRect({ data->InputPos.x, data->InputPos.y, 1, data->InputLineHeight }); +} diff --git a/GraphicApplication/src/debug_ui/private/ImGuiPlatform.hpp b/GraphicApplication/src/debug_ui/private/ImGuiPlatform.hpp new file mode 100644 index 00000000..eab61d09 --- /dev/null +++ b/GraphicApplication/src/debug_ui/private/ImGuiPlatform.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include "InputState.hpp" + +#include + +struct ImGuiViewport; +struct ImGuiPlatformImeData; + +namespace tp { + + class Window; + + class ImGuiPlatform { + public: + explicit ImGuiPlatform(Window& window); + ~ImGuiPlatform(); + + ImGuiPlatform(const ImGuiPlatform&) = delete; + ImGuiPlatform& operator=(const ImGuiPlatform&) = delete; + + void newFrame(); + void endFrame(); + + private: + void consumeInput(const InputState& input); + void onKey(Key key, bool pressed); + + static const char* readClipboard(void* userData); + static void writeClipboard(void* userData, const char* text); + static void placeInputMethod(ImGuiViewport* viewport, ImGuiPlatformImeData* data); + + void updateCursor(); + void updateModifier(Key key, bool pressed); + + Window& mWindow; + std::string mClipboard; + double mPreviousTime = 0; + int mCursor = -2; + bool mModifierSides[4][2] = {}; + bool mTextInputWanted = false; + }; +} diff --git a/GraphicApplication/src/debug_ui/public/DebugGUI.hpp b/GraphicApplication/src/debug_ui/public/DebugGUI.hpp new file mode 100644 index 00000000..3b7f1be9 --- /dev/null +++ b/GraphicApplication/src/debug_ui/public/DebugGUI.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include "Window.hpp" + +#include +#include + +namespace tp { + + // Visible renders ImGui into the window and feeds it input; Headless keeps the ImGui context and frames alive + // for code that calls ImGui directly, without drawing or taking input. + enum class DebugInterface { Visible, Headless }; + + // Owns the process ImGui and ImPlot contexts. ImGui calls are valid between drawBegin and drawEnd. + class DebugGUI { + class Context; + + public: + DebugGUI(Window& window, const std::filesystem::path& resourceDirectory, DebugInterface mode); + ~DebugGUI(); + + DebugGUI(const DebugGUI&) = delete; + DebugGUI& operator=(const DebugGUI&) = delete; + + [[nodiscard]] DebugInterface mode() const; + + void drawBegin(); + void drawEnd(const GpuFrame& frame); + + void showValue(const char* label, halnf value); + + private: + std::unique_ptr mContext; + }; +} diff --git a/GraphicApplication/src/decorations/private/WindowDecorations.cpp b/GraphicApplication/src/decorations/private/WindowDecorations.cpp new file mode 100644 index 00000000..5ef9e256 --- /dev/null +++ b/GraphicApplication/src/decorations/private/WindowDecorations.cpp @@ -0,0 +1,55 @@ +#include "WindowDecorations.hpp" + +using namespace tp; + +namespace { + const RGBA kTransparent = { 0, 0, 0, 0 }; + const RGBA kOpaque = { 0, 0, 0, 1 }; + + RectF contentArea(const WindowFrame& frame) { return { { 0, 0 }, frame.contentSize() }; } +} + +void WindowDecorations::paintBase(Canvas& canvas, const WindowFrame& frame) const { + canvas.rect(contentArea(frame), mStyle.base); +} + +void WindowDecorations::paintFrame(Canvas& canvas, const WindowFrame& frame, bool focused) const { + if (!frame.hasShadow()) { + return; + } + + const Canvas::SavedState saved(canvas); + cutCorners(canvas, frame); + paintOutline(canvas, frame); + paintShadow(canvas, frame, focused); +} + +void WindowDecorations::cutCorners(Canvas& canvas, const WindowFrame& frame) const { + canvas.setCompositeOperation(Canvas::CompositeOperation::DestinationOut); + canvas.beginPath(); + canvas.addPathRect(contentArea(frame)); + canvas.addPathRect(contentArea(frame), frame.cornerRadius()); + canvas.setPathWinding(Canvas::PathWinding::Hole); + canvas.fillPath(kOpaque); +} + +void WindowDecorations::paintOutline(Canvas& canvas, const WindowFrame& frame) const { + canvas.setCompositeOperation(Canvas::CompositeOperation::SourceOver); + canvas.outline(contentArea(frame), mStyle.outline, frame.cornerRadius(), mStyle.outlineWidth); +} + +void WindowDecorations::paintShadow(Canvas& canvas, const WindowFrame& frame, bool focused) const { + const halnf margin = frame.margin(); + const Canvas::BoxGradient shadow = { + .box = RectF(Vec2F(0.0f, mStyle.shadowOffsetY), frame.contentSize()), + .radius = frame.cornerRadius(), + .feather = margin * mStyle.shadowFeatherFraction, + .innerColor = focused ? mStyle.focusedShadow : mStyle.unfocusedShadow, + .outerColor = kTransparent, + }; + + canvas.setCompositeOperation(Canvas::CompositeOperation::DestinationOver); + canvas.beginPath(); + canvas.addPathRect({ { -margin, -margin }, frame.surfaceSize() }); + canvas.fillPath(shadow); +} diff --git a/GraphicApplication/src/decorations/public/WindowDecorations.hpp b/GraphicApplication/src/decorations/public/WindowDecorations.hpp new file mode 100644 index 00000000..e6ab1a2f --- /dev/null +++ b/GraphicApplication/src/decorations/public/WindowDecorations.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include "Canvas.hpp" +#include "WindowFrame.hpp" + +namespace tp { + + struct WindowDecorationStyle { + RGBA base = { 0.118f, 0.122f, 0.133f, 1.0f }; + RGBA outline = { 1.0f, 1.0f, 1.0f, 0.07f }; + RGBA focusedShadow = { 0, 0, 0, 0.42f }; + RGBA unfocusedShadow = { 0, 0, 0, 0.24f }; + halnf shadowFeatherFraction = 0.9f; + halnf shadowOffsetY = 3; + halnf outlineWidth = 1; + }; + + // Client-side decorations of a translucent desktop window: base, rounded corners, outline and drop shadow. + // Geometry and edge hit-testing come from WindowFrame, which the backend uses for input; this paints the same frame. + class WindowDecorations { + public: + WindowDecorations() = default; + explicit WindowDecorations(const WindowDecorationStyle& style) : + mStyle(style) {} + + void paintBase(Canvas& canvas, const WindowFrame& frame) const; + void paintFrame(Canvas& canvas, const WindowFrame& frame, bool focused) const; + + private: + void cutCorners(Canvas& canvas, const WindowFrame& frame) const; + void paintOutline(Canvas& canvas, const WindowFrame& frame) const; + void paintShadow(Canvas& canvas, const WindowFrame& frame, bool focused) const; + + WindowDecorationStyle mStyle; + }; +} diff --git a/GraphicApplication/tests/FontCoverageTests.cpp b/GraphicApplication/tests/FontCoverageTests.cpp new file mode 100644 index 00000000..e74a144e --- /dev/null +++ b/GraphicApplication/tests/FontCoverageTests.cpp @@ -0,0 +1,196 @@ +#include "CanvasFonts.hpp" + +#include "stb_truetype.h" + +#include "UnitTest++/UnitTest++.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + const std::filesystem::path kSourceRoot = MODULES_SOURCE_DIR; + const std::filesystem::path kResourceRoot = kSourceRoot / "GraphicApplication" / "rsc"; + const std::vector kUiSourceDirectories = { + "Widgets/private", "Widgets/public", "Taskboard/private", "Taskboard/public" + }; + + class LoadedFont { + public: + explicit LoadedFont(const tp::FontFile& file) { + std::ifstream stream(kResourceRoot / file.path, std::ios::binary); + mBytes.assign(std::istreambuf_iterator(stream), {}); + const auto* data = reinterpret_cast(mBytes.data()); + mValid = !mBytes.empty() && stbtt_InitFont(&mInfo, data, stbtt_GetFontOffsetForIndex(data, 0)); + } + + [[nodiscard]] bool isValid() const { return mValid; } + [[nodiscard]] bool hasGlyph(char32_t codepoint) const { + return mValid && stbtt_FindGlyphIndex(&mInfo, int(codepoint)) != 0; + } + + private: + std::vector mBytes; + stbtt_fontinfo mInfo{}; + bool mValid = false; + }; + + std::optional decodeUtf8(const std::string& text, size_t& position) { + const auto byte = [&](size_t offset) { return static_cast(text[position + offset]); }; + const unsigned char lead = byte(0); + const size_t length = lead < 0x80 ? 1 : lead >= 0xF0 ? 4 : lead >= 0xE0 ? 3 : lead >= 0xC0 ? 2 : 0; + if (length == 0 || position + length > text.size()) { + position++; + return std::nullopt; + } + + char32_t codepoint = length == 1 ? lead : lead & (0xFF >> (length + 1)); + for (size_t index = 1; index < length; index++) { + codepoint = (codepoint << 6) | (byte(index) & 0x3F); + } + position += length; + return codepoint; + } + + void collectEscapes(const std::string& source, std::set& codepoints) { + static const std::regex escape(R"(\\u([0-9A-Fa-f]{4})|\\U([0-9A-Fa-f]{8}))"); + for (std::sregex_iterator match(source.begin(), source.end(), escape), end; match != end; ++match) { + const std::string digits = (*match)[1].matched ? (*match)[1].str() : (*match)[2].str(); + codepoints.insert(char32_t(std::stoul(digits, nullptr, 16))); + } + } + + void collectLiterals(const std::string& source, std::set& codepoints) { + for (size_t position = 0; position < source.size();) { + const std::optional codepoint = decodeUtf8(source, position); + if (codepoint && *codepoint >= 0x80) { + codepoints.insert(*codepoint); + } + } + } + + std::set codepointsUsedByUi() { + std::set codepoints; + for (const std::string& directory : kUiSourceDirectories) { + if (!std::filesystem::is_directory(kSourceRoot / directory)) { + continue; + } + for (const auto& entry : std::filesystem::recursive_directory_iterator(kSourceRoot / directory)) { + if (!entry.is_regular_file()) { + continue; + } + std::ifstream stream(entry.path(), std::ios::binary); + const std::string source{ std::istreambuf_iterator(stream), {} }; + collectEscapes(source, codepoints); + collectLiterals(source, codepoints); + } + } + return codepoints; + } + + std::string hex(char32_t codepoint) { + char buffer[16]; + std::snprintf(buffer, sizeof(buffer), "U+%04X", unsigned(codepoint)); + return buffer; + } + + struct FaceChain { + const char* label; + tp::FontFile face; + std::vector fallbacks; + }; + + std::vector faceChains() { + return { + { "regular", tp::kRegularFont, { tp::kRegularFallbacks.begin(), tp::kRegularFallbacks.end() } }, + { "bold", tp::kBoldFont, { tp::kBoldFallbacks.begin(), tp::kBoldFallbacks.end() } }, + { "monospace", tp::kMonospaceFont, { tp::kMonospaceFallbacks.begin(), tp::kMonospaceFallbacks.end() } }, + }; + } + + const char* const kRussianAlphabetAndPunctuation = + "АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдеёжзийклмнопрстуфхцчшщъыьэюя№«»—–…"; + + std::set codepointsOf(const char* text) { + std::set codepoints; + const std::string source{ text }; + for (size_t position = 0; position < source.size();) { + const std::optional codepoint = decodeUtf8(source, position); + if (codepoint) { + codepoints.insert(*codepoint); + } + } + return codepoints; + } + + void checkChainCovers(const FaceChain& chain, const std::set& codepoints) { + const LoadedFont primary(chain.face); + std::vector fallbacks; + for (const tp::FontFile& file : chain.fallbacks) { + fallbacks.emplace_back(file); + } + + for (const char32_t codepoint : codepoints) { + bool covered = primary.hasGlyph(codepoint); + for (const LoadedFont& fallback : fallbacks) { + covered = covered || fallback.hasGlyph(codepoint); + } + CHECK_EQUAL( + std::string(chain.label) + " " + hex(codepoint) + " drawn", + std::string(chain.label) + " " + hex(codepoint) + (covered ? " drawn" : " has no glyph") + ); + } + } + + template + bool chainContains(const Chain& chain, const tp::FontFile& font) { + for (const tp::FontFile& file : chain) { + if (std::strcmp(file.path, font.path) == 0) { + return true; + } + } + return false; + } +} + +SUITE(FontCoverage) { + TEST(EveryBundledFontLoads) { + for (const tp::FontFile& file : tp::kTextFonts) { + CHECK(LoadedFont(file).isValid()); + } + for (const tp::FontFile& file : tp::kFallbackFonts) { + CHECK(LoadedFont(file).isValid()); + } + } + + TEST(EveryCharacterTheUiDrawsHasAGlyphInEveryTextFace) { + const std::set used = codepointsUsedByUi(); + for (const FaceChain& chain : faceChains()) { + checkChainCovers(chain, used); + } + } + + TEST(RussianTextHasAGlyphInEveryTextFace) { + const std::set russian = codepointsOf(kRussianAlphabetAndPunctuation); + CHECK_EQUAL(size_t(72), russian.size()); + + for (const FaceChain& chain : faceChains()) { + checkChainCovers(chain, russian); + } + } + + TEST(EachFaceFallsBackToTheCyrillicCutOfItsOwnWeight) { + CHECK(chainContains(tp::kRegularFallbacks, tp::kCyrillicRegularFont)); + CHECK(chainContains(tp::kMonospaceFallbacks, tp::kCyrillicRegularFont)); + CHECK(chainContains(tp::kBoldFallbacks, tp::kCyrillicBoldFont)); + CHECK(!chainContains(tp::kBoldFallbacks, tp::kCyrillicRegularFont)); + CHECK(!chainContains(tp::kRegularFallbacks, tp::kCyrillicBoldFont)); + } +} -- 2.49.1 From 51d26678bfc9b879bfd0398aa1f45a13da0e0d19 Mon Sep 17 00:00:00 2001 From: agent Date: Thu, 29 Oct 2026 05:24:06 +0300 Subject: [PATCH 5/5] GraphicApplication: application loop, libraries, and replace Graphics application/ holds the frame loop. Application owns the profiler, the GPU device, the window, canvas and debug UI, and drives an ApplicationDelegate (wantsFrame, processFrame, drawFrame) instead of being a base class. It is configured with ApplicationOptions (window size, identity, platform context, resource directory, debug interface) and has an explicit start/step/finish lifecycle driven by run(). Each frame it paints the decorations around the delegate's drawing; ImGui renders last. SessionRecorder records an Application's raw input through the window tap and its frame profile, and saves them with the environment as one directory for bug reports and replays. Each src/ directory is its own library, linked only to what its headers use: GraphicApplicationInputLayer, -Diagnostics, -Gpu, -BackendContract, -Backend (the platform for the build target), -Window, -Canvas, -Decorations, -DebugUI, and GraphicApplication on top. Only Window can see the backend. graphic_application_resources() copies the shared fonts next to an executable. The old Graphics module is removed. WidgetApplication becomes a delegate that owns its Application; widgets receive an InputView whose origin and button visibility the update manager chooses, instead of mutating shared input state. Consumers use typed text styles and image handles. The color wheel moves from Canvas into the color picker widget; Math gains Vec2::angle() and Vec2::rotated() for it. Duplicate per-module font copies are removed. The port's MODULES_BUILD_* gates are replaced by ANDROID checks, and the generic app id is set directly until the root desktop-entry helpers land. GraphicApplication/.clang-format inherits the root style and requires braces on every control-statement body. --- 3DEditor/CMakeLists.txt | 2 +- 3DEditor/applications/Entry.cpp | 2 +- 3DEditor/public/EditorWidget.hpp | 16 +- CMakeLists.txt | 2 +- GraphicApplication/.clang-format | 4 + GraphicApplication/CMakeLists.txt | 133 ++++++ GraphicApplication/examples/Example.cpp | 25 ++ .../examples/OffscreenProof.cpp | 378 ++++++++++++++++ .../private/GraphicApplication.cpp | 175 ++++++++ .../application/private/SessionRecording.cpp | 175 ++++++++ .../application/public/GraphicApplication.hpp | 97 ++++ .../application/public/SessionRecording.hpp | 73 +++ Graphics/CMakeLists.txt | 23 - Graphics/examples/Example.cpp | 34 -- Graphics/private/Canvas.cpp | 416 ------------------ Graphics/private/DebugGUI.cpp | 107 ----- Graphics/private/EventHandler.cpp | 156 ------- Graphics/private/GraphicApplication.cpp | 146 ------ Graphics/private/Window.cpp | 190 -------- Graphics/private/WindowContext.hpp | 14 - Graphics/public/EventHandler.hpp | 116 ----- Graphics/public/GraphicApplication.hpp | 47 -- Graphics/public/Graphics.hpp | 124 ------ Graphics/public/InputCodes.hpp | 152 ------- Graphics/public/Window.hpp | 38 -- Graphics/rsc/Font.ttf | Bin 42624 -> 0 bytes LibraryViewer/CMakeLists.txt | 4 +- LibraryViewer/applications/Font.ttf | Bin 42624 -> 0 bytes LibraryViewer/private/Player.cpp | 2 + LibraryViewer/public/GUI.hpp | 10 +- LibraryViewer/public/Library.hpp | 1 + Math/public/Vec.hpp | 8 + Math/tests/Tests.cpp | 15 + Objects/CMakeLists.txt | 4 +- Objects/applications/GUIEntry.cpp | 14 +- Objects/rsc/Font.ttf | Bin 42624 -> 0 bytes Sketch3D/CMakeLists.txt | 4 +- Sketch3D/applications/Entry.cpp | 2 +- Sketch3D/applications/Font.ttf | Bin 42624 -> 0 bytes Sketch3D/private/SketchGUI.cpp | 22 +- Sketch3D/public/SketchGUI.hpp | 6 +- Widgets/CMakeLists.txt | 4 +- Widgets/examples/Font.ttf | Bin 42624 -> 0 bytes Widgets/private/RootWidget.cpp | 28 +- Widgets/private/WidgetApplication.cpp | 23 +- Widgets/private/managers/DebugManager.cpp | 34 +- Widgets/private/managers/UpdateManager.cpp | 45 +- .../private/widgets/AnimationTestWidget.cpp | 6 +- Widgets/private/widgets/ColorPickerWidget.cpp | 146 +++++- Widgets/private/widgets/DockWidget.cpp | 6 +- Widgets/private/widgets/FloatingWidget.cpp | 6 +- Widgets/private/widgets/ScrollableWidget.cpp | 6 +- Widgets/private/widgets/SimpleWidgets.cpp | 16 +- Widgets/public/RootWidget.hpp | 2 +- Widgets/public/Widget.hpp | 7 +- Widgets/public/WidgetApplication.hpp | 20 +- Widgets/public/mangers/DebugManager.hpp | 3 +- Widgets/public/mangers/UpdateManager.hpp | 10 +- .../public/widgets/AnimationTestWidget.hpp | 2 +- Widgets/public/widgets/ColorPickerWidget.hpp | 30 +- Widgets/public/widgets/DockWidget.hpp | 2 +- Widgets/public/widgets/FloatingWidget.hpp | 2 +- Widgets/public/widgets/ScrollableWidget.hpp | 2 +- Widgets/public/widgets/SimpleWidgets.hpp | 6 +- 64 files changed, 1409 insertions(+), 1734 deletions(-) create mode 100644 GraphicApplication/.clang-format create mode 100644 GraphicApplication/CMakeLists.txt create mode 100644 GraphicApplication/examples/Example.cpp create mode 100644 GraphicApplication/examples/OffscreenProof.cpp create mode 100644 GraphicApplication/src/application/private/GraphicApplication.cpp create mode 100644 GraphicApplication/src/application/private/SessionRecording.cpp create mode 100644 GraphicApplication/src/application/public/GraphicApplication.hpp create mode 100644 GraphicApplication/src/application/public/SessionRecording.hpp delete mode 100644 Graphics/CMakeLists.txt delete mode 100644 Graphics/examples/Example.cpp delete mode 100644 Graphics/private/Canvas.cpp delete mode 100644 Graphics/private/DebugGUI.cpp delete mode 100644 Graphics/private/EventHandler.cpp delete mode 100644 Graphics/private/GraphicApplication.cpp delete mode 100644 Graphics/private/Window.cpp delete mode 100644 Graphics/private/WindowContext.hpp delete mode 100644 Graphics/public/EventHandler.hpp delete mode 100644 Graphics/public/GraphicApplication.hpp delete mode 100644 Graphics/public/Graphics.hpp delete mode 100644 Graphics/public/InputCodes.hpp delete mode 100644 Graphics/public/Window.hpp delete mode 100644 Graphics/rsc/Font.ttf delete mode 100644 LibraryViewer/applications/Font.ttf delete mode 100644 Objects/rsc/Font.ttf delete mode 100644 Sketch3D/applications/Font.ttf delete mode 100644 Widgets/examples/Font.ttf diff --git a/3DEditor/CMakeLists.txt b/3DEditor/CMakeLists.txt index 1a410b4c..54adad14 100644 --- a/3DEditor/CMakeLists.txt +++ b/3DEditor/CMakeLists.txt @@ -18,7 +18,7 @@ target_link_libraries(${PROJECT_NAME} PRIVATE ${BINDINGS_LIBS}) file(GLOB APP_SOURCES "./applications/*.cpp") add_executable(3DEditorApp ${APP_SOURCES}) target_link_libraries(3DEditorApp ${PROJECT_NAME}) +graphic_application_resources(3DEditorApp) file(COPY "../RasterRender/rsc" DESTINATION "${CMAKE_BINARY_DIR}/${PROJECT_NAME}/") -file(COPY "../Graphics/rsc" DESTINATION "${CMAKE_BINARY_DIR}/${PROJECT_NAME}/") file(COPY "../3DScene/rsc" DESTINATION "${CMAKE_BINARY_DIR}/${PROJECT_NAME}/") diff --git a/3DEditor/applications/Entry.cpp b/3DEditor/applications/Entry.cpp index 004bae33..6867377b 100644 --- a/3DEditor/applications/Entry.cpp +++ b/3DEditor/applications/Entry.cpp @@ -9,7 +9,7 @@ using namespace tp; class EditorGUI : public WidgetApplication { public: EditorGUI() { - auto canvas = this->mGraphics->getCanvas(); + auto canvas = &this->canvas(); mGui = new EditorWidget(canvas, &mEditor); mEditor.loadDefaults(); diff --git a/3DEditor/public/EditorWidget.hpp b/3DEditor/public/EditorWidget.hpp index 7b4e7485..48b4568a 100644 --- a/3DEditor/public/EditorWidget.hpp +++ b/3DEditor/public/EditorWidget.hpp @@ -10,28 +10,28 @@ namespace tp { public: explicit ViewportWidget(Canvas* canvas, Editor* editor) { mEditor = editor; - mImage = canvas->createImageFromTextId(mEditor->getViewportTexID(), { 0, 0 }); + mImage = canvas->createImageFromRenderTarget(mEditor->getViewportTexID()); mCanvas = canvas; } - ~ViewportWidget() override { mCanvas->deleteImageHandle(mImage); } + ~ViewportWidget() override { mCanvas->deleteImage(mImage); } - void process(const EventHandler& events) override { + void process(const InputView& events) override { mEditor->setViewportSize(getAreaT().size); } void draw(Canvas& canvas) override { mEditor->renderViewport(); - canvas.updateTextureID(mImage, mEditor->getViewportTexID()); - canvas.drawImage(getArea().relative(), &mImage, PI); + canvas.updateImageFromRenderTarget(mImage, mEditor->getViewportTexID()); + canvas.drawImage(getArea().relative(), mImage, PI); } public: Editor* mEditor; Canvas* mCanvas = nullptr; - Canvas::ImageHandle mImage; + ImageHandle mImage; }; class EditorWidget : public DockWidget { @@ -91,7 +91,7 @@ namespace tp { mNavigationReset.setAction( [this]() { mEditor->navigationReset(); }); } - void process(const EventHandler& events) override { + void process(const InputView& events) override { DockWidget::process(events); if (auto obj = mEditor->getActiveObject()) { // dummy rotate active object @@ -107,7 +107,7 @@ namespace tp { auto pointerPrev = events.getPointerPrev(); auto pointerRelative = (((pointer - activeArea.pos) / activeArea.size) - 0.5f) * 2; - if (activeArea.isInside(pointer) && events.isDown(InputID::MOUSE1)) { + if (activeArea.isInside(pointer) && events.isDown(PointerButton::Left)) { switch (mNavigationType) { case ORBIT: mEditor->navigationOrbit(events.getPointerDelta() / activeArea.size * 3); break; diff --git a/CMakeLists.txt b/CMakeLists.txt index db5b4851..be7af340 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,7 +27,7 @@ add_subdirectory(Allocators) add_subdirectory(Strings) add_subdirectory(Language) add_subdirectory(Connection) -add_subdirectory(Graphics) +add_subdirectory(GraphicApplication) add_subdirectory(DataAnalysis) add_subdirectory(Objects) add_subdirectory(Widgets) diff --git a/GraphicApplication/.clang-format b/GraphicApplication/.clang-format new file mode 100644 index 00000000..98267ea4 --- /dev/null +++ b/GraphicApplication/.clang-format @@ -0,0 +1,4 @@ +--- +BasedOnStyle: InheritParentConfig +AllowShortIfStatementsOnASingleLine: Never +InsertBraces: true diff --git a/GraphicApplication/CMakeLists.txt b/GraphicApplication/CMakeLists.txt new file mode 100644 index 00000000..ccfd36a4 --- /dev/null +++ b/GraphicApplication/CMakeLists.txt @@ -0,0 +1,133 @@ +project(GraphicApplication) + +find_package(Vulkan REQUIRED) +find_package(Threads REQUIRED) + +function(graphic_application_library TARGET_NAME LIBRARY_DIR) + set(DIR ${CMAKE_CURRENT_SOURCE_DIR}/src/${LIBRARY_DIR}) + file(GLOB SOURCES CONFIGURE_DEPENDS ${DIR}/private/*.cpp ${DIR}/private/*.hpp ${DIR}/public/*.hpp) + add_library(${TARGET_NAME} STATIC ${SOURCES}) + if (EXISTS ${DIR}/public) + target_include_directories(${TARGET_NAME} PUBLIC ${DIR}/public) + endif () + target_include_directories(${TARGET_NAME} PRIVATE ${DIR}/private) +endfunction() + +### ---------------------- Input layer --------------------- ### +graphic_application_library(${PROJECT_NAME}InputLayer input_layer) +target_link_libraries(${PROJECT_NAME}InputLayer PUBLIC Math Modules) + +### ---------------------- Diagnostics --------------------- ### +graphic_application_library(${PROJECT_NAME}Diagnostics diagnostics) +target_link_libraries(${PROJECT_NAME}Diagnostics PUBLIC Callstack Threads::Threads) + +### ---------------------- GPU device and window surfaces --------------------- ### +graphic_application_library(${PROJECT_NAME}Gpu gpu) +target_link_libraries(${PROJECT_NAME}Gpu PUBLIC Vulkan::Vulkan PRIVATE ${PROJECT_NAME}Diagnostics) + +### ---------------------- Wayland protocols --------------------- ### +if (NOT ANDROID) + find_package(PkgConfig REQUIRED) + pkg_check_modules(WAYLAND REQUIRED IMPORTED_TARGET wayland-client xkbcommon) + pkg_get_variable(WAYLAND_PROTOCOLS_DIR wayland-protocols pkgdatadir) + pkg_get_variable(WAYLAND_SCANNER wayland-scanner wayland_scanner) + + ### ---------------------- Wayland Protocols --------------------- ### + set(WAYLAND_PROTOCOL_FILES + stable/xdg-shell/xdg-shell.xml + stable/viewporter/viewporter.xml + staging/fractional-scale/fractional-scale-v1.xml + staging/cursor-shape/cursor-shape-v1.xml + staging/xdg-activation/xdg-activation-v1.xml + stable/tablet/tablet-v2.xml + unstable/text-input/text-input-unstable-v3.xml + unstable/xdg-decoration/xdg-decoration-unstable-v1.xml + unstable/pointer-gestures/pointer-gestures-unstable-v1.xml + ) + + set(WAYLAND_GENERATED_DIR ${CMAKE_CURRENT_BINARY_DIR}/wayland) + file(MAKE_DIRECTORY ${WAYLAND_GENERATED_DIR}) + + foreach(PROTOCOL_FILE ${WAYLAND_PROTOCOL_FILES}) + get_filename_component(PROTOCOL_NAME ${PROTOCOL_FILE} NAME_WE) + set(PROTOCOL_XML ${WAYLAND_PROTOCOLS_DIR}/${PROTOCOL_FILE}) + set(PROTOCOL_HEADER ${WAYLAND_GENERATED_DIR}/${PROTOCOL_NAME}-client-protocol.h) + set(PROTOCOL_SOURCE ${WAYLAND_GENERATED_DIR}/${PROTOCOL_NAME}-protocol.c) + + add_custom_command( + OUTPUT ${PROTOCOL_HEADER} ${PROTOCOL_SOURCE} + COMMAND ${WAYLAND_SCANNER} client-header ${PROTOCOL_XML} ${PROTOCOL_HEADER} + COMMAND ${WAYLAND_SCANNER} private-code ${PROTOCOL_XML} ${PROTOCOL_SOURCE} + DEPENDS ${PROTOCOL_XML} + ) + list(APPEND WAYLAND_PROTOCOL_SOURCES ${PROTOCOL_HEADER} ${PROTOCOL_SOURCE}) + endforeach() + + add_library(WaylandProtocols STATIC ${WAYLAND_PROTOCOL_SOURCES}) + target_include_directories(WaylandProtocols PUBLIC ${WAYLAND_GENERATED_DIR}) + target_link_libraries(WaylandProtocols PUBLIC PkgConfig::WAYLAND) + set_target_properties(WaylandProtocols PROPERTIES LINKER_LANGUAGE C) +endif () + +### ---------------------- Backend contract --------------------- ### +graphic_application_library(${PROJECT_NAME}BackendContract backends/contract) +target_link_libraries(${PROJECT_NAME}BackendContract PUBLIC Math ${PROJECT_NAME}InputLayer ${PROJECT_NAME}Gpu) + +### ---------------------- Platform backend --------------------- ### +if (ANDROID) + graphic_application_library(${PROJECT_NAME}Backend backends/android) + target_link_libraries(${PROJECT_NAME}Backend PUBLIC native_app_glue PRIVATE android log) +else () + graphic_application_library(${PROJECT_NAME}Backend backends/wayland) + target_link_libraries(${PROJECT_NAME}Backend PRIVATE WaylandProtocols PkgConfig::WAYLAND) +endif () +target_link_libraries(${PROJECT_NAME}Backend PUBLIC ${PROJECT_NAME}BackendContract) + +### ---------------------- Window --------------------- ### +graphic_application_library(${PROJECT_NAME}Window window) +target_link_libraries(${PROJECT_NAME}Window PUBLIC Math ${PROJECT_NAME}BackendContract PRIVATE ${PROJECT_NAME}Backend) + +### ---------------------- Canvas --------------------- ### +graphic_application_library(${PROJECT_NAME}Canvas canvas) +target_link_libraries(${PROJECT_NAME}Canvas PUBLIC Math Containers ${PROJECT_NAME}BackendContract PRIVATE ${PROJECT_NAME}Diagnostics NanovgVk) + +### ---------------------- Window decorations --------------------- ### +graphic_application_library(${PROJECT_NAME}Decorations decorations) +target_link_libraries(${PROJECT_NAME}Decorations PUBLIC ${PROJECT_NAME}Canvas) + +### ---------------------- Debug UI --------------------- ### +graphic_application_library(${PROJECT_NAME}DebugUI debug_ui) +target_link_libraries(${PROJECT_NAME}DebugUI PUBLIC ${PROJECT_NAME}Window PRIVATE Imgui) + +### ---------------------- Application --------------------- ### +graphic_application_library(${PROJECT_NAME} application) +target_link_libraries(${PROJECT_NAME} PUBLIC ${PROJECT_NAME}Window ${PROJECT_NAME}Canvas ${PROJECT_NAME}Decorations ${PROJECT_NAME}DebugUI ${PROJECT_NAME}Diagnostics) +target_compile_definitions(${PROJECT_NAME} PRIVATE MODULES_GENERIC_APP_ID="online.ilushabasic.modules" MODULES_GENERIC_APP_NAME="Modules") + +# Copies the shared fonts into an "rsc" directory next to an executable, where the desktop backend looks for them. +set(GRAPHIC_APPLICATION_RESOURCES ${CMAKE_CURRENT_SOURCE_DIR}/rsc CACHE INTERNAL "") +function(graphic_application_resources TARGET_NAME) + add_custom_command(TARGET ${TARGET_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory ${GRAPHIC_APPLICATION_RESOURCES} $/rsc + ) +endfunction() + +### -------------------------- Examples -------------------------- ### +if (NOT ANDROID) + add_executable(Example${PROJECT_NAME} ./examples/Example.cpp) + target_link_libraries(Example${PROJECT_NAME} ${PROJECT_NAME} Imgui) + graphic_application_resources(Example${PROJECT_NAME}) + + add_executable(OffscreenProof ./examples/OffscreenProof.cpp) + target_link_libraries(OffscreenProof NanovgVk Vulkan::Vulkan) +endif () + +### -------------------------- Tests -------------------------- ### +if (NOT ANDROID) + file(GLOB TEST_SOURCES CONFIGURE_DEPENDS "./tests/*.cpp") + add_executable(Test${PROJECT_NAME} ${TEST_SOURCES}) + target_include_directories(Test${PROJECT_NAME} PRIVATE ./src/canvas/private ./src/backends/wayland/private) + target_compile_definitions(Test${PROJECT_NAME} PRIVATE MODULES_SOURCE_DIR="${CMAKE_SOURCE_DIR}") + target_link_libraries(Test${PROJECT_NAME} ${PROJECT_NAME}Window ${PROJECT_NAME}Backend ${PROJECT_NAME}Diagnostics NanovgVk UnitTest++) + add_test(NAME Test${PROJECT_NAME} COMMAND Test${PROJECT_NAME}) +endif () diff --git a/GraphicApplication/examples/Example.cpp b/GraphicApplication/examples/Example.cpp new file mode 100644 index 00000000..84e7530e --- /dev/null +++ b/GraphicApplication/examples/Example.cpp @@ -0,0 +1,25 @@ +#include "GraphicApplication.hpp" + +#include "imgui.h" + +using namespace tp; + +class ExampleDelegate : public ApplicationDelegate { +public: + void attach(Application& application) { mApplication = &application; } + + void drawFrame(Canvas&) override { + ImGui::ShowDemoWindow(); + mApplication->showFrameRates(); + } + +private: + Application* mApplication = nullptr; +}; + +int main() { + ExampleDelegate delegate; + Application application(delegate); + delegate.attach(application); + application.run(); +} diff --git a/GraphicApplication/examples/OffscreenProof.cpp b/GraphicApplication/examples/OffscreenProof.cpp new file mode 100644 index 00000000..c91be7d2 --- /dev/null +++ b/GraphicApplication/examples/OffscreenProof.cpp @@ -0,0 +1,378 @@ +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace { + + constexpr uint32_t kWidth = 256; + constexpr uint32_t kHeight = 256; + constexpr VkFormat kColorFormat = VK_FORMAT_R8G8B8A8_UNORM; + constexpr VkFormat kDepthFormat = VK_FORMAT_D24_UNORM_S8_UINT; + + void check(VkResult result, const char* what) { + if (result != VK_SUCCESS) { + throw std::runtime_error(std::string(what) + " failed"); + } + } + + uint32_t findMemoryType(VkPhysicalDevice gpu, uint32_t filter, VkMemoryPropertyFlags flags) { + VkPhysicalDeviceMemoryProperties properties{}; + vkGetPhysicalDeviceMemoryProperties(gpu, &properties); + for (uint32_t i = 0; i < properties.memoryTypeCount; i++) { + if ((filter & (1 << i)) && (properties.memoryTypes[i].propertyFlags & flags) == flags) { + return i; + } + } + throw std::runtime_error("no memory type"); + } + + struct Image { + VkImage image = VK_NULL_HANDLE; + VkDeviceMemory memory = VK_NULL_HANDLE; + VkImageView view = VK_NULL_HANDLE; + }; + + Image createImage( + VkPhysicalDevice gpu, VkDevice device, VkFormat format, VkImageUsageFlags usage, VkImageAspectFlags aspect + ) { + Image out; + VkImageCreateInfo info{ + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .imageType = VK_IMAGE_TYPE_2D, + .format = format, + .extent = { kWidth, kHeight, 1 }, + .mipLevels = 1, + .arrayLayers = 1, + .samples = VK_SAMPLE_COUNT_1_BIT, + .tiling = VK_IMAGE_TILING_OPTIMAL, + .usage = usage, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + }; + check(vkCreateImage(device, &info, nullptr, &out.image), "vkCreateImage"); + + VkMemoryRequirements req{}; + vkGetImageMemoryRequirements(device, out.image, &req); + VkMemoryAllocateInfo alloc{ + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .allocationSize = req.size, + .memoryTypeIndex = findMemoryType(gpu, req.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT), + }; + check(vkAllocateMemory(device, &alloc, nullptr, &out.memory), "vkAllocateMemory"); + vkBindImageMemory(device, out.image, out.memory, 0); + + VkImageViewCreateInfo viewInfo{ + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .image = out.image, + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = format, + .subresourceRange = { aspect, 0, 1, 0, 1 }, + }; + check(vkCreateImageView(device, &viewInfo, nullptr, &out.view), "vkCreateImageView"); + return out; + } + + void writePpm(const char* path, const std::vector& rgba) { + FILE* file = std::fopen(path, "wb"); + std::fprintf(file, "P6\n%u %u\n255\n", kWidth, kHeight); + for (uint32_t i = 0; i < kWidth * kHeight; i++) { + std::fputc(rgba[i * 4 + 0], file); + std::fputc(rgba[i * 4 + 1], file); + std::fputc(rgba[i * 4 + 2], file); + } + std::fclose(file); + } + +} + +int main() { + VkApplicationInfo appInfo{ .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, .apiVersion = VK_API_VERSION_1_2 }; + VkInstanceCreateInfo instanceInfo{ .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .pApplicationInfo = &appInfo }; + VkInstance instance = VK_NULL_HANDLE; + check(vkCreateInstance(&instanceInfo, nullptr, &instance), "vkCreateInstance"); + + uint32_t gpuCount = 0; + vkEnumeratePhysicalDevices(instance, &gpuCount, nullptr); + std::vector gpus(gpuCount); + vkEnumeratePhysicalDevices(instance, &gpuCount, gpus.data()); + VkPhysicalDevice gpu = gpus.at(0); + + uint32_t familyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(gpu, &familyCount, nullptr); + std::vector families(familyCount); + vkGetPhysicalDeviceQueueFamilyProperties(gpu, &familyCount, families.data()); + uint32_t graphicsFamily = 0; + for (uint32_t i = 0; i < familyCount; i++) { + if (families[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) { + graphicsFamily = i; + break; + } + } + + uint32_t deviceExtCount = 0; + vkEnumerateDeviceExtensionProperties(gpu, nullptr, &deviceExtCount, nullptr); + std::vector deviceExts(deviceExtCount); + vkEnumerateDeviceExtensionProperties(gpu, nullptr, &deviceExtCount, deviceExts.data()); + bool hasExtendedDynamicState = false; + for (const auto& extension : deviceExts) { + if (std::strcmp(extension.extensionName, VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME) == 0) { + hasExtendedDynamicState = true; + } + } + + std::vector enabledDeviceExts; + if (hasExtendedDynamicState) { + enabledDeviceExts.push_back(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME); + } + + VkPhysicalDeviceExtendedDynamicStateFeaturesEXT dynamicStateFeatures{ + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_FEATURES_EXT, + .extendedDynamicState = VK_TRUE, + }; + + const float priority = 1.0f; + VkDeviceQueueCreateInfo queueInfo{ + .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, + .queueFamilyIndex = graphicsFamily, + .queueCount = 1, + .pQueuePriorities = &priority, + }; + VkDeviceCreateInfo deviceInfo{ + .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, + .pNext = hasExtendedDynamicState ? &dynamicStateFeatures : nullptr, + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &queueInfo, + .enabledExtensionCount = static_cast(enabledDeviceExts.size()), + .ppEnabledExtensionNames = enabledDeviceExts.data(), + }; + VkDevice device = VK_NULL_HANDLE; + check(vkCreateDevice(gpu, &deviceInfo, nullptr, &device), "vkCreateDevice"); + std::printf("extended_dynamic_state: %s\n", hasExtendedDynamicState ? "enabled" : "unavailable"); + + VkQueue queue = VK_NULL_HANDLE; + vkGetDeviceQueue(device, graphicsFamily, 0, &queue); + + Image color = createImage( + gpu, + device, + kColorFormat, + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT, + VK_IMAGE_ASPECT_COLOR_BIT + ); + Image depth = createImage( + gpu, + device, + kDepthFormat, + VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, + VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT + ); + + const std::array attachments = { + VkAttachmentDescription{ + .format = kColorFormat, + .samples = VK_SAMPLE_COUNT_1_BIT, + .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, + .storeOp = VK_ATTACHMENT_STORE_OP_STORE, + .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, + .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .finalLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + }, + VkAttachmentDescription{ + .format = kDepthFormat, + .samples = VK_SAMPLE_COUNT_1_BIT, + .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, + .storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, + .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, + .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + }, + }; + VkAttachmentReference colorRef{ 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }; + VkAttachmentReference depthRef{ 1, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL }; + VkSubpassDescription subpass{ + .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, + .colorAttachmentCount = 1, + .pColorAttachments = &colorRef, + .pDepthStencilAttachment = &depthRef, + }; + VkRenderPassCreateInfo renderPassInfo{ + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, + .attachmentCount = static_cast(attachments.size()), + .pAttachments = attachments.data(), + .subpassCount = 1, + .pSubpasses = &subpass, + }; + VkRenderPass renderPass = VK_NULL_HANDLE; + check(vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass), "vkCreateRenderPass"); + + const std::array framebufferViews = { color.view, depth.view }; + VkFramebufferCreateInfo framebufferInfo{ + .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, + .renderPass = renderPass, + .attachmentCount = static_cast(framebufferViews.size()), + .pAttachments = framebufferViews.data(), + .width = kWidth, + .height = kHeight, + .layers = 1, + }; + VkFramebuffer framebuffer = VK_NULL_HANDLE; + check(vkCreateFramebuffer(device, &framebufferInfo, nullptr, &framebuffer), "vkCreateFramebuffer"); + + VkCommandPoolCreateInfo poolInfo{ + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, + .queueFamilyIndex = graphicsFamily, + }; + VkCommandPool pool = VK_NULL_HANDLE; + check(vkCreateCommandPool(device, &poolInfo, nullptr, &pool), "vkCreateCommandPool"); + + std::array commandBuffers{}; + VkCommandBufferAllocateInfo cmdAlloc{ + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .commandPool = pool, + .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, + .commandBufferCount = 1, + }; + check(vkAllocateCommandBuffers(device, &cmdAlloc, commandBuffers.data()), "vkAllocateCommandBuffers"); + + uint32_t currentFrame = 0; + VKNVGCreateInfo nvgInfo{}; + nvgInfo.gpu = gpu; + nvgInfo.device = device; + nvgInfo.renderpass = renderPass; + nvgInfo.cmdBuffer = commandBuffers.data(); + nvgInfo.graphicsQueueFamilyIndex = graphicsFamily; + nvgInfo.currentFrame = ¤tFrame; + nvgInfo.swapchainImageCount = 1; + nvgInfo.ext.dynamicState = hasExtendedDynamicState; + NVGcontext* vg = nvgCreateVk(&nvgInfo, NVG_ANTIALIAS | NVG_STENCIL_STROKES, queue); + if (vg == nullptr) { + throw std::runtime_error("nvgCreateVk returned null"); + } + + VkCommandBuffer cmd = commandBuffers[0]; + VkCommandBufferBeginInfo begin{ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; + vkBeginCommandBuffer(cmd, &begin); + + std::array clears{}; + clears[0].color = { { 0.10f, 0.10f, 0.12f, 1.0f } }; + clears[1].depthStencil = { 1.0f, 0 }; + VkRenderPassBeginInfo pass{ + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, + .renderPass = renderPass, + .framebuffer = framebuffer, + .renderArea = { { 0, 0 }, { kWidth, kHeight } }, + .clearValueCount = static_cast(clears.size()), + .pClearValues = clears.data(), + }; + vkCmdBeginRenderPass(cmd, &pass, VK_SUBPASS_CONTENTS_INLINE); + + VkViewport viewport{ + .x = 0.0f, + .y = 0.0f, + .width = static_cast(kWidth), + .height = static_cast(kHeight), + .minDepth = 0.0f, + .maxDepth = 1.0f, + }; + vkCmdSetViewport(cmd, 0, 1, &viewport); + VkRect2D scissor{ { 0, 0 }, { kWidth, kHeight } }; + vkCmdSetScissor(cmd, 0, 1, &scissor); + + nvgBeginFrame(vg, kWidth, kHeight, 1.0f); + + nvgBeginPath(vg); + nvgRect(vg, 40, 40, 120, 80); + nvgFillColor(vg, nvgRGBA(220, 80, 60, 255)); + nvgFill(vg); + + nvgBeginPath(vg); + nvgCircle(vg, 170, 170, 50); + nvgFillColor(vg, nvgRGBA(60, 160, 230, 255)); + nvgFill(vg); + + nvgBeginPath(vg); + nvgMoveTo(vg, 30, 220); + nvgLineTo(vg, 230, 150); + nvgStrokeColor(vg, nvgRGBA(240, 220, 40, 255)); + nvgStrokeWidth(vg, 6); + nvgStroke(vg); + + nvgEndFrame(vg); + + vkCmdEndRenderPass(cmd); + vkEndCommandBuffer(cmd); + + VkSubmitInfo submit{ + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .commandBufferCount = 1, + .pCommandBuffers = &cmd, + }; + vkQueueSubmit(queue, 1, &submit, VK_NULL_HANDLE); + vkQueueWaitIdle(queue); + + VkBufferCreateInfo bufferInfo{ + .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .size = static_cast(kWidth) * kHeight * 4, + .usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + }; + VkBuffer readback = VK_NULL_HANDLE; + check(vkCreateBuffer(device, &bufferInfo, nullptr, &readback), "vkCreateBuffer"); + VkMemoryRequirements bufReq{}; + vkGetBufferMemoryRequirements(device, readback, &bufReq); + VkMemoryAllocateInfo bufAlloc{ + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .allocationSize = bufReq.size, + .memoryTypeIndex = findMemoryType( + gpu, bufReq.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT + ), + }; + VkDeviceMemory readbackMemory = VK_NULL_HANDLE; + check(vkAllocateMemory(device, &bufAlloc, nullptr, &readbackMemory), "vkAllocateMemory readback"); + vkBindBufferMemory(device, readback, readbackMemory, 0); + + vkResetCommandBuffer(cmd, 0); + vkBeginCommandBuffer(cmd, &begin); + VkBufferImageCopy region{ + .imageSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 }, + .imageExtent = { kWidth, kHeight, 1 }, + }; + vkCmdCopyImageToBuffer(cmd, color.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, readback, 1, ®ion); + vkEndCommandBuffer(cmd); + vkQueueSubmit(queue, 1, &submit, VK_NULL_HANDLE); + vkQueueWaitIdle(queue); + + void* mapped = nullptr; + vkMapMemory(device, readbackMemory, 0, bufferInfo.size, 0, &mapped); + std::vector pixels(bufferInfo.size); + std::memcpy(pixels.data(), mapped, bufferInfo.size); + vkUnmapMemory(device, readbackMemory); + + writePpm("nanovg_offscreen.ppm", pixels); + + uint64_t nonBackground = 0; + for (uint32_t i = 0; i < kWidth * kHeight; i++) { + const uint8_t r = pixels[i * 4 + 0]; + const uint8_t g = pixels[i * 4 + 1]; + const uint8_t b = pixels[i * 4 + 2]; + if (r > 40 || g > 40 || b > 40) { + nonBackground++; + } + } + std::printf( + "rendered %llu / %u non-background pixels\n", static_cast(nonBackground), kWidth * kHeight + ); + + nvgDeleteVk(vg); + return nonBackground > 1000 ? 0 : 1; +} diff --git a/GraphicApplication/src/application/private/GraphicApplication.cpp b/GraphicApplication/src/application/private/GraphicApplication.cpp new file mode 100644 index 00000000..30df86be --- /dev/null +++ b/GraphicApplication/src/application/private/GraphicApplication.cpp @@ -0,0 +1,175 @@ +#include "GraphicApplication.hpp" + +using namespace tp; + +WindowIdentity WindowIdentity::generic() { return { MODULES_GENERIC_APP_ID, MODULES_GENERIC_APP_NAME }; } + +Application::Application(ApplicationDelegate& delegate, const ApplicationOptions& options) : + mDelegate(delegate), + mProcessSection(mProfiler.section("frame.process")), + mPresentSection(mProfiler.section("frame.present")), + mDrawSection(mProfiler.section("frame.draw")), + mInputEventsCounter(mProfiler.counter("input.events")), + mGpu(Window::createGpuDevice()), + mWindow(std::make_unique(*mGpu, mProfiler, options.window)), + mCanvas(std::make_unique(mWindow->surface(), mProfiler, options.resourceDirectory)), + mDebugGUI(std::make_unique(*mWindow, options.resourceDirectory, options.debugInterface)) { + mHangSettings.application = HangWatchdog::processName(); + mPerSecondTimer.setDuration(1000.f); +} + +Application::~Application() { finish(); } + +void Application::setHangSettings(HangSettings settings) { mHangSettings = std::move(settings); } + +void Application::start() { + if (mRunning) { + return; + } + + mHangWatchdog = std::make_unique(mHangSettings, mProfiler.trail()); + for (const HangWatchdog::ReportFileSource& reportFileSource : mHangReportFileSources) { + mHangWatchdog->addReportFileSource(reportFileSource); + } + + mWindow->loop().waitForReady(); + + mPreviousFrameTime = get_time(); + mPerSecondTimer.reset(); + mRunning = true; + runFrame(); +} + +void Application::step(halnf deltaMs) { + if (!mRunning) { + return; + } + + enterPhase(UiPhase::Idle); + mWindow->loop().processEvents(false); + runFrame(deltaMs); +} + +void Application::finish() { + if (!mRunning) { + return; + } + + mHangWatchdog.reset(); + mRunning = false; +} + +void Application::run() { + start(); + + WindowEventLoop& loop = mWindow->loop(); + while (mRunning && !mWindow->controls().shouldClose()) { + enterPhase(UiPhase::Idle); + loop.processEvents(!wantsFrame() || loop.isWaitingForFrame()); + while (loop.isWaitingForFrame() && !mWindow->controls().shouldClose()) { + loop.processEvents(true); + } + runFrame(); + } + + finish(); +} + +void Application::addHangReportFileSource(HangWatchdog::ReportFileSource reportFileSource) { + if (mHangWatchdog) { + mHangWatchdog->addReportFileSource(reportFileSource); + } + mHangReportFileSources.push_back(std::move(reportFileSource)); +} + +void Application::enterPhase(UiPhase phase) { + if (mHangWatchdog) { + mHangWatchdog->enterPhase(phase); + } +} + +bool Application::wantsFrame() { return mDelegate.wantsFrame() || mWindow->loop().hasPendingWork(); } + +void Application::runFrame() { + updateGlobalTime(); + + const time_ms now = get_time(); + const halnf deltaMs = halnf(now - mPreviousFrameTime); + mPreviousFrameTime = now; + runFrame(deltaMs); +} + +void Application::runFrame(halnf deltaMs) { + mProfiler.beginFrame(); + + enterPhase(UiPhase::Input); + processPendingInput(deltaMs); + + enterPhase(UiPhase::Draw); + present(); + updateFrameCounters(); + + mProfiler.endFrame(); +} + +void Application::processPendingInput(halnf deltaMs) { + const ProfileScope scope(mProfiler, mProcessSection); + + const size_t eventCount = mWindow->loop().advanceFrame(); + mProfiler.addCounter(mInputEventsCounter, (double) eventCount); + if (mHangWatchdog) { + mHangWatchdog->noteInputEvents(eventCount); + } + + mDelegate.processFrame(mWindow->input().state(), deltaMs); + mFramesProcessed++; +} + +void Application::present() { + const ProfileScope scope(mProfiler, mPresentSection); + const std::optional frame = mWindow->loop().frameBegin(); + if (!frame) { + return; + } + + mCanvas->drawBegin(*frame, mWindow->frame()); + mDebugGUI->drawBegin(); + + drawFrameContent(); + + enterPhase(UiPhase::Present); + mCanvas->drawEnd(); + mDebugGUI->drawEnd(*frame); + + mWindow->loop().frameEnd(); + mFramesDrawn++; +} + +void Application::drawFrameContent() { + const ProfileScope scope(mProfiler, mDrawSection); + const WindowFrame& frame = mWindow->frame(); + + mDecorations.paintBase(*mCanvas, frame); + { + const Canvas::SavedState saved(*mCanvas); + mDelegate.drawFrame(*mCanvas); + } + mDecorations.paintFrame(*mCanvas, frame, mWindow->controls().isFocused()); +} + +void Application::updateFrameCounters() { + if (!mPerSecondTimer.isTimeout()) { + return; + } + mPerSecondTimer.reset(); + + mFramesProcessedPerSecond = mFramesProcessed; + mFramesDrawnPerSecond = mFramesDrawn; + mFramesDrawn = 0; + mFramesProcessed = 0; +} + +void Application::showFrameRates() { + mDebugGUI->showValue("Frames processed per second", mFramesProcessedPerSecond); + mDebugGUI->showValue("Frames drawn per second", mFramesDrawnPerSecond); +} diff --git a/GraphicApplication/src/application/private/SessionRecording.cpp b/GraphicApplication/src/application/private/SessionRecording.cpp new file mode 100644 index 00000000..cde05da5 --- /dev/null +++ b/GraphicApplication/src/application/private/SessionRecording.cpp @@ -0,0 +1,175 @@ +#include "SessionRecording.hpp" + +#include +#include +#include +#include +#include +#include + +namespace tp { + + namespace { + double steadyMs() { + const auto now = std::chrono::steady_clock::now().time_since_epoch(); + return std::chrono::duration(now).count(); + } + + bool writeText(const std::filesystem::path& path, const std::string& text) { + std::ofstream file(path, std::ios::binary | std::ios::trunc); + file.write(text.data(), (std::streamsize) text.size()); + return (bool) file; + } + } + + std::string SessionEnvironment::serialize() const { + char size[64]; + std::snprintf(size, sizeof(size), "%.1f %.1f", windowSize.x, windowSize.y); + char scale[32]; + std::snprintf(scale, sizeof(scale), "%.3f", contentScale); + return "application " + application + "\nrevision " + revision + "\ndevice " + device + "\ndate " + date + + "\nwindow " + size + "\nscale " + scale + "\n"; + } + + bool SessionRecording::save(const std::string& directory) const { + const std::filesystem::path root(directory); + std::error_code error; + std::filesystem::create_directories(root, error); + if (error) { + return false; + } + + bool written = writeText(root / "environment.txt", environment.serialize()); + written = written && input.save((root / "input.txt").string()); + if (!recordedFrames.empty()) { + written = written && writeText(root / "recorded-frames.tsv", recordedFrames); + } + if (saveSnapshot) { + written = written && saveSnapshot(directory); + } + return written; + } + + struct SessionRecorder::Recording { + explicit Recording(InputRecorder::Clock clock) : + input(std::move(clock)) {} + + SessionEnvironment environment; + SessionRecording::Saver snapshot; + InputRecorder input; + double startMs = 0; + bool profilerWasEnabled = false; + }; + + SessionRecorder::SessionRecorder(Application& application, Clock clock) : + mApplication(application), + mClock(clock ? std::move(clock) : Clock(steadyMs)) {} + + SessionRecorder::~SessionRecorder() { cancel(); } + + double SessionRecorder::elapsedMs() const { return mRecording ? mClock() - mRecording->startMs : 0.0; } + + SessionEnvironment SessionRecorder::environment() const { + SessionEnvironment environment; + environment.application = HangWatchdog::processName(); + environment.revision = mRevision; + environment.device = mApplication.gpu().deviceName(); + + const std::time_t now = std::time(nullptr); + char date[64]; + std::strftime(date, sizeof(date), "%Y-%m-%d %H:%M:%S", std::localtime(&now)); + environment.date = date; + + environment.windowSize = mApplication.window().contentSize(); + environment.contentScale = mApplication.window().contentScale(); + return environment; + } + + void SessionRecorder::start() { + if (mRecording) { + return; + } + + const SessionEnvironment recorded = environment(); + mRecording = std::make_unique(mClock); + mRecording->startMs = mClock(); + mRecording->environment = recorded; + mRecording->snapshot = mSnapshot ? mSnapshot(recorded) : SessionRecording::Saver(); + mRecording->input.setWindowSize(recorded.windowSize); + mRecording->input.startClockNow(); + + Profiler& profiler = mApplication.profiler(); + mRecording->profilerWasEnabled = profiler.isEnabled(); + profiler.clear(); + profiler.setEnabled(true); + mApplication.window().input().setRawInputTap(&mRecording->input); + } + + SessionRecording SessionRecorder::stop() { + SessionRecording session; + if (!mRecording) { + return session; + } + + mApplication.window().input().setRawInputTap(nullptr); + session.environment = std::move(mRecording->environment); + session.input = mRecording->input.recording(); + session.recordedFrames = recordedFramesTsv(mApplication.profiler()); + session.saveSnapshot = std::move(mRecording->snapshot); + restoreProfiler(); + return session; + } + + void SessionRecorder::cancel() { + if (!mRecording) { + return; + } + mApplication.window().input().setRawInputTap(nullptr); + restoreProfiler(); + } + + void SessionRecorder::restoreProfiler() { + Profiler& profiler = mApplication.profiler(); + profiler.setEnabled(mRecording->profilerWasEnabled); + profiler.clear(); + mRecording.reset(); + } + + std::string SessionRecorder::defaultDirectory(const std::string& applicationSessionsDirectory) { + const char* configured = std::getenv("MODULES_SESSION_DIR"); + if (configured && *configured) { + return configured; + } + return applicationSessionsDirectory; + } + + std::string SessionRecorder::recordedFramesTsv(const Profiler& profiler) { + std::string text = "frame\twall_ms\tcpu_ms"; + for (const std::string& name : profiler.sectionNames()) { + text += "\t" + name; + } + for (const std::string& name : profiler.counterNames()) { + text += "\t" + name; + } + text += "\n"; + + char value[32]; + auto append = [&](double number) { + std::snprintf(value, sizeof(value), "\t%.4f", number); + text += value; + }; + for (const FrameProfile& frame : profiler.frames()) { + text += std::to_string(frame.index); + append(frame.wallMs); + append(frame.cpuMs); + for (size_t index = 0; index < profiler.sectionNames().size(); index++) { + append(index < frame.sectionMs.size() ? frame.sectionMs[index] : 0.0); + } + for (size_t index = 0; index < profiler.counterNames().size(); index++) { + append(index < frame.counters.size() ? frame.counters[index] : 0.0); + } + text += "\n"; + } + return text; + } +} diff --git a/GraphicApplication/src/application/public/GraphicApplication.hpp b/GraphicApplication/src/application/public/GraphicApplication.hpp new file mode 100644 index 00000000..8a6db53a --- /dev/null +++ b/GraphicApplication/src/application/public/GraphicApplication.hpp @@ -0,0 +1,97 @@ +#pragma once + +#include "Canvas.hpp" +#include "DebugGUI.hpp" +#include "HangWatchdog.hpp" +#include "Profiler.hpp" +#include "Timing.hpp" +#include "Window.hpp" +#include "WindowDecorations.hpp" + +#include +#include +#include + +namespace tp { + + struct ApplicationOptions { + WindowOptions window = { .identity = WindowIdentity::generic() }; + std::filesystem::path resourceDirectory = Window::platformResourceDirectory(); + DebugInterface debugInterface = DebugInterface::Visible; + }; + + // What an application does each frame. Application drives the frame loop and calls it. + class ApplicationDelegate { + public: + virtual ~ApplicationDelegate() = default; + + [[nodiscard]] virtual bool wantsFrame() { return false; } + virtual void processFrame(const InputState& input, halnf deltaMs) {} + virtual void drawFrame(Canvas& canvas) {} + }; + + class Application { + public: + explicit Application(ApplicationDelegate& delegate, const ApplicationOptions& options = {}); + ~Application(); + + Application(const Application&) = delete; + Application& operator=(const Application&) = delete; + + void run(); + void start(); + void step(halnf deltaMs); + void finish(); + [[nodiscard]] bool isRunning() const { return mRunning; } + + [[nodiscard]] Window& window() { return *mWindow; } + [[nodiscard]] Canvas& canvas() { return *mCanvas; } + [[nodiscard]] DebugGUI& debugGUI() { return *mDebugGUI; } + [[nodiscard]] Profiler& profiler() { return mProfiler; } + [[nodiscard]] GpuDevice& gpu() { return *mGpu; } + + void setHangSettings(HangSettings settings); + void addHangReportFileSource(HangWatchdog::ReportFileSource reportFileSource); + [[nodiscard]] HangWatchdog* hangWatchdog() { return mHangWatchdog.get(); } + + [[nodiscard]] halnf framesProcessedPerSecond() const { return mFramesProcessedPerSecond; } + [[nodiscard]] halnf framesDrawnPerSecond() const { return mFramesDrawnPerSecond; } + void showFrameRates(); + + private: + void enterPhase(UiPhase phase); + [[nodiscard]] bool wantsFrame(); + void runFrame(); + void runFrame(halnf deltaMs); + void processPendingInput(halnf deltaMs); + void present(); + void drawFrameContent(); + void updateFrameCounters(); + + ApplicationDelegate& mDelegate; + Profiler mProfiler; + HangSettings mHangSettings; + + bool mRunning = false; + time_ms mPreviousFrameTime = 0; + std::unique_ptr mHangWatchdog; + std::vector mHangReportFileSources; + + Timer mPerSecondTimer; + halnf mFramesProcessedPerSecond = 0; + halnf mFramesDrawnPerSecond = 0; + halnf mFramesProcessed = 0; + halnf mFramesDrawn = 0; + + ProfileId mProcessSection; + ProfileId mPresentSection; + ProfileId mDrawSection; + ProfileId mInputEventsCounter; + + WindowDecorations mDecorations; + std::unique_ptr mGpu; + std::unique_ptr mWindow; + std::unique_ptr mCanvas; + std::unique_ptr mDebugGUI; + }; +} diff --git a/GraphicApplication/src/application/public/SessionRecording.hpp b/GraphicApplication/src/application/public/SessionRecording.hpp new file mode 100644 index 00000000..57e5a85a --- /dev/null +++ b/GraphicApplication/src/application/public/SessionRecording.hpp @@ -0,0 +1,73 @@ +#pragma once + +#include "GraphicApplication.hpp" +#include "InputRecording.hpp" + +#include +#include +#include + +namespace tp { + + struct SessionEnvironment { + std::string revision; + std::string device; + std::string date; + Vec2F windowSize = { 0, 0 }; + halnf contentScale = 1; + std::string application; + + [[nodiscard]] std::string serialize() const; + }; + + // A recorded session: the environment, every raw input event and the profiled frames, saved as one directory. + struct SessionRecording { + using Saver = std::function; + + SessionEnvironment environment; + InputRecording input; + std::string recordedFrames; + Saver saveSnapshot; + + bool save(const std::string& directory) const; + }; + + // Records an application's raw input and frame profile between start() and stop(), for bug reports and replays. + class SessionRecorder { + public: + using Snapshot = std::function; + using Clock = std::function; + + explicit SessionRecorder(Application& application, Clock clock = {}); + ~SessionRecorder(); + + SessionRecorder(const SessionRecorder&) = delete; + SessionRecorder& operator=(const SessionRecorder&) = delete; + + void setSnapshot(Snapshot snapshot) { mSnapshot = std::move(snapshot); } + void setRevision(std::string revision) { mRevision = std::move(revision); } + + [[nodiscard]] bool isRecording() const { return mRecording != nullptr; } + [[nodiscard]] double elapsedMs() const; + + void start(); + [[nodiscard]] SessionRecording stop(); + void cancel(); + + [[nodiscard]] SessionEnvironment environment() const; + + [[nodiscard]] static std::string defaultDirectory(const std::string& applicationSessionsDirectory); + [[nodiscard]] static std::string recordedFramesTsv(const Profiler& profiler); + + private: + struct Recording; + + void restoreProfiler(); + + Application& mApplication; + Clock mClock; + std::string mRevision = "dev"; + Snapshot mSnapshot; + std::unique_ptr mRecording; + }; +} diff --git a/Graphics/CMakeLists.txt b/Graphics/CMakeLists.txt deleted file mode 100644 index 3d4118bd..00000000 --- a/Graphics/CMakeLists.txt +++ /dev/null @@ -1,23 +0,0 @@ -project(Graphics) - -### ---------------------- Externals --------------------- ### -set(BINDINGS_INCLUDE ../Externals/glfw/include ${GLEW_INCLUDE_DIR}) -set(BINDINGS_LIBS glfw Imgui Nanovg ${GLEW_LIB}) - -### ---------------------- Static Library --------------------- ### -file(GLOB SOURCES "./private/*.cpp") -file(GLOB HEADERS "./public/*.hpp") -add_library(${PROJECT_NAME} STATIC ${SOURCES} ${HEADERS}) -target_include_directories(${PROJECT_NAME} PUBLIC ./public/) -target_include_directories(${PROJECT_NAME} PRIVATE ${BINDINGS_INCLUDE}) -target_link_libraries(${PROJECT_NAME} PUBLIC Math) -target_link_libraries(${PROJECT_NAME} PRIVATE ${BINDINGS_LIBS}) - -### -------------------------- Examples -------------------------- ### -add_executable(Example${PROJECT_NAME} ./examples/Example.cpp) -target_link_libraries(Example${PROJECT_NAME} ${PROJECT_NAME} ${BINDINGS_LIBS}) -target_include_directories(Example${PROJECT_NAME} PRIVATE ${BINDINGS_INCLUDE}) - - -file(COPY "../Graphics/rsc" DESTINATION "${CMAKE_BINARY_DIR}/${PROJECT_NAME}/") - diff --git a/Graphics/examples/Example.cpp b/Graphics/examples/Example.cpp deleted file mode 100644 index 39907efb..00000000 --- a/Graphics/examples/Example.cpp +++ /dev/null @@ -1,34 +0,0 @@ - -#include "GraphicApplication.hpp" -#include - - -#include "imgui.h" - -using namespace tp; - -class ExampleApplication : public Application { -public: - ExampleApplication() = default; - - void processFrame(EventHandler* eventHandler, halnf) override { - // example - } - - virtual void drawFrame(Canvas* canvas) override { - glClear(GL_COLOR_BUFFER_BIT); - - ImGui::ShowDemoWindow(); - - drawDebug(); - } - - bool forceNewFrame() override { return false; } - - virtual ~ExampleApplication() = default; -}; - -int main() { - ExampleApplication app; - app.run(); -} diff --git a/Graphics/private/Canvas.cpp b/Graphics/private/Canvas.cpp deleted file mode 100644 index 82a2cc5a..00000000 --- a/Graphics/private/Canvas.cpp +++ /dev/null @@ -1,416 +0,0 @@ -#include "Window.hpp" - -// -------- OpenGL -------- // -#define GLEW_STATIC -#include - -#include "Graphics.hpp" - -// -------- Canvas -------- // -#define NANOVG_GL3_IMPLEMENTATION -#include -#include - -namespace tp { - class Canvas::Context { - public: - NVGcontext* vg {}; - Window* window {}; - }; -} - -using namespace tp; - -Canvas::Canvas(Window* window) { - mContext = new Context(); - mContext->window = window; - - mContext->vg = nvgCreateGL3(NVG_ANTIALIAS | NVG_STENCIL_STROKES); - - if (nvgCreateFont(mContext->vg, "default", "rsc/Font.ttf") == -1) { - ASSERT(!"Cant create NVG font") - } -} - -Canvas::~Canvas() { - nvgDeleteGL3(mContext->vg); - delete mContext; -} - -void Canvas::rect(RectF rec, const RGBA& col, halnf round) { - rec.pos += mOrigin; - - nvgBeginPath(mContext->vg); - - if (round == 0) { - nvgRect(mContext->vg, rec.x, rec.y, rec.z, rec.w); - } else { - nvgRoundedRect(mContext->vg, rec.x, rec.y, rec.z, rec.w, round); - } - - nvgFillColor(mContext->vg, { col.r, col.g, col.b, col.a }); - nvgFill(mContext->vg); -} - -void Canvas::debugCross(RectF rec, const RGBA& col) { - nvgBeginPath(mContext->vg); - - nvgMoveTo(mContext->vg, rec.p1().x, rec.p1().y); - nvgLineTo(mContext->vg, rec.p3().x, rec.p3().y); - - nvgMoveTo(mContext->vg, rec.p2().x, rec.p2().y); - nvgLineTo(mContext->vg, rec.p4().x, rec.p4().y); - - nvgStrokeWidth(mContext->vg, 2); - nvgStrokeColor(mContext->vg, { col.r, col.g, col.b, col.a }); - nvgStroke(mContext->vg); - // nvgFill(mContext->vg); -} - -void Canvas::frame(RectF rec, const RGBA& col, halnf round) { - nvgBeginPath(mContext->vg); - - nvgMoveTo(mContext->vg, rec.p1().x, rec.p1().y); - nvgLineTo(mContext->vg, rec.p2().x, rec.p2().y); - nvgLineTo(mContext->vg, rec.p3().x, rec.p3().y); - nvgLineTo(mContext->vg, rec.p4().x, rec.p4().y); - nvgLineTo(mContext->vg, rec.p1().x, rec.p1().y); - - nvgStrokeWidth(mContext->vg, 2); - nvgStrokeColor(mContext->vg, { col.r, col.g, col.b, col.a }); - nvgStroke(mContext->vg); - // nvgFill(mContext->vg); -} - -void Canvas::line(Vec2F start, Vec2F end, const RGBA& col, halnf thickness) { - start += mOrigin; - end += mOrigin; - - nvgBeginPath(mContext->vg); - nvgFillColor(mContext->vg, { col.r, col.g, col.b, col.a}); - nvgMoveTo(mContext->vg, start.x, start.y); - nvgLineTo(mContext->vg, end.x, end.y); - nvgStrokeWidth(mContext->vg, thickness); - nvgStrokeColor(mContext->vg, { col.r, col.g, col.b, col.a }); - nvgStroke(mContext->vg); -} - -void Canvas::circle(Vec2F pos, halnf size, const RGBA& col) { - pos += mOrigin; - - nvgBeginPath(mContext->vg); - nvgCircle(mContext->vg, pos.x, pos.y, size); - - nvgFillColor(mContext->vg, { col.r, col.g, col.b, col.a }); - nvgFill(mContext->vg); -} - -void Canvas::setOrigin(const Vec2F& origin) { - mOrigin = origin; -} - -void Canvas::pushClamp(const RectF& rec) { - RectF intersection = rec; - if (mScissors.size()) { - mScissors.last().calcIntersection(rec, intersection); - } - nvgScissor(mContext->vg, intersection.x, intersection.y, intersection.z, intersection.w); - mScissors.append(intersection); -} - -void Canvas::popClamp() { - DEBUG_ASSERT(mScissors.size()); - mIsClamping = false; - mScissors.pop(); - if (mScissors.size()) { - const auto& rec = mScissors.last(); - nvgScissor(mContext->vg, rec.x, rec.y, rec.z, rec.w); - } else { - nvgResetScissor(mContext->vg); - } -} - -const RectF& Canvas::getClampedArea() const { - return mScissors.last(); -} - -void Canvas::text( - const char* string, const RectF& aRec, halnf size, Align align, halnf marging, const RGBA& col -) { - - RectF rec = { aRec.x + marging, aRec.y + marging, aRec.z - marging * 2, aRec.w - marging * 2 }; - rec.pos += mOrigin; - - pushClamp(rec); - - nvgFontSize(mContext->vg, size); - nvgFontFace(mContext->vg, "default"); - nvgFillColor(mContext->vg, { col.r, col.g, col.b, col.a }); - - float centerX = rec.x; - float centerY = rec.y; - int alignNVG = 0; - - if (((int1*) &align)[1] == 0x00) { // center x - alignNVG |= NVG_ALIGN_CENTER; - centerX += rec.z * 0.5f; - } else if (((int1*) &align)[1] == 0x01) { // left x - alignNVG |= NVG_ALIGN_LEFT; - } else if (((int1*) &align)[1] == 0x02) { // right x - alignNVG |= NVG_ALIGN_RIGHT; - centerX += rec.z; - } - - if (((int1*) &align)[0] == 0x00) { // center y - alignNVG |= NVG_ALIGN_MIDDLE; - centerY += rec.w * 0.5f; - } else if (((int1*) &align)[0] == 0x01) { // top y - alignNVG |= NVG_ALIGN_TOP; - centerY += rec.w; - } else if (((int1*) &align)[0] == 0x02) { // bottom y - alignNVG |= NVG_ALIGN_BOTTOM; - } - - nvgTextAlign(mContext->vg, alignNVG); - - nvgText(mContext->vg, centerX, centerY, string, nullptr); - - popClamp(); -} - -void Canvas::drawImage(RectF rec, ImageHandle* image, halnf angle, halnf alpha, halnf rounding) { - rec.pos += mOrigin; - - auto imgPaint = nvgImagePattern(mContext->vg, rec.x, rec.y, rec.z, rec.w, angle, image->id, alpha); - nvgBeginPath(mContext->vg); - nvgRoundedRect(mContext->vg, rec.x, rec.y, rec.z, rec.w, rounding); - nvgFillPaint(mContext->vg, imgPaint); - nvgFill(mContext->vg); -} - - Canvas::ImageHandle Canvas::createImageFromTextId(ualni id, Vec2F size) { -#ifdef ENV_OS_ANDROID - return { (ualni) nvglCreateImageFromHandleGLES3(mContext->vg, id, size.x, size.y, 0) }; -#else - //return { (ualni) nvglCreateImageFromHandleGL3(mContext->vg, id, size.x, size.y, 0) }; - return { (ualni) nvglCreateImageFromHandleGL3(mContext->vg, id, size.x, size.y, 0) }; -#endif -} - -void Canvas::updateTextureID(ImageHandle handle, ualni id) { - auto nvgl = (GLNVGcontext*) nvgInternalParams(mContext->vg)->userPtr; - auto glhandle = nvglImageHandleGL3(mContext->vg, handle.id); - - auto tex = glnvg__findTexture(nvgl, handle.id); - tex->tex = id; -} - -void Canvas::deleteImageHandle(ImageHandle image) { - if (image.id) { - nvgDeleteImage(mContext->vg, image.id); - } -} - -void Canvas::drawBegin() { - const auto size = mContext->window->getSize(); - nvgBeginFrame(mContext->vg, size.x, size.y, 1.0); - glViewport(0, 0, size.x, size.y); -} - -void Canvas::drawEnd() { - const auto size = mContext->window->getSize(); - glViewport(0, 0, size.x, size.y); - nvgEndFrame(mContext->vg); -} - - -void Canvas::colorWheel(const RectF& rec, const ColorWheel& colorWheel) { - auto NVG = mContext->vg; - const auto sizeInner = 5.f; - const auto sizeOuter = 7.f; - - float const x = rec.x + mOrigin.x; - float const y = rec.y + mOrigin.y; - float const w = rec.z; - float const h = rec.w; - - const HSV& hsv = colorWheel.color; - float const hue = hsv.h / (NVG_PI * 2); - - int i; - float r0, r1, ax, ay, bx, by, cx, cy, aeps, r; - NVGpaint paint; - - nvgSave(mContext->vg); - - cx = x + w * 0.5f; - cy = y + h * 0.5f; - r1 = (w < h ? w : h) * 0.5f - colorWheel.margin; - r0 = r1 - colorWheel.thickness; - - aeps = 0.5f / r1; // half a pixel arc length in radians (2pi cancels out). - - for (i = 0; i < 6; i++) { - float a0 = (float)i / 6.0f * NVG_PI * 2.0f - aeps; - float a1 = (float)(i + 1.0f) / 6.0f * NVG_PI * 2.0f + aeps; - nvgBeginPath(NVG); - nvgArc(NVG, cx, cy, r0, a0, a1, NVG_CW); - nvgArc(NVG, cx, cy, r1, a1, a0, NVG_CCW); - nvgClosePath(NVG); - ax = cx + cosf(a0) * (r0 + r1) * 0.5f; - ay = cy + sinf(a0) * (r0 + r1) * 0.5f; - bx = cx + cosf(a1) * (r0 + r1) * 0.5f; - by = cy + sinf(a1) * (r0 + r1) * 0.5f; - paint = nvgLinearGradient(NVG, ax, ay, bx, by, nvgHSLA(a0 / (NVG_PI * 2), 1.0f, 0.55f, 255), nvgHSLA(a1 / (NVG_PI * 2), 1.0f, 0.55f, 255)); - nvgFillPaint(NVG, paint); - nvgFill(NVG); - } - - nvgBeginPath(NVG); - nvgCircle(NVG, cx, cy, r0 - 0.5f); - nvgCircle(NVG, cx, cy, r1 + 0.5f); - nvgStrokeColor(NVG, nvgRGBA(0, 0, 0, 64)); - nvgStrokeWidth(NVG, 1.0f); - nvgStroke(NVG); - - // Selector - nvgSave(NVG); - nvgTranslate(NVG, cx, cy); - nvgRotate(NVG, hue * NVG_PI * 2); - - // Marker on - nvgStrokeWidth(NVG, 2.0f); - nvgBeginPath(NVG); - nvgRect(NVG, r0 - 1, -3, r1 - r0 + 2, 6); - nvgStrokeColor(NVG, nvgRGBA(255, 255, 255, 192)); - nvgStroke(NVG); - - paint = nvgBoxGradient(NVG, r0 - 3, -5, r1 - r0 + 6, 10, 2, 4, nvgRGBA(0, 0, 0, 128), nvgRGBA(0, 0, 0, 0)); - nvgBeginPath(NVG); - nvgRect(NVG, r0 - 2 - 10, -4 - 10, r1 - r0 + 4 + 20, 8 + 20); - nvgRect(NVG, r0 - 2, -4, r1 - r0 + 4, 8); - nvgPathWinding(NVG, NVG_HOLE); - nvgFillPaint(NVG, paint); - nvgFill(NVG); - - // Center triangle - r = r0 - 6; - ax = cosf(120.0f / 180.0f * NVG_PI) * r; - ay = sinf(120.0f / 180.0f * NVG_PI) * r; - bx = cosf(-120.0f / 180.0f * NVG_PI) * r; - by = sinf(-120.0f / 180.0f * NVG_PI) * r; - nvgBeginPath(NVG); - nvgMoveTo(NVG, r, 0); - nvgLineTo(NVG, ax, ay); - nvgLineTo(NVG, bx, by); - nvgClosePath(NVG); - paint = nvgLinearGradient(NVG, r, 0, ax, ay, nvgHSLA(hue, 1.0f, 0.5f, 255), nvgRGBA(255, 255, 255, 255)); - nvgFillPaint(NVG, paint); - nvgFill(NVG); - paint = nvgLinearGradient(NVG, (r + ax) * 0.5f, (0 + ay) * 0.5f, bx, by, nvgRGBA(0, 0, 0, 0), nvgRGBA(0, 0, 0, 255)); - nvgFillPaint(NVG, paint); - nvgFill(NVG); - nvgStrokeColor(NVG, nvgRGBA(0, 0, 0, 64)); - nvgStroke(NVG); - - // Select circle on triangle - float yt = hsv.v * hsv.s; - float xt = hsv.v - 0.5 * yt; - ay = sinf(120.0f / 180.0f * NVG_PI) * r * (-1.0f + xt * 2.0f); - ax = cosf(120.0f / 180.0f * NVG_PI) * r * (1.0f - yt * 3.f); - nvgStrokeWidth(NVG, 2.0f); - nvgBeginPath(NVG); - nvgCircle(NVG, ax, ay, sizeInner); - nvgStrokeColor(NVG, nvgRGBA(255, 255, 255, 192)); - nvgStroke(NVG); - - paint = nvgRadialGradient(NVG, ax, ay, 7, 9, nvgRGBA(0, 0, 0, 64), nvgRGBA(0, 0, 0, 0)); - nvgBeginPath(NVG); - nvgRect(NVG, ax - 20, ay - 20, 40, 40); - nvgCircle(NVG, ax, ay, sizeOuter); - nvgPathWinding(NVG, NVG_HOLE); - nvgFillPaint(NVG, paint); - nvgFill(NVG); - - nvgRestore(NVG); - - nvgRestore(NVG); -} - -void Canvas::ColorWheel::fromPoint(const tp::RectF& area, const tp::Vec2F& crs) { - auto wheel_rec = area; - wheel_rec.pos += margin; - wheel_rec.size -= margin * 2; - - auto center = wheel_rec.pos + wheel_rec.size / 2.f; - auto edge = min(wheel_rec.size.x, wheel_rec.size.y); - wheel_rec.pos = center - edge / 2.f; - wheel_rec.size = edge; - - HSV hsv = color; - - auto pos = crs - center; - - auto r = (crs - center).length(); - if (r < edge / 2.f) { - if (r > edge / 2.f - thickness) { - auto angle = halnf(atan2(pos.y, pos.x)); - angle = halnf(angle > 0 ? angle : PI * 2 + angle); - angle = clamp(angle, 0.f, halnf(PI * 2)); - hsv.h = angle; - } - else { - pos.y *= -1; - pos /= (edge / 2.f) - thickness - 10; - - auto angle = PI * 2 - hsv.h - PI / 2; - if (angle < 0) { - angle += PI * 2; - } - - auto sin = halnf(::sin(-angle)); - auto cos = halnf(::cos(-angle)); - auto sv_pos = Vec2F{ pos.x * cos - pos.y * sin, pos.x * sin + pos.y * cos }; - - auto sv_angle = halnf(atan2(sv_pos.x, sv_pos.y)); - sv_angle = halnf(sv_angle > 0 ? sv_angle : PI * 2 + sv_angle); - sv_angle = clamp(sv_angle, 0.f, halnf(PI * 2)); - - auto p1 = Vec2F(0, 1); - auto p2 = Vec2F(::cos(PI / 6), -::sin(PI / 6)); - auto p3 = Vec2F{ -p2.x, p2.y }; - - Vec2F intersection = sv_pos; - if (sv_angle < PI / 3 * 2) { - if (intersectLines2D({ 0, 0 }, sv_pos, p1, p2, &intersection)) { - sv_pos = intersection; - } - } - else if (sv_angle < PI / 3 * 4) { - if (intersectLines2D({ 0, 0 }, sv_pos, p3, p2, &intersection)) { - sv_pos = intersection; - } - } - else { - if (intersectLines2D({ 0, 0 }, sv_pos, p1, p3, &intersection)) { - sv_pos = intersection; - } - } - - sv_pos.y = halnf((sv_pos.y + 1 / 2.f) * 2 / 3.f * 1.0); - sv_pos.x = halnf(sv_pos.x * 2 / sqrt(3) * 1.0); - sv_pos.x = (sv_pos.x + 1.f) / 2; - - sv_pos.x = clamp(sv_pos.x, 0.f, 1.f); - sv_pos.y = clamp(sv_pos.y, 0.f, 1.f); - - auto v = sv_pos.y * 0.5f + sv_pos.x; - auto s = sv_pos.y / v; - - hsv.v = clamp(v, 0.001f, 0.999f); - hsv.s = clamp(s, 0.001f, 0.999f); - } - } - - color = hsv; -} \ No newline at end of file diff --git a/Graphics/private/DebugGUI.cpp b/Graphics/private/DebugGUI.cpp deleted file mode 100644 index 5880db11..00000000 --- a/Graphics/private/DebugGUI.cpp +++ /dev/null @@ -1,107 +0,0 @@ -#include "Window.hpp" -#include "WindowContext.hpp" - -#include "Graphics.hpp" - -// -------- Debug UI -------- // -#include -#include -#include -#include - -#include "implot.h" - -namespace tp { - class DebugGUI::Context { - public: - ImGuiIO* io{}; - ImGuiContext* ctx{}; - }; -} - -using namespace tp; - -DebugGUI::DebugGUI(Window* window) { - mContext = new Context(); - - IMGUI_CHECKVERSION(); - mContext->ctx = ImGui::CreateContext(); - mContext->io = &ImGui::GetIO(); - - // Initialize ImGui with GLFW and OpenGL - ImGui_ImplGlfw_InitForOpenGL(window->getContext()->window, true); - - // ImGui_ImplGlfw_GetBackendData(); - - ImGui_ImplOpenGL3_Init("#version 330"); - - ImGuiIO& io = ImGui::GetIO(); - io.Fonts->AddFontFromFileTTF("rsc/Font.ttf", 20.f); - - io.ConfigInputTrickleEventQueue = false; - - // appearance - io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; - - ImPlot::CreateContext(); - - return; - - auto& colors = ImGui::GetStyle().Colors; - colors[ImGuiCol_WindowBg] = ImVec4{ 0.1f, 0.105f, 0.11f, 1.0f }; - - // Headers - colors[ImGuiCol_Header] = ImVec4{ 0.2f, 0.205f, 0.21f, 1.0f }; - colors[ImGuiCol_HeaderHovered] = ImVec4{ 0.3f, 0.305f, 0.31f, 1.0f }; - colors[ImGuiCol_HeaderActive] = ImVec4{ 0.15f, 0.1505f, 0.151f, 1.0f }; - - // Buttons - colors[ImGuiCol_Button] = ImVec4{ 0.2f, 0.205f, 0.21f, 1.0f }; - colors[ImGuiCol_ButtonHovered] = ImVec4{ 0.3f, 0.305f, 0.31f, 1.0f }; - colors[ImGuiCol_ButtonActive] = ImVec4{ 0.15f, 0.1505f, 0.151f, 1.0f }; - - // Frame BG - colors[ImGuiCol_FrameBg] = ImVec4{ 0.2f, 0.205f, 0.21f, 1.0f }; - colors[ImGuiCol_FrameBgHovered] = ImVec4{ 0.3f, 0.305f, 0.31f, 1.0f }; - colors[ImGuiCol_FrameBgActive] = ImVec4{ 0.15f, 0.1505f, 0.151f, 1.0f }; - - // Tabs - colors[ImGuiCol_Tab] = ImVec4{ 0.15f, 0.1505f, 0.151f, 1.0f }; - colors[ImGuiCol_TabHovered] = ImVec4{ 0.38f, 0.3805f, 0.381f, 1.0f }; - colors[ImGuiCol_TabActive] = ImVec4{ 0.28f, 0.2805f, 0.281f, 1.0f }; - colors[ImGuiCol_TabUnfocused] = ImVec4{ 0.15f, 0.1505f, 0.151f, 1.0f }; - colors[ImGuiCol_TabUnfocusedActive] = ImVec4{ 0.2f, 0.205f, 0.21f, 1.0f }; - - // Title - colors[ImGuiCol_TitleBg] = ImVec4{ 0.15f, 0.1505f, 0.151f, 1.0f }; - colors[ImGuiCol_TitleBgActive] = ImVec4{ 0.15f, 0.1505f, 0.151f, 1.0f }; - colors[ImGuiCol_TitleBgCollapsed] = ImVec4{ 0.15f, 0.1505f, 0.151f, 1.0f }; -} - -DebugGUI::~DebugGUI() { - ImPlot::DestroyContext(); - - ImGui_ImplOpenGL3_Shutdown(); - ImGui_ImplGlfw_Shutdown(); - ImGui::DestroyContext(); - - - delete mContext; -} - -void DebugGUI::procBegin() { -} - -void DebugGUI::procEnd() { -} - -void DebugGUI::drawBegin() { - ImGui_ImplOpenGL3_NewFrame(); - ImGui_ImplGlfw_NewFrame(); - ImGui::NewFrame(); -} - -void DebugGUI::drawEnd() { - ImGui::Render(); - ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); -} \ No newline at end of file diff --git a/Graphics/private/EventHandler.cpp b/Graphics/private/EventHandler.cpp deleted file mode 100644 index 1f49e0db..00000000 --- a/Graphics/private/EventHandler.cpp +++ /dev/null @@ -1,156 +0,0 @@ - -#include "EventHandler.hpp" - -using namespace tp; - -EventHandler::EventHandler() { - mTimerEvent.reset(); -} - -EventHandler::~EventHandler() = default; - -void EventHandler::postEvent(InputID inputID, InputEvent inputEvent) { - mMutex.lock(); - mEventQueue.pushBack({ inputID, inputEvent }); - mMutex.unlock(); -} - -bool EventHandler::isEvents() { - mMutex.lock(); - auto res = mEventQueue.length(); - mMutex.unlock(); - - if (mTimerEvent.isTimeout()) { - mTimerEvent.reset(); - res = true; - } - - return res; -} - -InputState::State transitions[4][4] = { - { InputState::State::NONE, InputState::State::PRESSED, InputState::State::PRESSED, InputState::State::NONE }, - { InputState::State::PRESSED, InputState::State::HOLD, InputState::State::HOLD, InputState::State::HOLD }, - { InputState::State::HOLD, InputState::State::HOLD, InputState::State::HOLD, InputState::State::RELEASED }, - { InputState::State::NONE, InputState::State::NONE, InputState::State::NONE, InputState::State::NONE }, -}; - -bool transitionsReduce[4][4] = { - { true, false, false, true }, - { true, true, true, false }, - { true, true, true, false }, - { true, true, true, true }, -}; - -void EventHandler::processAllEvent() { - mMutex.lock(); - - mPointerPrev = mPointer; - - while (mEventQueue.size()) { - processEventUnguarded(); - } - mMutex.unlock(); -} - -void EventHandler::processEvent() { - mMutex.lock(); - - mPointerPrev = mPointer; - - if (!mEventQueue.size()) { - mMutex.unlock(); - return; - } - - processEventUnguarded(); - - mMutex.unlock(); -} - -void EventHandler::processEventUnguarded() { - - auto firstEvent = &mEventQueue.first(); - - const auto& eventData = firstEvent->second; - const auto& inputId = firstEvent->first; - - switch (eventData.type) { - case InputEvent::Type::MOUSE_POS: - { - mPointerPrev = mPointer; - mPointer = eventData.mouseEvent; - mEventQueue.popFront(); - break; - } - - case InputEvent::Type::BUTTON_ACTION: - { - auto currentState = (int) mInputStates[(int) inputId].mCurrentState; - auto reportedEvent = (int) eventData.buttonAction; - - mInputStates[(int) inputId].mCurrentState = transitions[currentState][reportedEvent]; - - if (transitionsReduce[currentState][reportedEvent]) { - mEventQueue.popFront(); - } - - break; - } - - case InputEvent::Type::SCROLL: - { - if (mScrollDelta == Vec2F { 0, 0 }) { - mScrollDelta = eventData.scrollDelta; - } else { - mEventQueue.popFront(); - mScrollDelta = 0; - } - break; - } - - default: - { - mEventQueue.popFront(); - } - } - - mPointerPressure = mInputStates[(int) InputID::MOUSE1].mCurrentState != InputState::State::NONE; -} - -void EventHandler::setCursorOrigin(const Vec2F& origin) { - mPointerOrigin = origin; -} - -Vec2F EventHandler::getPointer() const { return mPointer - mPointerOrigin; } - -Vec2F EventHandler::getPointerPrev() const { return mPointerPrev - mPointerOrigin; } - -bool EventHandler::isPressed(InputID id) const { - if (!mEnableKeyEvents) return false; - return mInputStates[(int) id].mCurrentState == InputState::State::PRESSED; -} - -bool EventHandler::isReleased(InputID id) const { - if (!mEnableKeyEvents) return false; - return mInputStates[(int) id].mCurrentState == InputState::State::RELEASED; -} - -halnf EventHandler::getPointerPressure() const { - if (!mEnableKeyEvents) return 0; - return mPointerPressure; -} - -bool EventHandler::isDown(InputID id) const { - if (!mEnableKeyEvents) return false; - return mInputStates[(int) id].mCurrentState == InputState::State::PRESSED || - mInputStates[(int) id].mCurrentState == InputState::State::HOLD; -} - -halnf EventHandler::getScrollY() const { return mScrollDelta.y; } - -Vec2F EventHandler::getPointerDelta() const { return mPointer - mPointerPrev; } - -void EventHandler::setEnableKeyEvents(bool enable) { - mEnableKeyEvents = enable; -} \ No newline at end of file diff --git a/Graphics/private/GraphicApplication.cpp b/Graphics/private/GraphicApplication.cpp deleted file mode 100644 index e59ddc78..00000000 --- a/Graphics/private/GraphicApplication.cpp +++ /dev/null @@ -1,146 +0,0 @@ - -#include "GraphicApplication.hpp" - -#include "imgui.h" - -using namespace tp; - -Application::Application() { - mWindow = Window::createWindow({1500, 900}); - mGraphics = new Graphics(mWindow); - - mDrawTimer.setDuration(1000.f / mDrawPerSecond); - mProcTimer.setDuration(1000.f / mProcPerSecond); - mPerSecondTimer.setDuration(1000.f); -} - -void Application::runDefaultLoop() { - auto eventHandler = new EventHandler(); - - mWindow->setEventHandler(eventHandler); - - mDrawTimer.reset(); - mProcTimer.reset(); - mPerSecondTimer.reset(); - - bool redrawNeeded = false; - - // proc first frame by default - { - mWindow->processEvents(false); - processFrame(eventHandler, 0); - - mGraphics->drawBegin(); - drawFrame(mGraphics->getCanvas()); - mGraphics->drawEnd(); - mWindow->draw(); - } - - time_ms prevProcTime = get_time(); - - bool isForcedNewFrame = true; - - while (!mWindow->shouldClose()) { - mWindow->processEvents(!isForcedNewFrame); - updateGlobalTime(); - - if (mProcTimer.isTimeout() || eventHandler->isEvents() || isForcedNewFrame) { - - while ((eventHandler->isEvents() || isForcedNewFrame)) { - - eventHandler->processEvent(); - - time_ms currentTime = get_time(); - processFrame(eventHandler, halnf(currentTime - prevProcTime)); - prevProcTime = currentTime; - - redrawNeeded = true; - mFramesProcessed++; - - isForcedNewFrame = forceNewFrame(); - - if (isForcedNewFrame) { - // mWindow->processEvents(); - break; - } - } - - mProcTimer.wait(); - mProcTimer.reset(); - } - - if (mDrawTimer.isTimeout()) { - mDrawTimer.reset(); - - if (redrawNeeded) { - mGraphics->drawBegin(); - - drawFrame(mGraphics->getCanvas()); - - mGraphics->drawEnd(); - - mWindow->draw(); - - redrawNeeded = false; - mFramesDrawn++; - } - } - - if (mPerSecondTimer.isTimeout()) { - mPerSecondTimer.reset(); - - mFramesProcessedPerSecond = mFramesProcessed; - mFramesDrawnPerSecond = mFramesDrawn; - - mFramesDrawn = 0; - mFramesProcessed = 0; - } - } - - delete eventHandler; -} - -void Application::runDebugLoop() { - auto eventHandler = new EventHandler(); - mWindow->setEventHandler(eventHandler); - - time_ms prevProcTime = get_time(); - - while (!mWindow->shouldClose()) { - mWindow->processEvents(); - eventHandler->processAllEvent(); - time_ms currentTime = get_time(); - processFrame(eventHandler, halnf(currentTime - prevProcTime)); - prevProcTime = currentTime; - - - mGraphics->drawBegin(); - drawFrame(mGraphics->getCanvas()); - mGraphics->drawEnd(); - mWindow->draw(); - } - - delete eventHandler; -} - -void Application::run() { - runDefaultLoop(); - // runDebugLoop(); -} - -void Application::processFrame(EventHandler* eventHandler, halnf deltaTime) {} - -void Application::drawFrame(Canvas* canvas) { - // ImGui::Text("Frames processed per second: %f", mFramesProcessedPerSecond); - // ImGui::Text("Frames drawn per second: %f", mFramesDrawnPerSecond); -} - -void Application::drawDebug() { - ImGui::Text("Frames processed per second: %f", this->mFramesProcessedPerSecond); - ImGui::Text("Frames drawn per second: %f", this->mFramesDrawnPerSecond); -} - -Application::~Application() { - delete mGraphics; - Window::destroyWindow(mWindow); -} \ No newline at end of file diff --git a/Graphics/private/Window.cpp b/Graphics/private/Window.cpp deleted file mode 100644 index ccaa39b3..00000000 --- a/Graphics/private/Window.cpp +++ /dev/null @@ -1,190 +0,0 @@ - -#include "Window.hpp" -#include "WindowContext.hpp" - -#include "Rect.hpp" - -#include - -#ifdef ENV_OS_WINDOWS -#define GLFW_EXPOSE_NATIVE_WIN32 -#endif - -#ifdef ENV_OS_LINUX -#define GLFW_EXPOSE_NATIVE_WAYLAND -#endif - -#include -#include - -using namespace tp; - -static void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods); -static void mouseButtonCallback(GLFWwindow* window, int button, int action, int mods); -static void scrollCallback(GLFWwindow* window, double xOffset, double yOffset); - -Window::Window(Vec2F size, const char* title) { - mContext = new Context(); - - // glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, 1); - // glfwWindowHint(GLFW_DECORATED, GLFW_FALSE); - - mSize = size; - - // Create a window and OpenGL context - mContext->window = glfwCreateWindow((int) size.x, (int) size.y, title, nullptr, nullptr); - if (!mContext->window) { - printf("Failed to create GLFW window\n"); - return; - } - - glfwMakeContextCurrent(mContext->window); - - // Initialize GLEW - if (glewInit() != GLEW_OK) { - printf("Failed to initialize GLEW\n"); - exit(1); - return; - } - - // set event bindings - glfwSetWindowUserPointer(mContext->window, this); - glfwSetKeyCallback(mContext->window, keyCallback); - glfwSetMouseButtonCallback(mContext->window, mouseButtonCallback); - glfwSetScrollCallback(mContext->window, scrollCallback); -} - -Window::~Window() { - glfwDestroyWindow(mContext->window); - delete mContext; -} - -Window* Window::createWindow(Vec2F size, const char* title) { - static int count = 1; - if (!count) { - printf("Window class is a singleton\n"); - return nullptr; - } - count--; - - #ifdef ENV_OS_LINUX - glfwInitHint(GLFW_PLATFORM, GLFW_PLATFORM_X11); - #endif - // glfwInitHint(GLFW_WAYLAND_LIBDECOR, GLFW_WAYLAND_DISABLE_LIBDECOR); - - // Initialize GLFW - if (!glfwInit()) { - printf("Failed to initialize GLFW\n"); - return nullptr; - } - - // Set the GLFW error callback - glfwSetErrorCallback([](int error, const char* description) { - printf("GLFW Error: %i %s\n", error, description); - }); - - auto out = new Window(size, title); - - return out; -} - -void Window::destroyWindow(Window* window) { - delete window; - glfwTerminate(); -} - -bool Window::shouldClose() const { return glfwWindowShouldClose(mContext->window); } - -void Window::processEvents(bool wait) { - if (wait) glfwWaitEvents(); - else glfwPollEvents(); - checkAxisUpdates(); -} - -void Window::draw() { - glfwSwapBuffers(mContext->window); -} - -const Vec2F& Window::getSize() const { return mSize; } - -Window::Context* Window::getContext() { return mContext; } - -void Window::setEventHandler(EventHandler* eventHandler) { mEventHandler = eventHandler; } -EventHandler* Window::getEventHandler() { return mEventHandler; } - - -void Window::checkAxisUpdates() { - if (!mEventHandler) return; - - int w, h; - glfwGetWindowSize(mContext->window, &w, &h); - - if (mSize != Vec2F((halnf) w, (halnf) h)) { - mSize = { (halnf) w, (halnf) h }; - mEventHandler->postEvent(InputID::WINDOW_RESIZE, {}); - } - - double x, y; - glfwGetCursorPos(mContext->window, &x, &y); - - // int posX, posY; - // glfwGetWindowPos(mContext->window, &posX, &posY); - - if (mPointerPos != Vec2F{ (halnf) x, (halnf) y }) { - mPointerPos = { (halnf) x, (halnf) y }; - - // auto pos = Vec2F{ (halnf) posX, (halnf) posY }; - RectF windowRec = { { 0, 0 }, mSize }; - - if (windowRec.isInside(mPointerPos)) { - mEventHandler->postEvent(InputID::MOUSE1, { InputEvent::Type::MOUSE_POS, {}, mPointerPos }); - } - } -} - -static void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) { - - auto* self = static_cast(glfwGetWindowUserPointer(window)); - if (!self) return; - - EventHandler* eventHandler = self->getEventHandler(); - if (!eventHandler) return; - - self->checkAxisUpdates(); - - if (action == GLFW_PRESS) { - eventHandler->postEvent((InputID) key, { InputEvent::Type::BUTTON_ACTION, InputEvent::ButtonAction::PRESS, {} }); - } else if (action == GLFW_RELEASE) { - eventHandler->postEvent((InputID) key, { InputEvent::Type::BUTTON_ACTION, InputEvent::ButtonAction::RELEASE, {} }); - } -} - -static void mouseButtonCallback(GLFWwindow* window, int button, int action, int mods) { - auto* self = static_cast(glfwGetWindowUserPointer(window)); - if (!self) return; - - EventHandler* eventHandler = self->getEventHandler(); - if (!eventHandler) return; - - self->checkAxisUpdates(); - - auto id = (InputID) ((int) InputID::MOUSE1 + button); - - if (action == GLFW_PRESS) { - // printf("mouse\n"); - eventHandler->postEvent(id, { InputEvent::Type::BUTTON_ACTION, InputEvent::ButtonAction::PRESS, {} }); - } else if (action == GLFW_RELEASE) { - eventHandler->postEvent(id, { InputEvent::Type::BUTTON_ACTION, InputEvent::ButtonAction::RELEASE, {} }); - } - -} - -static void scrollCallback(GLFWwindow* window, double xOffset, double yOffset) { - auto* self = static_cast(glfwGetWindowUserPointer(window)); - if (!self) return; - - EventHandler* eventHandler = self->getEventHandler(); - if (!eventHandler) return; - - eventHandler->postEvent(InputID::SCROLL, { InputEvent::Type::SCROLL, {}, {}, { xOffset, yOffset } }); -} \ No newline at end of file diff --git a/Graphics/private/WindowContext.hpp b/Graphics/private/WindowContext.hpp deleted file mode 100644 index c237297b..00000000 --- a/Graphics/private/WindowContext.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include "Window.hpp" - -struct GLFWwindow; - -namespace tp { - - class Window::Context { - public: - Context() = default; - GLFWwindow* window = nullptr; - }; -} \ No newline at end of file diff --git a/Graphics/public/EventHandler.hpp b/Graphics/public/EventHandler.hpp deleted file mode 100644 index c62bd896..00000000 --- a/Graphics/public/EventHandler.hpp +++ /dev/null @@ -1,116 +0,0 @@ -#pragma once - -#include "InputCodes.hpp" -#include "Vec.hpp" -#include "List.hpp" -#include "Map.hpp" -#include "Timing.hpp" - -#include - -namespace tp { - - struct InputEvent { - enum class Type { - NONE = 0, - BUTTON_ACTION, - MOUSE_DELTA, - MOUSE_POS, - SCROLL, - } type = Type::NONE; - - enum class ButtonAction { - NONE = 0, - PRESS, - HOLD, - RELEASE, - REPEAT, - } buttonAction = ButtonAction::NONE; - - Vec2F mouseEvent = { 0, 0 }; - Vec2F scrollDelta = { 0, 0 }; - }; - - class InputState { - public: - InputState() = default; - - void handleEvent(const InputEvent& event); - - bool isStateChanged(); - - // true if event is no longer needed and false if we need to emulate some transitions further - bool isHandled(); - - public: - // Transitions from state to state is strict and defined by state order in the enum - // If posted event conflicts with that automata, we need to emulate intermediate states - enum State { - NONE = 0, // Button is inactive - PRESSED, // Button is pressed - HOLD, // Button is still pressed - RELEASED, // Button is released - }; - - State mCurrentState; - State mPreviousState; - }; - - // Assumes that user and event poster use different threads - // User can add own states for each input and decide how this state changes on specific events - // Event posters has no access to any custom state and only report any changes in any way - class EventHandler { - public: - EventHandler(); - ~EventHandler(); - - public: // Event Poster Interface - // Record event - void postEvent(InputID inputID, InputEvent inputEvent); - - public: // User interface - bool isEvents(); - void processEvent(); - void processAllEvent(); - - void setCursorOrigin(const Vec2F& origin); - - [[nodiscard]] Vec2F getPointer() const; - [[nodiscard]] Vec2F getPointerPrev() const; - [[nodiscard]] Vec2F getPointerDelta() const; - - [[nodiscard]] bool isPressed(InputID id) const; - [[nodiscard]] bool isReleased(InputID id) const; - [[nodiscard]] bool isDown(InputID id) const; - [[nodiscard]] halnf getScrollY() const; - - [[nodiscard]] halnf getPointerPressure() const; - - void setEnableKeyEvents(bool); - - private: - void processEventUnguarded(); - - private: - std::mutex mMutex = {}; - - // Store thread protected queue of posted events - List> mEventQueue; - - // input states - Vec2F mPointerOrigin = { 0, 0 }; - - Vec2F mPointer; - Vec2F mPointerPrev; - Vec2F mScrollDelta; - - halnf mPointerPressure = 0; - - InputState mInputStates[(int) InputID::LAST_KEY_CODE]{}; - - Timer mTimerEvent = Timer(1000); - - bool mEnableKeyEvents = true; - }; - -} \ No newline at end of file diff --git a/Graphics/public/GraphicApplication.hpp b/Graphics/public/GraphicApplication.hpp deleted file mode 100644 index 253314d0..00000000 --- a/Graphics/public/GraphicApplication.hpp +++ /dev/null @@ -1,47 +0,0 @@ - -#pragma once - -#include "Window.hpp" -#include "Graphics.hpp" -#include "Timing.hpp" - -namespace tp { - class Application { - public: - Application(); - - void run(); - - virtual bool forceNewFrame() { return false; } - - virtual void processFrame(EventHandler* eventHandler, halnf deltaTime); - virtual void drawFrame(Canvas* canvas); - - virtual ~Application(); - - void drawDebug(); - - private: - void runDefaultLoop(); - void runDebugLoop(); - - protected: - bool mInitialized = false; - - ualni mDrawPerSecond = 1160; - ualni mProcPerSecond = 1300; - - Timer mDrawTimer; - Timer mProcTimer; - Timer mPerSecondTimer; - - halnf mFramesProcessedPerSecond = 0; - halnf mFramesDrawnPerSecond = 0; - - halnf mFramesProcessed = 0; - halnf mFramesDrawn = 0; - - Graphics* mGraphics = nullptr; - Window* mWindow = nullptr; - }; -} \ No newline at end of file diff --git a/Graphics/public/Graphics.hpp b/Graphics/public/Graphics.hpp deleted file mode 100644 index afbc3256..00000000 --- a/Graphics/public/Graphics.hpp +++ /dev/null @@ -1,124 +0,0 @@ -#pragma once - -#include "Window.hpp" - -#include "Color.hpp" -#include "Rect.hpp" - -#include "Buffer.hpp" - -namespace tp { - - class DebugGUI { - class Context; - Context* mContext; - - public: - explicit DebugGUI(Window* window); - ~DebugGUI(); - - void procBegin(); - void procEnd(); - - void drawBegin(); - void drawEnd(); - }; - - class Canvas { - class Context; - Context* mContext; - - public: - explicit Canvas(Window* window); - ~Canvas(); - - void procBegin() {} - void procEnd() {} - - void drawBegin(); - void drawEnd(); - - public: - enum Align : int2 { - CC = 0x0000, - CT = 0x0001, - CB = 0x0002, - LC = 0x0100, - LT = 0x0101, - LB = 0x0102, - RC = 0x0200, - RT = 0x0201, - RB = 0x0202, - }; - - struct ImageHandle { - ualni id = 0; - }; - - struct ColorWheel { - HSV color = { 0.13, 0.5, 1 }; - halnf thickness = 20; - halnf margin = 5; - - void fromPoint(const RectF& area, const Vec2F& point); - }; - - void setOrigin(const Vec2F& origin); - - void pushClamp(const RectF& rec); - void popClamp(); - [[nodiscard]] const RectF& getClampedArea() const; - - void debugCross(RectF rec, const RGBA& col); - void rect(RectF rec, const RGBA& col, halnf round = 0); - void frame(RectF rec, const RGBA& col, halnf round = 0); - void circle(Vec2F pos, halnf size, const RGBA& col); - void text(const char*, const RectF&, halnf size, Align, halnf padding, const RGBA&); - void colorWheel(const RectF& rec, const ColorWheel& colorWheel); - void line(Vec2F start, Vec2F end, const RGBA& col, halnf thickness); - - ImageHandle createImageFromTextId(ualni id, Vec2F size); - void updateTextureID(ImageHandle handle, ualni id); - - void deleteImageHandle(ImageHandle image); - void drawImage(RectF rec, ImageHandle* image, halnf angle = 0, halnf alpha = 1.f, halnf rounding = 0.f); - - private: - Buffer mScissors; - bool mIsClamping = false; - Vec2F mOrigin = { 0, 0 }; - }; - - class Graphics { - public: - explicit Graphics(Window* window) : - mGui(window), - mCanvas(window) {} - - void procBegin() { - mCanvas.procBegin(); - mGui.procBegin(); - } - - void procEnd() { - mCanvas.procEnd(); - mGui.procEnd(); - } - - void drawBegin() { - mCanvas.drawBegin(); - mGui.drawBegin(); - } - - void drawEnd() { - mCanvas.drawEnd(); - mGui.drawEnd(); - } - - Canvas* getCanvas() { return &mCanvas; } - - private: - Canvas mCanvas; - DebugGUI mGui; - }; -} \ No newline at end of file diff --git a/Graphics/public/InputCodes.hpp b/Graphics/public/InputCodes.hpp deleted file mode 100644 index 1cc8f401..00000000 --- a/Graphics/public/InputCodes.hpp +++ /dev/null @@ -1,152 +0,0 @@ - -#pragma once - -#include "Environment.hpp" - -namespace tp { - enum class InputID : ualni { - /* Printable keys */ - SPACE = 32, - APOSTROPHE = 39, /* ' */ - COMMA = 44, /* , */ - MINUS = 45, /* - */ - PERIOD = 46, /* . */ - SLASH = 0xBF, /* \ */ - N0 = 48, - N1 = 49, - N2 = 50, - N3 = 51, - N4 = 52, - N5 = 53, - N6 = 54, - N7 = 55, - N8 = 56, - N9 = 57, - SEMICOLON = 59, /* ; */ - EQUAL = 61, /* = */ - A = 65, - B = 66, - C = 67, - D = 68, - E = 69, - F = 70, - G = 71, - H = 72, - I = 73, - J = 74, - K = 75, - L = 76, - M = 77, - N = 78, - O = 79, - P = 80, - Q = 81, - R = 82, - S = 83, - T = 84, - U = 85, - V = 86, - W = 87, - X = 88, - Y = 89, - Z = 90, - LEFT_BRACKET = 91, /* [ */ - BACKSLASH = 92, /* \ */ - RIGHT_BRACKET = 93, /* ] */ - GRAVE_ACCENT = 96, /* ` */ - WORLD_1 = 161, /* non-US #1 */ - WORLD_2 = 162, /* non-US #2 */ - - /* function keys */ - - ESCAPE = 256, - ENTER = 257, - TAB = 258, - BACKSPACE = 259, - INSERT = 260, - DELETE_KEY = 261, - RIGHT = 262, - LEFT = 263, - DOWN = 264, - UP = 265, - PAGE_UP = 266, - PAGE_DOWN = 267, - HOME = 268, - END = 269, - CAPS_LOCK = 280, - SCROLL_LOCK = 281, - NUM_LOCK = 282, - PRINT_SCREEN = 283, - PAUSE = 284, - F1 = 290, - F2 = 291, - F3 = 292, - F4 = 293, - F5 = 294, - F6 = 295, - F7 = 296, - F8 = 297, - F9 = 298, - F10 = 299, - F11 = 300, - F12 = 301, - F13 = 302, - F14 = 303, - F15 = 304, - F16 = 305, - F17 = 306, - F18 = 307, - F19 = 308, - F20 = 309, - F21 = 310, - F22 = 311, - F23 = 312, - F24 = 313, - F25 = 314, - KP_0 = 320, - KP_1 = 321, - KP_2 = 322, - KP_3 = 323, - KP_4 = 324, - KP_5 = 325, - KP_6 = 326, - KP_7 = 327, - KP_8 = 328, - KP_9 = 329, - KP_DECIMAL = 330, - KP_DIVIDE = 331, - KP_MULTIPLY = 332, - KP_SUBTRACT = 333, - KP_ADD = 334, - KP_ENTER = 335, - KP_EQUAL = 336, - LEFT_SHIFT = 340, - LEFT_CONTROL = 341, - LEFT_ALT = 342, - LEFT_SUPER = 343, - RIGHT_SHIFT = 344, - RIGHT_CONTROL = 345, - RIGHT_ALT = 346, - RIGHT_SUPER = 347, - MENU = 348, - - INV_SLASH = 0xDC, - BRA = 0xDB, - KET = 0xDD, - TILDA = 0xC0, - DOUBLE_DOT = 0xBA, - QUOTES = 0xDE, - - MOUSE1 = 501, - MOUSE2 = 502, - MOUSE3 = 503, - MOUSE4 = 504, - MOUSE5 = 505, - - SCROLL, - - WINDOW_RESIZE, - - LAST_KEY_CODE, - }; -} \ No newline at end of file diff --git a/Graphics/public/Window.hpp b/Graphics/public/Window.hpp deleted file mode 100644 index c37d6563..00000000 --- a/Graphics/public/Window.hpp +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once - -#include "Buffer.hpp" -#include "EventHandler.hpp" - -namespace tp { - class Window { - class Context; - - private: - Window(Vec2F size, const char* title); - ~Window(); - - public: - static Window* createWindow(Vec2F size = { 1000.f, 700.f }, const char* title = "Window"); - static void destroyWindow(Window* window); - - public: - void draw(); - void processEvents(bool wait = true); - - void setEventHandler(EventHandler* eventHandler); - [[nodiscard]] EventHandler* getEventHandler(); - - Context* getContext(); - [[nodiscard]] bool shouldClose() const; - [[nodiscard]] const Vec2F& getSize() const; - - void checkAxisUpdates(); - - private: - Vec2F mSize; - Vec2F mPointerPos; - - Context* mContext; - EventHandler* mEventHandler = nullptr; - }; -} \ No newline at end of file diff --git a/Graphics/rsc/Font.ttf b/Graphics/rsc/Font.ttf deleted file mode 100644 index 8a630544c99ec9956153ebd9509abeac421c6d75..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 42624 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 + class DeviceStream { friend class Player; diff --git a/LibraryViewer/public/GUI.hpp b/LibraryViewer/public/GUI.hpp index 10a1ad76..8e8e93a4 100644 --- a/LibraryViewer/public/GUI.hpp +++ b/LibraryViewer/public/GUI.hpp @@ -23,7 +23,7 @@ namespace tp { getLayout()->setMinSize({ 70, 70 }); }; - void process(const EventHandler& events) override { + void process(const InputView& events) override { mState = IDLE; if (!mTrack) { @@ -35,7 +35,7 @@ namespace tp { case HOVER: case IDLE: mState = getArea().relative().isInside(events.getPointer()) ? HOVER : IDLE; - if (events.isPressed(InputID::MOUSE1)) { + if (events.isPressed(PointerButton::Left)) { mState = SELECTED; } break; @@ -68,8 +68,8 @@ namespace tp { const RectF textAreaName = { textArea.x, textArea.y, textArea.z, textArea.w * 0.5f }; const RectF textAreaAuthor = { textArea.x, textArea.y + textArea.w * 0.5f, textArea.z, textArea.w * 0.5f }; - canvas.text(mTrack->mName.c_str(), textAreaName, 15.f, Canvas::LC, rounding, colorNameTrack); - canvas.text(mTrack->mArtist.c_str(), textAreaAuthor, 12.f, Canvas::LC, rounding, colorNameArtist); + canvas.text(mTrack->mName.c_str(), textAreaName, { .size = 15.f, .color = colorNameTrack, .padding = rounding }); + canvas.text(mTrack->mArtist.c_str(), textAreaAuthor, { .size = 12.f, .color = colorNameArtist, .padding = rounding }); } [[nodiscard]] bool processesEvents() const override { return true; } @@ -245,7 +245,7 @@ namespace tp { } } - void process(const EventHandler& events) override { + void process(const InputView& events) override { filter(); for (auto track : mSongList.getContainer()->getChildren()) { diff --git a/LibraryViewer/public/Library.hpp b/LibraryViewer/public/Library.hpp index 7b34d429..d2472382 100644 --- a/LibraryViewer/public/Library.hpp +++ b/LibraryViewer/public/Library.hpp @@ -1,5 +1,6 @@ #pragma once +#include "Buffer.hpp" #include "Window.hpp" typedef tp::ualni SongId; diff --git a/Math/public/Vec.hpp b/Math/public/Vec.hpp index 0729046b..dd674ce1 100644 --- a/Math/public/Vec.hpp +++ b/Math/public/Vec.hpp @@ -375,6 +375,14 @@ namespace tp { return sqrt(tmp); } + [[nodiscard]] halnf angle() const { return (halnf) atan2(y, x); } + + [[nodiscard]] Vec rotated(halnf angle) const { + const halnf cosine = (halnf) cos(angle); + const halnf sine = (halnf) sin(angle); + return { (Type) (x * cosine - y * sine), (Type) (x * sine + y * cosine) }; + } + // Vector Transformation void Rot(float cosA, float sinA) { Type tmp = x; diff --git a/Math/tests/Tests.cpp b/Math/tests/Tests.cpp index 5625baec..b3a70deb 100644 --- a/Math/tests/Tests.cpp +++ b/Math/tests/Tests.cpp @@ -101,3 +101,18 @@ SUITE(DisplayGamma) { CHECK_EQUAL(1.0f, linearToGamma(2)); } } + +SUITE(Vec2) { + TEST(AngleIsMeasuredFromThePositiveXAxis) { + CHECK_CLOSE(0.0f, Vec2F(2, 0).angle(), 1e-6f); + CHECK_CLOSE(1.5707963f, Vec2F(0, 3).angle(), 1e-6f); + CHECK_CLOSE(-1.5707963f, Vec2F(0, -1).angle(), 1e-6f); + } + + TEST(RotatedTurnsCounterClockwiseAndKeepsLength) { + const Vec2F turned = Vec2F(1, 0).rotated(1.5707963f); + CHECK_CLOSE(0.0f, turned.x, 1e-6f); + CHECK_CLOSE(1.0f, turned.y, 1e-6f); + CHECK_CLOSE(Vec2F(3, 4).length(), Vec2F(3, 4).rotated(0.7f).length(), 1e-5f); + } +} diff --git a/Objects/CMakeLists.txt b/Objects/CMakeLists.txt index d3e0d91a..d377f040 100644 --- a/Objects/CMakeLists.txt +++ b/Objects/CMakeLists.txt @@ -24,9 +24,9 @@ add_executable(osg ./applications/GUI.cpp ./applications/GUIEntry.cpp) #target_link_libraries(osc ${PROJECT_NAME}) #target_link_libraries(osi ${PROJECT_NAME}) -target_link_libraries(osg ${PROJECT_NAME} Graphics Imgui) +target_link_libraries(osg ${PROJECT_NAME} GraphicApplication Imgui) +graphic_application_resources(osg) -file(COPY "rsc/Font.ttf" DESTINATION "${CMAKE_BINARY_DIR}/${PROJECT_NAME}/") ### -------------------------- Tests -------------------------- ### file(GLOB TEST_SOURCES "./tests/*.cpp" "./tests/*/*.cpp") diff --git a/Objects/applications/GUIEntry.cpp b/Objects/applications/GUIEntry.cpp index 8eef964f..30cb37a4 100644 --- a/Objects/applications/GUIEntry.cpp +++ b/Objects/applications/GUIEntry.cpp @@ -6,19 +6,23 @@ using namespace tp; using namespace obj; -class SimpleGUI : public Application { +class SimpleGUI : public ApplicationDelegate { public: - SimpleGUI() { gui.cd(objects_api::create(), "root"); } + SimpleGUI() : + mApplication(*this) { + gui.cd(objects_api::create(), "root"); + } - void processFrame(EventHandler* eventHandler, halnf delta) override {} + void run() { mApplication.run(); } - void drawFrame(Canvas* canvas) override { - canvas->rect({ { 0, 0 }, mWindow->getSize() }, RGBA(0.f, 0.f, 0.f, 1.f), 0); + void drawFrame(Canvas& canvas) override { + canvas.rect({ { 0, 0 }, mApplication.window().contentSize() }, RGBA(0.f, 0.f, 0.f, 1.f), 0); gui.draw(); } private: ObjectsGUI gui; + Application mApplication; }; int main() { diff --git a/Objects/rsc/Font.ttf b/Objects/rsc/Font.ttf deleted file mode 100644 index 8a630544c99ec9956153ebd9509abeac421c6d75..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 42624 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, { 2560, 1440 }); + mGui.createRenderWidget(&canvas(), { 2560, 1440 }); mGui.setProject(&mSketch); } diff --git a/Sketch3D/applications/Font.ttf b/Sketch3D/applications/Font.ttf deleted file mode 100644 index 8a630544c99ec9956153ebd9509abeac421c6d75..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 42624 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, mRenderer.getBuff()->getSize()); + mImage = canvas.createImageFromRenderTarget(mRenderer.getBuff()->texId()); mCanvas = &canvas; setSizePolicy(SizePolicy::Expanding, SizePolicy::Expanding); @@ -13,13 +13,13 @@ SketchRenderWidget::SketchRenderWidget(Canvas& canvas, Vec2F renderResolution) : setDebug("render", {}); } -SketchRenderWidget::~SketchRenderWidget() { mCanvas->deleteImageHandle(mImage); } +SketchRenderWidget::~SketchRenderWidget() { mCanvas->deleteImage(mImage); } void SketchRenderWidget::draw(Canvas& canvas) { auto area = getArea().relative(); if (mProject) { mRenderer.renderToTexture(mProject, area.size); - canvas.drawImage(area, &mImage, 0, 1, 12); + canvas.drawImage(area, mImage, 0, 1, 12); } } @@ -36,7 +36,7 @@ void SketchViewportWidget::setColor(const RGBA& color) { void SketchViewportWidget::setRenderWidget(SketchRenderWidget* widget) { addChild(widget); } -void SketchViewportWidget::process(const EventHandler& events) { +void SketchViewportWidget::process(const InputView& events) { if (!mProject) return; auto area = getArea().relative(); @@ -49,11 +49,11 @@ void SketchViewportWidget::process(const EventHandler& events) { crs.y /= area.w; crs = (crs - 0.5) * 2; - if (events.isPressed(InputID::MOUSE1)) { + if (events.isPressed(PointerButton::Left)) { mAction = true; lockFocus(); - } else if (events.isReleased(InputID::MOUSE1)) { + } else if (events.isReleased(PointerButton::Left)) { mAction = false; freeFocus(); } @@ -65,11 +65,11 @@ void SketchViewportWidget::process(const EventHandler& events) { Vec2F relativePosPrev = ((mActionPosAbsolutePrev / area.size) - 0.5f) * 2.f; Vec2F relativeDelta = relativePos - relativePosPrev; - if (events.isDown(InputID::LEFT_SHIFT)) { + if (events.isDown(Key::LeftShift)) { mProject->mCamera.move(relativePos, relativePosPrev); - } else if (events.isDown(InputID::LEFT_ALT)) { + } else if (events.isDown(Key::LeftAlt)) { mProject->mCamera.rotate(-relativeDelta.x * halnf(PI), -relativeDelta.y * halnf(PI)); - } else if (events.isDown(InputID::LEFT_CONTROL)) { + } else if (events.isDown(Key::LeftControl)) { halnf factor = pointer.y / mActionPosAbsolutePrev.y; mProject->mCamera.zoom(factor); } else { @@ -170,14 +170,14 @@ void SketchGUI::setProject(Project* project) { SketchGUI::~SketchGUI() { delete mRenderWidget; } -void SketchGUI::process(const EventHandler& events) { +void SketchGUI::process(const InputView& events) { DockWidget::process(events); if (!mProject) return; auto pencil = dynamic_cast(mProject->mBrushes.get("pencil")); - pencil->mCol = mColorPicker.mColorWheel.color; + pencil->mCol = mColorPicker.color(); pencil->mSize = mBrushSizeSlider.val() * 0.2f + 0.001f; } diff --git a/Sketch3D/public/SketchGUI.hpp b/Sketch3D/public/SketchGUI.hpp index f4770b2d..b232a8e2 100644 --- a/Sketch3D/public/SketchGUI.hpp +++ b/Sketch3D/public/SketchGUI.hpp @@ -19,7 +19,7 @@ namespace tp { private: Renderer mRenderer; Project* mProject = nullptr; - Canvas::ImageHandle mImage; + ImageHandle mImage; Canvas* mCanvas = nullptr; }; @@ -33,7 +33,7 @@ namespace tp { void setRenderWidget(SketchRenderWidget* widget); void setToolbarWidget(Widget* widget); - void process(const EventHandler& events) override; + void process(const InputView& events) override; [[nodiscard]] bool processesEvents() const override { return true; } private: @@ -65,7 +65,7 @@ namespace tp { void setProject(Project* project); ~SketchGUI() override; - void process(const EventHandler& events) override; + void process(const InputView& events) override; void draw(Canvas& canvas) override; private: diff --git a/Widgets/CMakeLists.txt b/Widgets/CMakeLists.txt index c4a68a5a..80d060bb 100644 --- a/Widgets/CMakeLists.txt +++ b/Widgets/CMakeLists.txt @@ -6,12 +6,12 @@ file(GLOB HEADERS "./public/*.hpp") add_library(${PROJECT_NAME} STATIC ${SOURCES} ${HEADERS}) target_include_directories(${PROJECT_NAME} PUBLIC ./public/ ./public/layouts ./public/mangers ./public/widgets) -target_link_libraries(${PROJECT_NAME} PUBLIC Math Graphics Imgui) +target_link_libraries(${PROJECT_NAME} PUBLIC Math GraphicApplication Imgui) ### -------------------------- Applications -------------------------- ### add_executable(WidgetsExample examples/Example.cpp) target_link_libraries(WidgetsExample ${PROJECT_NAME} ${GLEW_LIB}) +graphic_application_resources(WidgetsExample) target_include_directories(WidgetsExample PUBLIC ../Externals/glfw/include ${GLEW_INCLUDE_DIR}) -file(COPY "examples/Font.ttf" DESTINATION "${CMAKE_BINARY_DIR}/${PROJECT_NAME}/rsc") diff --git a/Widgets/examples/Font.ttf b/Widgets/examples/Font.ttf deleted file mode 100644 index 8a630544c99ec9956153ebd9509abeac421c6d75..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 42624 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.get(ENABLED)) return; - canvas.setOrigin(pos); - canvas.pushClamp({ pos, active->getArea().size }); + { + const Canvas::Placement placement(canvas, pos - canvas.origin(), active->getArea().size); + if (!canvas.isClippedAway()) { + if (active->getArea().sizeVecW() > active->getArea().pos) { + active->draw(canvas); + } - if (canvas.getClampedArea().size.length2() > EPSILON) { - if (active->getArea().sizeVecW() > active->getArea().pos) { - active->draw(canvas); - } - - for (auto child = active->mDepthOrder.lastNode(); child; child = child->prev) { - drawRecursion(canvas, child->data, pos + child->data->getArea().pos); + for (auto child = active->mDepthOrder.lastNode(); child; child = child->prev) { + drawRecursion(canvas, child->data, pos + child->data->getArea().pos); + } } } - canvas.setOrigin(pos); - canvas.popClamp(); - + const Canvas::Translation overlay(canvas, pos - canvas.origin()); active->drawOverlay(canvas); } diff --git a/Widgets/private/WidgetApplication.cpp b/Widgets/private/WidgetApplication.cpp index 70e5aa0e..c3888aca 100644 --- a/Widgets/private/WidgetApplication.cpp +++ b/Widgets/private/WidgetApplication.cpp @@ -4,6 +4,9 @@ using namespace tp; +WidgetApplication::WidgetApplication(const ApplicationOptions& options) : + mApplication(*this, options) {} + void WidgetApplication::debugUI() { // pass } @@ -11,13 +14,13 @@ void WidgetApplication::debugUI() { void WidgetApplication::setRoot(Widget* widget) { mRootWidget.setRootWidget(widget); - tp::RootWidget::setWidgetArea(*widget, {{}, mWindow->getSize()}); + tp::RootWidget::setWidgetArea(*widget, { {}, window().contentSize() }); } -void WidgetApplication::processFrame(EventHandler* eventHandler, halnf deltaTime) { - const auto area = RectF({ 0, 0 }, mWindow->getSize()); +void WidgetApplication::processFrame(const InputState& input, halnf) { + const auto area = RectF({ 0, 0 }, window().contentSize()); - gDebugWidget.update(&mRootWidget, *eventHandler); + gDebugWidget.update(&mRootWidget, InputView(input)); if (gDebugWidget.isDebug()) { mGuiArea = area.splitByFactorHL(mDebugSplitFactor); @@ -29,13 +32,13 @@ void WidgetApplication::processFrame(EventHandler* eventHandler, halnf deltaTime if (gDebugWidget.isFrozen()) return; Timer timer; - mRootWidget.processFrame(eventHandler, mGuiArea); + mRootWidget.processFrame(input, mGuiArea); gDebugWidget.mProcTime.addSample(timer.timePassed()); } -void WidgetApplication::drawFrame(Canvas* canvas) { +void WidgetApplication::drawFrame(Canvas& canvas) { Timer timer; - mRootWidget.drawFrame(*canvas); + mRootWidget.drawFrame(canvas); gDebugWidget.mDrawTime.addSample(timer.timePassed()); if (gDebugWidget.isDebug()) { @@ -48,15 +51,15 @@ void WidgetApplication::drawFrame(Canvas* canvas) { ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoDecoration )) { - drawDebug(); + mApplication.showFrameRates(); debugUI(); - gDebugWidget.drawDebug(&mRootWidget, *canvas); + gDebugWidget.drawDebug(&mRootWidget, canvas); } ImGui::End(); } } -bool WidgetApplication::forceNewFrame() { +bool WidgetApplication::wantsFrame() { return mRootWidget.needsUpdate(); } \ No newline at end of file diff --git a/Widgets/private/managers/DebugManager.cpp b/Widgets/private/managers/DebugManager.cpp index 921f7e1d..77986f62 100644 --- a/Widgets/private/managers/DebugManager.cpp +++ b/Widgets/private/managers/DebugManager.cpp @@ -14,37 +14,35 @@ DebugManager tp::gDebugWidget; #define LIST_SIZE \ { -FLT_MIN, 150 } -void DebugManager::update(RootWidget* rootWidget, EventHandler& events) { +void DebugManager::update(RootWidget* rootWidget, const InputView& events) { mRootWidget = rootWidget; - events.setEnableKeyEvents(true); - if (events.isPressed(InputID::D)) mDebug = !mDebug; + if (events.isPressed(Key::D)) mDebug = !mDebug; if (mDebug) { - events.setEnableKeyEvents(true); - if (events.isPressed(InputID::K)) mDebugStopProcessing = !mDebugStopProcessing; + if (events.isPressed(Key::K)) mDebugStopProcessing = !mDebugStopProcessing; if (auto widget = rootWidget->mUpdateManager.mInFocusWidget) { if (auto lay = dynamic_cast(widget->getLayout())) { - if (events.isPressed(InputID::V)) lay->setLayoutPolicy(LayoutPolicy::Vertical); - if (events.isPressed(InputID::H)) lay->setLayoutPolicy(LayoutPolicy::Horizontal); + if (events.isPressed(Key::V)) lay->setLayoutPolicy(LayoutPolicy::Vertical); + if (events.isPressed(Key::H)) lay->setLayoutPolicy(LayoutPolicy::Horizontal); auto sizing = lay->getSizePolicy(); - if (events.isDown(InputID::X)) { - if (events.isPressed(InputID::S)) lay->setSizePolicy(SizePolicy::Minimal, sizing.y); - if (events.isPressed(InputID::E)) lay->setSizePolicy(SizePolicy::Expanding, sizing.y); + if (events.isDown(Key::X)) { + if (events.isPressed(Key::S)) lay->setSizePolicy(SizePolicy::Minimal, sizing.y); + if (events.isPressed(Key::E)) lay->setSizePolicy(SizePolicy::Expanding, sizing.y); } - if (events.isDown(InputID::Y)) { - if (events.isPressed(InputID::S)) lay->setSizePolicy(sizing.x, SizePolicy::Minimal); - if (events.isPressed(InputID::E)) lay->setSizePolicy(sizing.x, SizePolicy::Expanding); + if (events.isDown(Key::Y)) { + if (events.isPressed(Key::S)) lay->setSizePolicy(sizing.x, SizePolicy::Minimal); + if (events.isPressed(Key::E)) lay->setSizePolicy(sizing.x, SizePolicy::Expanding); } } } if (auto breakWidget = rootWidget->mUpdateManager.mInFocusWidget) { - if (events.isPressed(InputID::P)) mProcBreakpoints.insert(breakWidget); - if (events.isPressed(InputID::L)) mLayBreakpoints.insert(breakWidget); + if (events.isPressed(Key::P)) mProcBreakpoints.insert(breakWidget); + if (events.isPressed(Key::L)) mLayBreakpoints.insert(breakWidget); } } } @@ -110,7 +108,11 @@ void DebugManager::recursiveDraw(Canvas& canvas, Widget* active, const Vec2F& po if (active->isUpdate()) { RGBA color = { 1, 0, 0, 1 }; canvas.frame(area, color); - canvas.text((active->mDebug.id + ":" + std::to_string(depthOrder)).c_str(), area, 22, Canvas::Align::LC, 2, color); + canvas.text( + (active->mDebug.id + ":" + std::to_string(depthOrder)).c_str(), + area, + { .size = 22, .color = color, .padding = 2 } + ); } if (active->mFlags.get(Widget::IN_FOCUS)) { diff --git a/Widgets/private/managers/UpdateManager.cpp b/Widgets/private/managers/UpdateManager.cpp index d1af2cd6..55c03688 100644 --- a/Widgets/private/managers/UpdateManager.cpp +++ b/Widgets/private/managers/UpdateManager.cpp @@ -3,16 +3,9 @@ using namespace tp; -void UpdateManager::processWidgets(Widget* root, EventHandler& events) { - events.setEnableKeyEvents(true); - events.setCursorOrigin({ 0, 0 }); - - processFocusItems(events); - - events.setEnableKeyEvents(false); - events.setCursorOrigin({ 0, 0 }); - - processActiveTree(root, events, { 0, 0 }); +void UpdateManager::processWidgets(Widget* root, const InputState& input) { + processFocusItems(input); + processActiveTree(root, input, { 0, 0 }); } void UpdateManager::clean() { @@ -69,14 +62,12 @@ void UpdateManager::getWidgetPath(Widget* widget, std::vector& out) { } } -void UpdateManager::handleFocusChanges(Widget* root, EventHandler& events) { +void UpdateManager::handleFocusChanges(Widget* root, const InputView& input) { auto prevFocus = mInFocusWidget; - events.setCursorOrigin({ 0, 0 }); - mInFocusWidget = nullptr; - findFocusWidget(root, &mInFocusWidget, events.getPointer()); + findFocusWidget(root, &mInFocusWidget, input.getPointer()); if (mFocusLockWidget) { bool hasLockedWidget = false; @@ -147,7 +138,7 @@ void UpdateManager::findFocusWidget(Widget* iter, Widget** focus, const Vec2F& p } } -void UpdateManager::processActiveTree(Widget* iter, EventHandler& events, Vec2F parent) { +void UpdateManager::processActiveTree(Widget* iter, const InputState& input, Vec2F parent) { if (!iter || !iter->isUpdate()) return; auto current = parent + iter->getAreaT().pos; @@ -155,16 +146,15 @@ void UpdateManager::processActiveTree(Widget* iter, EventHandler& events, Vec2F if (!iter->mFlags.get(Widget::IN_FOCUS)) { iter->mDebug.pGlobal = current; - events.setCursorOrigin(current); - procWidget(iter, events, false); + procWidget(iter, InputView(input, current, InputView::Buttons::Hidden)); } for (auto child : iter->mDepthOrder) { - processActiveTree(child.data(), events, current); + processActiveTree(child.data(), input, current); } } -void UpdateManager::processFocusItems(EventHandler& events) { +void UpdateManager::processFocusItems(const InputState& input) { if (!mInFocusWidget) return; std::vector path; @@ -191,23 +181,18 @@ void UpdateManager::processFocusItems(EventHandler& events) { for (int iter = (int) len - 1; iter >= 0; iter--) { auto widget = path[iter]; - events.setCursorOrigin(widgetGlobalPos[iter]); - widget->mDebug.pGlobal = widgetGlobalPos[iter]; - if (!eventsProcessed && widget->processesEvents()) { - procWidget(widget, events, true); - eventsProcessed = true; - } else { - procWidget(widget, events, false); - } + const bool receivesButtons = !eventsProcessed && widget->processesEvents(); + const InputView::Buttons buttons = receivesButtons ? InputView::Buttons::Visible : InputView::Buttons::Hidden; + procWidget(widget, InputView(input, widgetGlobalPos[iter], buttons)); + eventsProcessed = eventsProcessed || receivesButtons; } } -void UpdateManager::procWidget(Widget* widget, EventHandler& events, bool withEvents) { - events.setEnableKeyEvents(withEvents); +void UpdateManager::procWidget(Widget* widget, const InputView& input) { gDebugWidget.checkProcBreakPoints(widget); - widget->process(events); + widget->process(input); } void UpdateManager::lockFocus(tp::Widget* widget) { diff --git a/Widgets/private/widgets/AnimationTestWidget.cpp b/Widgets/private/widgets/AnimationTestWidget.cpp index 8b09d576..6c93c50f 100644 --- a/Widgets/private/widgets/AnimationTestWidget.cpp +++ b/Widgets/private/widgets/AnimationTestWidget.cpp @@ -2,12 +2,12 @@ using namespace tp; -void AnimationTestWidget::process(const EventHandler& events) { - if (events.isDown(tp::InputID::MOUSE1)) { +void AnimationTestWidget::process(const InputView& events) { + if (events.isDown(PointerButton::Left)) { mTestSpring.getStart().setTargetPosition(events.getPointer()); } - if (events.isDown(tp::InputID::MOUSE2)) { + if (events.isDown(PointerButton::Right)) { mTestSpring.getEnd().setTargetPosition(events.getPointer()); } diff --git a/Widgets/private/widgets/ColorPickerWidget.cpp b/Widgets/private/widgets/ColorPickerWidget.cpp index 1f5c0187..08a598c7 100644 --- a/Widgets/private/widgets/ColorPickerWidget.cpp +++ b/Widgets/private/widgets/ColorPickerWidget.cpp @@ -1,22 +1,154 @@ #include "ColorPickerWidget.hpp" +#include +#include +#include + using namespace tp; -void RGBPickerWidget::process(const EventHandler& events) { - if (events.isPressed(InputID::MOUSE1)) { - mColorWheel.fromPoint(getArea().relative(), events.getPointer()); +namespace { + constexpr halnf kTwoPi = 2 * std::numbers::pi_v; + constexpr halnf kThirdTurn = kTwoPi / 3; + constexpr halnf kRingThickness = 20; + constexpr halnf kWheelMargin = 5; + constexpr halnf kTriangleInset = 6; + constexpr int kRingSegments = 6; + + const RGBA kEdgeShade = { 0, 0, 0, 0.25f }; + const RGBA kMarkerColor = { 1, 1, 1, 0.75f }; + const RGBA kMarkerShadow = { 0, 0, 0, 0.25f }; + const RGBA kTransparent = { 0, 0, 0, 0 }; + + RGBA colorOf(halnf hue, halnf saturation, halnf value) { + RGBA color; + color = HSV(hue, saturation, value); + return color; + } + + Vec2F onCircle(Vec2F center, halnf radius, halnf angle) { return center + Vec2F(radius, 0.0f).rotated(angle); } + + Vec2F saturationValueAt(Vec2F unitTrianglePoint) { + const halnf whiteToHue = std::clamp((1 - unitTrianglePoint.x / std::cos(kThirdTurn)) / 3, 0.0f, 1.0f); + const halnf blackToWhite = (unitTrianglePoint.y / std::sin(kThirdTurn) + 1) / 2; + const halnf insideTriangle = std::clamp(blackToWhite, whiteToHue / 2, 1 - whiteToHue / 2); + + const halnf value = insideTriangle + whiteToHue / 2; + const halnf saturation = value > 0 ? whiteToHue / value : 0; + return { std::clamp(saturation, 0.0f, 1.0f), std::clamp(value, 0.0f, 1.0f) }; + } + + Vec2F unitTrianglePointOf(halnf saturation, halnf value) { + const halnf whiteToHue = saturation * value; + const halnf blackToWhite = value - whiteToHue / 2; + return { (1 - 3 * whiteToHue) * std::cos(kThirdTurn), (2 * blackToWhite - 1) * std::sin(kThirdTurn) }; + } +} + +halnf RGBPickerWidget::Geometry::innerRadius() const { return outerRadius - kRingThickness; } + +halnf RGBPickerWidget::Geometry::triangleRadius() const { return innerRadius() - kTriangleInset; } + +RGBPickerWidget::Geometry RGBPickerWidget::geometry() const { + const RectF area = getArea().relative(); + return { area.center(), std::min(area.z, area.w) / 2 - kWheelMargin }; +} + +RGBPickerWidget::Part RGBPickerWidget::partAt(Vec2F point) const { + const Geometry wheel = geometry(); + const halnf distance = (halnf) (point - wheel.center).length(); + + if (distance > wheel.outerRadius) { + return Part::Outside; + } + if (distance >= wheel.innerRadius()) { + return Part::HueRing; + } + return Part::SaturationValueTriangle; +} + +void RGBPickerWidget::pick(Vec2F point) { + const Geometry wheel = geometry(); + const Vec2F offset = point - wheel.center; + + if (mDragging == Part::HueRing) { + const halnf angle = offset.angle(); + mColor.h = angle >= 0 ? angle : angle + kTwoPi; + } else if (mDragging == Part::SaturationValueTriangle) { + const Vec2F saturationValue = saturationValueAt(offset.rotated(-mColor.h) / wheel.triangleRadius()); + mColor.s = saturationValue.x; + mColor.v = saturationValue.y; + } +} + +void RGBPickerWidget::process(const InputView& events) { + if (events.isPressed(PointerButton::Left)) { + mDragging = partAt(events.getPointer()); + pick(events.getPointer()); lockFocus(); } - if (events.isReleased(InputID::MOUSE1)) { + if (events.isReleased(PointerButton::Left)) { + mDragging = Part::Outside; freeFocus(); } - if (events.getPointerDelta().length() > EPSILON && events.isDown(InputID::MOUSE1)) { - mColorWheel.fromPoint(getArea().relative(), events.getPointer()); + if (events.getPointerDelta().length() > EPSILON && events.isDown(PointerButton::Left)) { + pick(events.getPointer()); } } void RGBPickerWidget::draw(Canvas& canvas) { - canvas.colorWheel(getArea().relative(), mColorWheel); + const Geometry wheel = geometry(); + + drawHueRing(canvas, wheel); + drawHueMarker(canvas, wheel); + drawSaturationValueTriangle(canvas, wheel); + drawSaturationValueMarker(canvas, wheel); +} + +void RGBPickerWidget::drawHueRing(Canvas& canvas, const Geometry& wheel) const { + const halnf seamOverlap = 0.5f / wheel.outerRadius; + const halnf middleRadius = (wheel.innerRadius() + wheel.outerRadius) / 2; + + for (int segment = 0; segment < kRingSegments; segment++) { + const halnf from = (halnf) segment / kRingSegments * kTwoPi; + const halnf to = (halnf) (segment + 1) / kRingSegments * kTwoPi; + const Canvas::LinearGradient hues = { + onCircle(wheel.center, middleRadius, from), + onCircle(wheel.center, middleRadius, to), + colorOf(from, 1, 1), + colorOf(to, 1, 1), + }; + canvas.ringSegment(wheel.center, wheel.innerRadius(), wheel.outerRadius, from - seamOverlap, to + seamOverlap, hues); + } + + canvas.circleOutline(wheel.center, wheel.innerRadius(), kEdgeShade, 1); + canvas.circleOutline(wheel.center, wheel.outerRadius + 1, kEdgeShade, 1); +} + +void RGBPickerWidget::drawHueMarker(Canvas& canvas, const Geometry& wheel) const { + const halnf middleRadius = (wheel.innerRadius() + wheel.outerRadius) / 2; + const Vec2F marker = onCircle(wheel.center, middleRadius, mColor.h); + + canvas.radialGlow(marker, kRingThickness / 2, kRingThickness / 2 + 4, kMarkerShadow, kTransparent); + canvas.circleOutline(marker, kRingThickness / 2, kMarkerColor, 2); +} + +void RGBPickerWidget::drawSaturationValueTriangle(Canvas& canvas, const Geometry& wheel) const { + const halnf radius = wheel.triangleRadius(); + const Vec2F hueCorner = onCircle(wheel.center, radius, mColor.h); + const Vec2F whiteCorner = onCircle(wheel.center, radius, mColor.h + kThirdTurn); + const Vec2F blackCorner = onCircle(wheel.center, radius, mColor.h - kThirdTurn); + const Vec2F triangle[] = { hueCorner, whiteCorner, blackCorner }; + + canvas.polygon(triangle, 3, Canvas::LinearGradient{ hueCorner, whiteCorner, colorOf(mColor.h, 1, 1), { 1, 1, 1, 1 } }); + canvas.polygon(triangle, 3, Canvas::LinearGradient{ (hueCorner + whiteCorner) / 2, blackCorner, kTransparent, { 0, 0, 0, 1 } }); +} + +void RGBPickerWidget::drawSaturationValueMarker(Canvas& canvas, const Geometry& wheel) const { + const Vec2F unitPoint = unitTrianglePointOf(mColor.s, mColor.v); + const Vec2F marker = wheel.center + (unitPoint * wheel.triangleRadius()).rotated(mColor.h); + + canvas.radialGlow(marker, 7, 9, kMarkerShadow, kTransparent); + canvas.circleOutline(marker, 5, kMarkerColor, 2); } diff --git a/Widgets/private/widgets/DockWidget.cpp b/Widgets/private/widgets/DockWidget.cpp index b75a35c5..e28fd913 100644 --- a/Widgets/private/widgets/DockWidget.cpp +++ b/Widgets/private/widgets/DockWidget.cpp @@ -51,7 +51,7 @@ void DockWidget::toggleWidgetVisibility(DockLayout::Side side) { } } -void DockWidget::process(const EventHandler& events) { +void DockWidget::process(const InputView& events) { // calculateHeaderAreas(); Widget* floater = nullptr; @@ -79,12 +79,12 @@ void DockWidget::process(const EventHandler& events) { if (!mPreviewWidget) { layout()->updateResizeHover(events.getPointer()); - if (events.isPressed(InputID::MOUSE1)) { + if (events.isPressed(PointerButton::Left)) { if (layout()->startResize(events.getPointer())) { triggerWidgetUpdate("dock layout resizing"); lockFocus(); } - } else if (events.isReleased(InputID::MOUSE1)) { + } else if (events.isReleased(PointerButton::Left)) { layout()->endResize(); freeFocus(); } diff --git a/Widgets/private/widgets/FloatingWidget.cpp b/Widgets/private/widgets/FloatingWidget.cpp index 0a8579ad..e95e53c8 100644 --- a/Widgets/private/widgets/FloatingWidget.cpp +++ b/Widgets/private/widgets/FloatingWidget.cpp @@ -3,17 +3,17 @@ using namespace tp; -void FloatingWidget::process(const EventHandler& events) { +void FloatingWidget::process(const InputView& events) { const auto pointer = events.getPointer(); - if (getRelativeAreaT().isInside(pointer) && events.isPressed(InputID::MOUSE1)) { + if (getRelativeAreaT().isInside(pointer) && events.isPressed(PointerButton::Left)) { layout()->startAction(pointer); bringToFront(); lockFocus(); } - if (layout()->isFloating() && events.isReleased(InputID::MOUSE1)) { + if (layout()->isFloating() && events.isReleased(PointerButton::Left)) { layout()->endAction(); freeFocus(); } diff --git a/Widgets/private/widgets/ScrollableWidget.cpp b/Widgets/private/widgets/ScrollableWidget.cpp index e67f0fac..fe6d669c 100644 --- a/Widgets/private/widgets/ScrollableWidget.cpp +++ b/Widgets/private/widgets/ScrollableWidget.cpp @@ -4,7 +4,7 @@ using namespace tp; -void ScrollableBarWidget::process(const EventHandler& events) { +void ScrollableBarWidget::process(const InputView& events) { // all content is visible no need to process anything if (mSizeFactor >= 1) { mScrolling = false; @@ -20,7 +20,7 @@ void ScrollableBarWidget::process(const EventHandler& events) { mHandleHovered = getHandleRect().isInside(pointer); - if (getRelativeAreaT().isInside(pointer) && events.isPressed(InputID::MOUSE1)) { + if (getRelativeAreaT().isInside(pointer) && events.isPressed(PointerButton::Left)) { if (!mHandleHovered) { jumpTo((pointer / size)[mVertical]); mStartPos = { 0, 0 }; @@ -31,7 +31,7 @@ void ScrollableBarWidget::process(const EventHandler& events) { lockFocus(); } - if (mScrolling && events.isReleased(InputID::MOUSE1)) { + if (mScrolling && events.isReleased(PointerButton::Left)) { mScrolling = false; freeFocus(); } diff --git a/Widgets/private/widgets/SimpleWidgets.cpp b/Widgets/private/widgets/SimpleWidgets.cpp index 3407fa4e..4b763d96 100644 --- a/Widgets/private/widgets/SimpleWidgets.cpp +++ b/Widgets/private/widgets/SimpleWidgets.cpp @@ -12,7 +12,7 @@ const std::string& LabelWidget::getText() const { } void LabelWidget::draw(Canvas& canvas) { - canvas.text(mText.c_str(), getRelativeArea(), mSize, Canvas::LC, mPadding, mColor); + canvas.text(mText.c_str(), getRelativeArea(), { .size = mSize, .color = mColor, .padding = mPadding }); } ButtonWidget::ButtonWidget() { @@ -36,9 +36,9 @@ void ButtonWidget::setColor(const RGBA& in) { triggerWidgetUpdate("color changed"); } -void ButtonWidget::process(const EventHandler& eventHandler) { - if (getRelativeArea().isInside(eventHandler.getPointer())) { - if (eventHandler.isPressed(InputID::MOUSE1)) { +void ButtonWidget::process(const InputView& inputState) { + if (getRelativeArea().isInside(inputState.getPointer())) { + if (inputState.isPressed(PointerButton::Left)) { mAction(); } } @@ -81,12 +81,12 @@ void ButtonWidget::mouseLeave() { } -void SliderWidget::process(const EventHandler& events) { +void SliderWidget::process(const InputView& events) { const auto pointer = events.getPointer(); switch (mState) { case SLIDING: - if (events.isReleased(InputID::MOUSE1)) { + if (events.isReleased(PointerButton::Left)) { mState = IDLE; freeFocus(); } @@ -98,7 +98,7 @@ void SliderWidget::process(const EventHandler& events) { case IDLE: case HOVER: mState = getHandleArea().isInside(pointer) ? HOVER : IDLE; - if (getRelativeAreaT().isInside(pointer) && events.isPressed(InputID::MOUSE1)) { + if (getRelativeAreaT().isInside(pointer) && events.isPressed(PointerButton::Left)) { mState = SLIDING; lockFocus(); } @@ -138,7 +138,7 @@ void PopupWidget::open(Widget* parent, const RectF& at) { parent->openPopup(this); } -void PopupWidget::process(const EventHandler& events) { +void PopupWidget::process(const InputView& events) { bool insideParent = mParentArea.isInside(events.getPointer()); bool insideMenu = getArea().relative().isInside(events.getPointer()); diff --git a/Widgets/public/RootWidget.hpp b/Widgets/public/RootWidget.hpp index d66b1b07..5d72c8e1 100644 --- a/Widgets/public/RootWidget.hpp +++ b/Widgets/public/RootWidget.hpp @@ -20,7 +20,7 @@ namespace tp { // Graphic Application Interface public: - void processFrame(EventHandler* events, const RectF& screenArea); + void processFrame(const InputState& input, const RectF& screenArea); void drawFrame(Canvas& canvas); [[nodiscard]] bool needsUpdate() const; diff --git a/Widgets/public/Widget.hpp b/Widgets/public/Widget.hpp index c44d7183..1defb36c 100644 --- a/Widgets/public/Widget.hpp +++ b/Widgets/public/Widget.hpp @@ -4,8 +4,9 @@ #include "Layout.hpp" -#include "EventHandler.hpp" -#include "Graphics.hpp" +#include "InputState.hpp" +#include "Canvas.hpp" +#include "List.hpp" #include #include @@ -73,7 +74,7 @@ namespace tp { void freeFocus(); protected: - virtual void process(const EventHandler& events) {} + virtual void process(const InputView& events) {} virtual void draw(Canvas& canvas) {} virtual void drawOverlay(Canvas& canvas) {} virtual void endAnimations(); diff --git a/Widgets/public/WidgetApplication.hpp b/Widgets/public/WidgetApplication.hpp index 7bf6fbef..c328ff8a 100644 --- a/Widgets/public/WidgetApplication.hpp +++ b/Widgets/public/WidgetApplication.hpp @@ -5,27 +5,31 @@ #include "Timing.hpp" namespace tp { - class WidgetApplication : public Application { + class WidgetApplication : public ApplicationDelegate { public: - WidgetApplication() = default; + explicit WidgetApplication(const ApplicationOptions& options = {}); + + void run() { mApplication.run(); } + [[nodiscard]] Application& application() { return mApplication; } + [[nodiscard]] Window& window() { return mApplication.window(); } + [[nodiscard]] Canvas& canvas() { return mApplication.canvas(); } void setRoot(Widget* widget); virtual void debugUI(); private: - void processFrame(EventHandler* eventHandler, halnf deltaTime) override; - - void drawFrame(Canvas* canvas) override; - bool forceNewFrame() override ; - + void processFrame(const InputState& input, halnf deltaMs) override; + void drawFrame(Canvas& canvas) override; + bool wantsFrame() override; private: halnf mDebugSplitFactor = 0.7; RootWidget mRootWidget; + Application mApplication; private: RectF mGuiArea; RectF mDebugArea; }; -} \ No newline at end of file +} diff --git a/Widgets/public/mangers/DebugManager.hpp b/Widgets/public/mangers/DebugManager.hpp index 0b4ce929..72390a79 100644 --- a/Widgets/public/mangers/DebugManager.hpp +++ b/Widgets/public/mangers/DebugManager.hpp @@ -1,5 +1,6 @@ #pragma once +#include "Timing.hpp" #include "Widget.hpp" #include @@ -54,7 +55,7 @@ namespace tp { [[nodiscard]] bool isFrozen() const { return mDebugStopProcessing; } [[nodiscard]] bool isRedrawAlways() const { return mDebugRedrawAlways; } - void update(RootWidget* rootWidget, EventHandler& events); + void update(RootWidget* rootWidget, const InputView& events); void drawDebug(RootWidget* rootWidget, Canvas& canvas); void checkProcBreakPoints(Widget* widget) { diff --git a/Widgets/public/mangers/UpdateManager.hpp b/Widgets/public/mangers/UpdateManager.hpp index 33c5ecf3..b6e3252b 100644 --- a/Widgets/public/mangers/UpdateManager.hpp +++ b/Widgets/public/mangers/UpdateManager.hpp @@ -21,19 +21,19 @@ namespace tp { return !mTriggeredWidgets.empty(); } - void processWidgets(Widget* root, EventHandler& eventHandler); + void processWidgets(Widget* root, const InputState& input); void clean(); void updateTreeToProcess(Widget* root); - void handleFocusChanges(Widget* root, EventHandler& events); + void handleFocusChanges(Widget* root, const InputView& input); void findFocusWidget(Widget* iter, Widget** focus, const Vec2F& pointer); static void getWidgetPath(Widget* widget, std::vector& out); - void processActiveTree(Widget* iter, EventHandler& events, Vec2F pos); - void processFocusItems(EventHandler& events); + void processActiveTree(Widget* iter, const InputState& input, Vec2F pos); + void processFocusItems(const InputState& input); private: - static void procWidget(Widget* widget, EventHandler& events, bool withEvents = false); + static void procWidget(Widget* widget, const InputView& input); private: std::map mTriggeredWidgets; diff --git a/Widgets/public/widgets/AnimationTestWidget.hpp b/Widgets/public/widgets/AnimationTestWidget.hpp index dd3f66c4..0c5851d2 100644 --- a/Widgets/public/widgets/AnimationTestWidget.hpp +++ b/Widgets/public/widgets/AnimationTestWidget.hpp @@ -10,7 +10,7 @@ namespace tp { ~AnimationTestWidget() override = default; void draw(Canvas& canvas) override; - void process(const EventHandler& events) override; + void process(const InputView& events) override; [[nodiscard]] bool needsNextFrame() const override; diff --git a/Widgets/public/widgets/ColorPickerWidget.hpp b/Widgets/public/widgets/ColorPickerWidget.hpp index e9d592c0..a6b40410 100644 --- a/Widgets/public/widgets/ColorPickerWidget.hpp +++ b/Widgets/public/widgets/ColorPickerWidget.hpp @@ -7,12 +7,34 @@ namespace tp { public: RGBPickerWidget() = default; - void process(const EventHandler& events) override; + void process(const InputView& events) override; void draw(Canvas& canvas) override; [[nodiscard]] bool processesEvents() const override { return true; } - public: - Canvas::ColorWheel mColorWheel; + [[nodiscard]] const HSV& color() const { return mColor; } + + private: + enum class Part { Outside, HueRing, SaturationValueTriangle }; + + struct Geometry { + Vec2F center; + halnf outerRadius = 0; + + [[nodiscard]] halnf innerRadius() const; + [[nodiscard]] halnf triangleRadius() const; + }; + + [[nodiscard]] Geometry geometry() const; + [[nodiscard]] Part partAt(Vec2F point) const; + void pick(Vec2F point); + + void drawHueRing(Canvas& canvas, const Geometry& wheel) const; + void drawHueMarker(Canvas& canvas, const Geometry& wheel) const; + void drawSaturationValueTriangle(Canvas& canvas, const Geometry& wheel) const; + void drawSaturationValueMarker(Canvas& canvas, const Geometry& wheel) const; + + HSV mColor = { 0.13f, 0.5f, 1 }; + Part mDragging = Part::Outside; }; -} \ No newline at end of file +} diff --git a/Widgets/public/widgets/DockWidget.hpp b/Widgets/public/widgets/DockWidget.hpp index 0e07f2fa..91ff0a8f 100644 --- a/Widgets/public/widgets/DockWidget.hpp +++ b/Widgets/public/widgets/DockWidget.hpp @@ -6,7 +6,7 @@ namespace tp { DockWidget(); public: - void process(const EventHandler& events) override; + void process(const InputView& events) override; void draw(Canvas& canvas) override; void drawOverlay(Canvas& canvas) override; diff --git a/Widgets/public/widgets/FloatingWidget.hpp b/Widgets/public/widgets/FloatingWidget.hpp index 71f16865..c8e096c0 100644 --- a/Widgets/public/widgets/FloatingWidget.hpp +++ b/Widgets/public/widgets/FloatingWidget.hpp @@ -12,7 +12,7 @@ namespace tp { setLayout(new FloatingLayout(this)); } - void process(const EventHandler& events) override; + void process(const InputView& events) override; void draw(Canvas& canvas) override; diff --git a/Widgets/public/widgets/ScrollableWidget.hpp b/Widgets/public/widgets/ScrollableWidget.hpp index 1903df20..bcb67ad1 100644 --- a/Widgets/public/widgets/ScrollableWidget.hpp +++ b/Widgets/public/widgets/ScrollableWidget.hpp @@ -12,7 +12,7 @@ namespace tp { [[nodiscard]] bool propagateEventsToChildren() const override { return false; } [[nodiscard]] bool needsNextFrame() const override { return mScrolling; } - void process(const EventHandler& events) override; + void process(const InputView& events) override; void draw(Canvas& canvas) override; void updateSizeFactor(halnf factor); // holder-size divided by content-size diff --git a/Widgets/public/widgets/SimpleWidgets.hpp b/Widgets/public/widgets/SimpleWidgets.hpp index b1036cc1..c4c6d79b 100644 --- a/Widgets/public/widgets/SimpleWidgets.hpp +++ b/Widgets/public/widgets/SimpleWidgets.hpp @@ -33,7 +33,7 @@ namespace tp { void setAction(const std::function& action); - void process(const EventHandler& eventHandler) override; + void process(const InputView& inputState) override; void draw(Canvas& canvas) override; void mouseEnter() override; @@ -67,7 +67,7 @@ namespace tp { public: SliderWidget() = default; - void process(const EventHandler& eventHandler) override; + void process(const InputView& inputState) override; void draw(Canvas& canvas) override; [[nodiscard]] bool processesEvents() const override { return true; } @@ -97,7 +97,7 @@ namespace tp { PopupWidget() = default; public: - void process(const EventHandler& events) override; + void process(const InputView& events) override; void draw(Canvas& canvas) override; void open(Widget* parent, const RectF& at); -- 2.49.1

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 z