Compare commits

...

4 Commits

Author SHA1 Message Date
7d72cca2cf do it, ugly 2026-10-25 14:28:37 +03:00
74df2d95a4 use a factory 2026-10-25 13:55:18 +03:00
ad47769e46 drop updateViewIf()
it is useless, radio button groups have the signal actually needed
2026-10-25 13:28:34 +03:00
96c75c3571 use a vtable instead of direct linking 2026-10-25 13:26:49 +03:00
12 changed files with 206 additions and 145 deletions

View File

@ -1,13 +1,12 @@
include(impl.cmake) include(impl.cmake)
qt_add_executable(PROJECT-NAME qt_add_library(PROJECT_NAME_ui STATIC
src/api.cxx
src/main.cxx src/main.cxx
src/main_window.cxx src/main_window.cxx
src/main_window.ui src/main_window.ui
src/viewport.cxx src/viewport.cxx
) )
target_link_libraries(PROJECT-NAME PRIVATE Qt6::Gui Qt6::Widgets) target_link_libraries(PROJECT_NAME_ui PRIVATE Qt6::Gui Qt6::Widgets)
target_link_libraries(PROJECT-NAME PRIVATE PROJECT_NAME_impl) target_link_libraries(PROJECT_NAME_ui PRIVATE PROJECT_NAME_impl)
target_include_directories(PROJECT-NAME PRIVATE src) target_include_directories(PROJECT_NAME_ui PRIVATE src)

View File

@ -3,9 +3,6 @@ name = "PROJECT-NAME-impl"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"
[lib]
crate-type = ["staticlib"]
[dependencies] [dependencies]
PROJECT-NAME = {path = "../"} PROJECT-NAME = {path = "../"}

7
ui/build.rs Normal file
View File

@ -0,0 +1,7 @@
fn main() {
println!("cargo::rustc-link-search=native=build/ui");
println!("cargo::rustc-link-lib=dylib=Qt6Core");
println!("cargo::rustc-link-lib=dylib=Qt6Gui");
println!("cargo::rustc-link-lib=dylib=Qt6Widgets");
println!("cargo::rustc-link-lib=dylib=stdc++");
}

View File

@ -1,57 +0,0 @@
#include "api.hxx"
#include <stdexcept>
#include <utility>
namespace ffi {
extern "C" Core* rt4_viewport_create(xcb_connection_t* connection, std::uint32_t window, std::uint32_t width, std::uint32_t height);
extern "C" void rt4_viewport_destroy(Core* viewport);
extern "C" void rt4_viewport_configure(Core* viewport, std::uint32_t width, std::uint32_t height);
extern "C" void rt4_viewport_redraw(Core* viewport, const RedrawArgs* args);
} // namespace ffi
BoxCore::BoxCore(BoxCore&& b)
: ptr(std::exchange(b.ptr, {})) {
}
BoxCore::~BoxCore() {
reset();
}
BoxCore& BoxCore::operator=(BoxCore&& b) {
if (&b == this)
return *this;
std::swap(ptr, b.ptr);
b.reset();
return *this;
}
BoxCore BoxCore::from_xcb(xcb_connection_t* connection, std::uint32_t window, std::uint32_t width, std::uint32_t height) {
if (!connection)
throw std::logic_error("attempt to use a null connection");
if (!window)
throw std::logic_error("attempt to use a null window");
BoxCore out;
out.ptr.ptr = ffi::rt4_viewport_create(connection, window, width, height);
return out;
}
void BoxCore::reset() {
auto viewport = std::exchange(ptr, {});
if (viewport)
ffi::rt4_viewport_destroy(viewport.use());
}
ffi::Core* MutCore::use() const {
if (!ptr)
throw std::logic_error("attempt to use a null Core");
return ptr;
}
void MutCore::configure(std::uint32_t width, std::uint32_t height) const {
rt4_viewport_configure(use(), width, height);
}
void MutCore::redraw(const RedrawArgs& args) const {
rt4_viewport_redraw(use(), &args);
}

View File

@ -2,15 +2,31 @@
#include <cstdint> #include <cstdint>
#include <memory>
struct xcb_connection_t; struct xcb_connection_t;
class IBoxed {
friend struct BoxDeleter;
private:
virtual void destroy() = 0;
};
struct BoxDeleter {
void operator()(IBoxed* obj) {
obj->destroy();
}
};
template <typename T>
using Box = std::unique_ptr<T, BoxDeleter>;
struct alignas(16) Vec4 { struct alignas(16) Vec4 {
float x, y, z, w; float x, y, z, w;
}; };
namespace ffi { namespace ffi {
struct Core;
struct RedrawArgs { struct RedrawArgs {
float cam_yaw; float cam_yaw;
float cam_pitch; float cam_pitch;
@ -21,36 +37,18 @@ struct RedrawArgs {
using ffi::RedrawArgs; using ffi::RedrawArgs;
class MutCore { class ICore : public IBoxed {
friend class BoxCore;
public: public:
explicit operator bool() const { return ptr; } virtual void configure(std::uint32_t width, std::uint32_t height) = 0;
virtual void redraw(const RedrawArgs& args) = 0;
void configure(std::uint32_t width, std::uint32_t height) const;
void redraw(const RedrawArgs& args) const;
private:
ffi::Core* ptr = nullptr;
ffi::Core* use() const;
}; };
class BoxCore { class ICoreFactory {
public:
BoxCore() = default;
BoxCore(const BoxCore&) = delete;
BoxCore(BoxCore&&);
BoxCore& operator=(const BoxCore&) = delete;
BoxCore& operator=(BoxCore&&);
~BoxCore();
explicit operator bool() const { return !!ptr; }
const MutCore* operator->() const { return &ptr; }
void reset();
static BoxCore from_xcb(xcb_connection_t* connection, std::uint32_t window, std::uint32_t width, std::uint32_t height);
private: private:
MutCore ptr; virtual ICore* new_xcb(xcb_connection_t* connection, std::uint32_t window, std::uint32_t width, std::uint32_t height) const = 0;
public:
Box<ICore> from_xcb(xcb_connection_t* connection, std::uint32_t window, std::uint32_t width, std::uint32_t height) const {
return Box<ICore>{new_xcb(connection, window, width, height)};
}
}; };

View File

@ -1,14 +1,122 @@
use PROJECT_NAME::{Core, init_gpu_inner};
use glam::UVec2;
use raw_window_handle::{RawDisplayHandle, RawWindowHandle};
use crate::core::ICoreImpl;
pub use factory::ICoreFactoryImpl;
mod core {
use std::ops::{Deref, DerefMut};
use PROJECT_NAME::{Core, RedrawArgs};
use glam::uvec2;
#[repr(C)]
struct ICoreVTable {
destroy: unsafe extern "C" fn(this: Box<ICoreImpl>),
configure: unsafe extern "C" fn(this: &mut ICoreImpl, width: u32, height: u32),
redraw: unsafe extern "C" fn(this: &mut ICoreImpl, args: &RedrawArgs),
}
unsafe extern "C" fn destroy(this: Box<ICoreImpl>) {
drop(this);
}
unsafe extern "C" fn configure(this: &mut ICoreImpl, width: u32, height: u32) {
this.core.configure(uvec2(width, height));
}
unsafe extern "C" fn redraw(this: &mut ICoreImpl, args: &RedrawArgs) {
this.core.redraw(args);
}
static VTABLE: ICoreVTable = ICoreVTable {
destroy,
configure,
redraw,
};
#[repr(C)]
pub(crate) struct ICoreImpl {
vtable: &'static ICoreVTable,
core: Core,
}
impl ICoreImpl {
pub(crate) fn new(core: Core) -> Box<Self> {
Box::new(ICoreImpl {
vtable: &VTABLE,
core,
})
}
}
impl DerefMut for ICoreImpl {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.core
}
}
impl Deref for ICoreImpl {
type Target = Core;
fn deref(&self) -> &Self::Target {
&self.core
}
}
}
mod factory {
use std::{ffi::c_void, num::NonZero, ptr::NonNull}; use std::{ffi::c_void, num::NonZero, ptr::NonNull};
use PROJECT_NAME::{Core, RedrawArgs, init_gpu_inner}; use glam::uvec2;
use glam::{UVec2, uvec2}; use raw_window_handle::{XcbDisplayHandle, XcbWindowHandle};
use raw_window_handle::{RawDisplayHandle, RawWindowHandle, XcbDisplayHandle, XcbWindowHandle};
use crate::core::ICoreImpl;
#[repr(C)]
struct ICoreFactoryVTable {
new_xcb: unsafe extern "C" fn(
this: &mut ICoreFactoryImpl,
connection: NonNull<c_void>,
window: NonZero<u32>,
width: u32,
height: u32,
) -> Box<ICoreImpl>,
}
unsafe extern "C" fn new_xcb(
_this: &mut ICoreFactoryImpl,
connection: NonNull<c_void>,
window: NonZero<u32>,
width: u32,
height: u32,
) -> Box<ICoreImpl> {
let display = XcbDisplayHandle::new(Some(connection), 0);
let window = XcbWindowHandle::new(window);
unsafe { crate::create_viewport(display, window, uvec2(width, height)) }
}
static VTABLE: ICoreFactoryVTable = ICoreFactoryVTable { new_xcb };
#[repr(C)]
pub struct ICoreFactoryImpl {
vtable: &'static ICoreFactoryVTable,
}
impl ICoreFactoryImpl {
pub fn new() -> Self {
ICoreFactoryImpl { vtable: &VTABLE }
}
}
}
unsafe fn create_viewport( unsafe fn create_viewport(
display: impl Into<RawDisplayHandle>, display: impl Into<RawDisplayHandle>,
window: impl Into<RawWindowHandle>, window: impl Into<RawWindowHandle>,
size: UVec2, size: UVec2,
) -> Box<Core> { ) -> Box<ICoreImpl> {
let target = wgpu::SurfaceTargetUnsafe::RawHandle { let target = wgpu::SurfaceTargetUnsafe::RawHandle {
raw_display_handle: display.into(), raw_display_handle: display.into(),
raw_window_handle: window.into(), raw_window_handle: window.into(),
@ -17,32 +125,5 @@ unsafe fn create_viewport(
instance.create_surface_unsafe(target) instance.create_surface_unsafe(target)
})) }))
.unwrap(); .unwrap();
Box::new(Core::new(gpu, size)) ICoreImpl::new(Core::new(gpu, size))
}
#[unsafe(no_mangle)]
unsafe extern "C" fn rt4_viewport_create(
connection: NonNull<c_void>,
window: NonZero<u32>,
width: u32,
height: u32,
) -> Box<Core> {
let display = XcbDisplayHandle::new(Some(connection), 0);
let window = XcbWindowHandle::new(window);
unsafe { create_viewport(display, window, uvec2(width, height)) }
}
#[unsafe(no_mangle)]
unsafe extern "C" fn rt4_viewport_destroy(viewport: Box<Core>) {
drop(viewport);
}
#[unsafe(no_mangle)]
unsafe extern "C" fn rt4_viewport_configure(viewport: &mut Core, width: u32, height: u32) {
viewport.configure(uvec2(width, height));
}
#[unsafe(no_mangle)]
unsafe extern "C" fn rt4_viewport_redraw(viewport: &mut Core, args: &RedrawArgs) {
viewport.redraw(args);
} }

View File

@ -1,14 +1,15 @@
#include "main_window.hxx"
#include <QApplication> #include <QApplication>
int main(int argc, char* argv[]) { #include "api.hxx"
#include "main_window.hxx"
extern "C" int run(int argc, char* argv[], ICoreFactory* factory) {
// only X11 is supported so, use XCB unless overriden // only X11 is supported so, use XCB unless overriden
setenv("QT_QPA_PLATFORM", "xcb", 0); setenv("QT_QPA_PLATFORM", "xcb", 0);
QApplication app(argc, argv); QApplication app(argc, argv);
auto w = new PROJECTNAME; auto w = new PROJECTNAME(factory);
w->show(); w->show();
return app.exec(); return app.exec();

26
ui/src/main.rs Normal file
View File

@ -0,0 +1,26 @@
use std::ffi::{c_char, c_int};
use PROJECT_NAME_impl::ICoreFactoryImpl;
#[link(kind = "static", name = "PROJECT_NAME_ui")]
unsafe extern "C" {
fn run(argc: c_int, argv: *mut *mut c_char, factory: &ICoreFactoryImpl) -> c_int;
}
fn main() {
let factory = ICoreFactoryImpl::new();
let mut args: Vec<_> = std::env::args().collect();
let mut argv: Vec<*mut c_char> = args
.iter_mut()
.map(|s| s.as_mut_ptr() as *mut c_char)
.collect();
argv.push(std::ptr::null_mut());
let exit_code = unsafe {
run(
args.len().try_into().expect("WAY too many arguments"),
argv.as_mut_ptr(),
&factory,
)
};
std::process::exit(exit_code);
}

View File

@ -2,10 +2,12 @@
#include "ui_main_window.h" #include "ui_main_window.h"
PROJECTNAME::PROJECTNAME(QWidget* parent) PROJECTNAME::PROJECTNAME(const ICoreFactory* factory, QWidget* parent)
: QMainWindow(parent), : QMainWindow(parent),
m_ui(new Ui::MainWindow) { m_ui(new Ui::MainWindow),
factory(factory) {
m_ui->setupUi(this); m_ui->setupUi(this);
m_ui->viewport->setFactory(factory);
updateView(); updateView();
} }
@ -21,9 +23,4 @@ void PROJECTNAME::updateView() {
m_ui->viewport->setView(args); m_ui->viewport->setView(args);
} }
void PROJECTNAME::updateViewIf(bool update) {
if (update)
updateView();
}
#include "moc_main_window.cpp" #include "moc_main_window.cpp"

View File

@ -3,6 +3,8 @@
#include <memory> #include <memory>
#include "api.hxx"
namespace Ui { namespace Ui {
class MainWindow; class MainWindow;
} }
@ -11,13 +13,13 @@ class PROJECTNAME : public QMainWindow {
Q_OBJECT Q_OBJECT
public: public:
explicit PROJECTNAME(QWidget* parent = nullptr); explicit PROJECTNAME(const ICoreFactory* factory, QWidget* parent = nullptr);
~PROJECTNAME() override; ~PROJECTNAME() override;
public slots: public slots:
void updateView(); void updateView();
void updateViewIf(bool update); // for radio buttons
private: private:
const std::unique_ptr<Ui::MainWindow> m_ui; const std::unique_ptr<Ui::MainWindow> m_ui;
const ICoreFactory* factory;
}; };

View File

@ -14,6 +14,11 @@ Viewport::Viewport(QWidget* parent, Qt::WindowFlags f)
Viewport::~Viewport() = default; Viewport::~Viewport() = default;
void Viewport::setFactory(const ICoreFactory* new_factory) {
factory = new_factory;
update();
}
void Viewport::setView(RedrawArgs new_args) { void Viewport::setView(RedrawArgs new_args) {
args = new_args; args = new_args;
update(); update();
@ -37,6 +42,8 @@ bool Viewport::event(QEvent* event) {
void Viewport::paintEvent(QPaintEvent* event) { void Viewport::paintEvent(QPaintEvent* event) {
if (!core) if (!core)
recreate(); recreate();
if (!core)
return;
core->redraw(args); core->redraw(args);
} }
@ -48,6 +55,9 @@ void Viewport::resizeEvent(QResizeEvent* event) {
} }
void Viewport::recreate() try { void Viewport::recreate() try {
if (!factory)
return;
auto* app = qobject_cast<QGuiApplication*>(QApplication::instance()); auto* app = qobject_cast<QGuiApplication*>(QApplication::instance());
if (!app) if (!app)
throw std::runtime_error("not a GUI application (WTF?)"); throw std::runtime_error("not a GUI application (WTF?)");
@ -59,11 +69,9 @@ void Viewport::recreate() try {
auto* xcb_connection = native->connection(); auto* xcb_connection = native->connection();
std::uint32_t x11_window = winId(); std::uint32_t x11_window = winId();
fprintf(stderr, "connection %p, window %#08x\n", xcb_connection, x11_window);
const QSize device_size = size() * devicePixelRatio(); const QSize device_size = size() * devicePixelRatio();
core.reset(); core.reset();
core = BoxCore::from_xcb(xcb_connection, x11_window, device_size.width(), device_size.height()); core = factory->from_xcb(xcb_connection, x11_window, device_size.width(), device_size.height());
} catch (const std::exception& e) { } catch (const std::exception& e) {
fprintf(stderr, "failed to recreate the viewport: %s", e.what()); fprintf(stderr, "failed to recreate the viewport: %s", e.what());
} }

View File

@ -12,6 +12,7 @@ public:
~Viewport() override; ~Viewport() override;
QPaintEngine* paintEngine() const override; QPaintEngine* paintEngine() const override;
void setFactory(const ICoreFactory* new_factory);
void setView(RedrawArgs new_args); void setView(RedrawArgs new_args);
protected: protected:
@ -20,7 +21,8 @@ protected:
void resizeEvent(QResizeEvent* event) override; void resizeEvent(QResizeEvent* event) override;
private: private:
BoxCore core; const ICoreFactory* factory = nullptr;
Box<ICore> core;
RedrawArgs args = {}; RedrawArgs args = {};
void recreate(); void recreate();