diff --git a/client/build.rs b/client/build.rs index 90bb452..68e8d33 100644 --- a/client/build.rs +++ b/client/build.rs @@ -7,12 +7,17 @@ fn main() { println!("cargo:rerun-if-changed=src/rust_wrapper.cpp"); println!("cargo:rerun-if-changed=../shared/src"); + // Build the required shared C++ bits, and the glue C++ code + // that we can import to Rust. (Since the API surface is so small, + // and not in danger of changing in a huge way, I am opting for manual + // bindings instead of using a crate like the `cxx` crate. Sue me.) build .emit_rerun_if_env_changed(true) .cpp(true) .std("c++20") .include("../shared/src") .file("../shared/src/ivshmem.cpp") + .file("../shared/src/client.cpp") .file("src/rust_wrapper.cpp") .compile("hazelnut_client_cpp_native"); } diff --git a/client/src/client.rs b/client/src/client.rs index b7dd89b..328d329 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -6,7 +6,7 @@ pub(crate) mod sys { #[repr(u32)] #[allow(unused)] // it IS used, just not by Rust - pub enum ResultCode { + pub enum ClientResultCode { /// The frame has not been changed. (This is implemented internally by comparing the frame serial; if it has changed then the frame has too) Unchanged, @@ -26,7 +26,7 @@ pub(crate) mod sys { pub(crate) fn rust_hazelnut_client_open(client: *mut ffi::c_void, fd: ffi::c_int) -> bool; - pub(crate) fn rust_hazelnut_client_tick(client: *mut ffi::c_void) -> ResultCode; + pub(crate) fn rust_hazelnut_client_tick(client: *mut ffi::c_void) -> ClientResultCode; // Explicitly lock/unlock. pub(crate) fn rust_hazelnut_client_lock(client: *mut ffi::c_void); @@ -39,7 +39,7 @@ pub(crate) mod sys { } } -pub type ResultCode = sys::ResultCode; +pub type ResultCode = sys::ClientResultCode; /// A Hazelnut IVSHMEM client. Wraps the C++ code (see ./rust_wrapper.cpp and the shared sources) /// so that we can use it in Rust, semi-safely. (Note that I really do not like the current diff --git a/client/src/rust_wrapper.cpp b/client/src/rust_wrapper.cpp index 7109825..c7f3ea8 100644 --- a/client/src/rust_wrapper.cpp +++ b/client/src/rust_wrapper.cpp @@ -1,84 +1,20 @@ -#include "ivshmem.hpp" -#include "ivshmem_protocol.hpp" -#include "Utils.hpp" +#include "client.hpp" -enum class ResultCode : u32 { Unchanged, Changed, LockContended, Fail }; - -struct HazelnutIvshmemClient { - bool Open(int fd) { - if(!ivshmemDevice.Open(fd)) - return false; - - auto* ptr = (u8*)ivshmemDevice.GetPointer(); - - pHeader = (hazelnut::IvshHeader*)&ptr[0]; - pFrameHeader = (hazelnut::FrameHeader*)&ptr[0x1000]; - - sessionId = pHeader->serverSessionId.load(); - lastFrameSerial = 0; - return true; - } - - // NOTE: This function leaves the lock held on successful returns. - ResultCode TickOne() { - if(!pHeader) - return ResultCode::Fail; - - if(sessionId != pHeader->serverSessionId.load()) - return ResultCode::Fail; - - if(pHeader->lock.try_lock_manually()) { - // failed to lock - return ResultCode::LockContended; - } - - auto current = pFrameHeader->serial.load(); - if(current == lastFrameSerial) { - return ResultCode::Unchanged; - } - - lastFrameSerial = current; - return ResultCode::Changed; - } - - void Lock() { pHeader->lock.lock_manually(); } - - void Unlock() { pHeader->lock.unlock(); } - - u32* Framebuffer() { - if(pFrameHeader) { - return pFrameHeader->bits(); - } - - return nullptr; - } - - hazelnut::IvshmemDevice ivshmemDevice; - - // Protocol/memory stuff - hazelnut::IvshHeader* pHeader; - hazelnut::FrameHeader* pFrameHeader; - - u32 sessionId {}; - - u32 lastFrameSerial {}; -}; - -// Rust bindings +// Rust bindings to the C++ Hazelnut client library extern "C" { void* rust_new_hazelnut_client() { - return (void*)new HazelnutIvshmemClient; + return (void*)new hazelnut::Client; } void rust_destroy_hazelnut_client(void* pClient) { if(pClient) - delete(HazelnutIvshmemClient*)pClient; + delete(hazelnut::Client*)pClient; } bool rust_hazelnut_client_open(void* pClient, int fd) { if(pClient) { - return ((HazelnutIvshmemClient*)pClient)->Open(fd); + return ((hazelnut::Client*)pClient)->Open(fd); } return false; @@ -86,41 +22,41 @@ bool rust_hazelnut_client_open(void* pClient, int fd) { int rust_hazelnut_client_tick(void* pClient) { if(pClient) { - return (int)((HazelnutIvshmemClient*)pClient)->TickOne(); + return (int)((hazelnut::Client*)pClient)->TickOne(); } - return (int)ResultCode::Fail; + return (int)hazelnut::Client::ResultCode::Fail; } void rust_hazelnut_client_lock(void* pClient) { if(pClient) { - return ((HazelnutIvshmemClient*)pClient)->Lock(); + return ((hazelnut::Client*)pClient)->Lock(); } } void rust_hazelnut_client_unlock(void* pClient) { if(pClient) { - return ((HazelnutIvshmemClient*)pClient)->Unlock(); + return ((hazelnut::Client*)pClient)->Unlock(); } } u32* rust_hazelnut_client_get_framebuffer(void* pClient) { if(pClient) { - return ((HazelnutIvshmemClient*)pClient)->Framebuffer(); + return ((hazelnut::Client*)pClient)->Framebuffer(); } return nullptr; } u32 rust_hazelnut_client_get_width(void* pClient) { - if(pClient) { - return ((HazelnutIvshmemClient*)pClient)->pFrameHeader->width.load(); + if(pClient) { + return ((hazelnut::Client*)pClient)->GetWidth(); } - return 0; + return 0; } u32 rust_hazelnut_client_get_height(void* pClient) { - if(pClient) { - return ((HazelnutIvshmemClient*)pClient)->pFrameHeader->height.load(); + if(pClient) { + return ((hazelnut::Client*)pClient)->GetHeight(); } - return 0; + return 0; } } \ No newline at end of file diff --git a/shared/README.md b/shared/README.md index f0b71f7..3c8f272 100644 --- a/shared/README.md +++ b/shared/README.md @@ -2,4 +2,6 @@ This is all of the things shared between the agent and client ends. -It is cross platform C++20. \ No newline at end of file +It is cross platform C++20. + +(Some of this should be moved to a client-cpp directory, probably.) \ No newline at end of file diff --git a/shared/src/client.cpp b/shared/src/client.cpp new file mode 100644 index 0000000..61a4dd9 --- /dev/null +++ b/shared/src/client.cpp @@ -0,0 +1,78 @@ +#include "client.hpp" + +#include "atomic_spinlock.hpp" +#include "ivshmem_protocol.hpp" +#include "Utils.hpp" + +namespace hazelnut { + + bool Client::Open(int fd) { + // We probably should have this return a tuple of + // so that we can pass the errno to Rust in a semi-sane manner, + // and actually communicate that. However, Rust/the code opening the file + // will handle file open errors which is probably the brink of most of the errors possible. + if(!ivshmemDevice.Open(fd)) + return false; + + auto* ptr = (u8*)ivshmemDevice.GetPointer(); + + pHeader = (hazelnut::IvshHeader*)&ptr[0]; + pFrameHeader = (hazelnut::FrameHeader*)&ptr[0x1000]; + + sessionId = pHeader->serverSessionId.load(); + lastFrameSerial = 0; + return true; + } + + Client::ResultCode Client::TickOne() { + if(!pHeader) + return ResultCode::Fail; + + if(sessionId != pHeader->serverSessionId.load()) + return ResultCode::Fail; + + if(pHeader->lock.try_lock_manually()) { + // failed to lock + return ResultCode::LockContended; + } + + auto current = pFrameHeader->serial.load(); + if(current == lastFrameSerial) { + return ResultCode::Unchanged; + } + + lastFrameSerial = current; + return ResultCode::Changed; + } + + void Client::Lock() { + pHeader->lock.lock_manually(); + } + + void Client::Unlock() { + pHeader->lock.unlock(); + } + + u32* Client::Framebuffer() { + if(pFrameHeader) { + return pFrameHeader->bits(); + } + + return nullptr; + } + + u32 Client::GetWidth() { + if(pFrameHeader) + return pFrameHeader->width.load(); + + return -1; + } + + u32 Client::GetHeight() { + if(pFrameHeader) + return pFrameHeader->width.load(); + + return -1; + } + +} // namespace hazelnut \ No newline at end of file diff --git a/shared/src/client.hpp b/shared/src/client.hpp new file mode 100644 index 0000000..ba40173 --- /dev/null +++ b/shared/src/client.hpp @@ -0,0 +1,40 @@ +#pragma once +#include "ivshmem.hpp" + +namespace hazelnut { + struct IvshHeader; + struct FrameHeader; + + struct Client { + enum class ResultCode : u32 { Unchanged, Changed, LockContended, Fail }; + + bool Open(int fd); + + // NOTE: This function leaves the lock held on successful returns. + ResultCode TickOne(); + + void Lock(); + + void Unlock(); + + // Only call these methods when the lock is held. + + u32* Framebuffer(); + + u32 GetWidth(); + + u32 GetHeight(); + + private: + hazelnut::IvshmemDevice ivshmemDevice; + + // Protocol/memory stuff + hazelnut::IvshHeader* pHeader; + hazelnut::FrameHeader* pFrameHeader; + + u32 sessionId {}; + + u32 lastFrameSerial {}; + }; + +} // namespace hazelnut diff --git a/shared/src/ivshmem_protocol.hpp b/shared/src/ivshmem_protocol.hpp index a7ea519..63fe490 100644 --- a/shared/src/ivshmem_protocol.hpp +++ b/shared/src/ivshmem_protocol.hpp @@ -30,6 +30,10 @@ namespace hazelnut { std::atomic width{}; std::atomic height{}; + // FIXME: We should have a frame format, with the following possible types + // RGBA - The current RGBA backed framebuffefr + // H264 - NVENC H.264 (we should probably also have a flag for frame type as well) + // obtain bits at the next page boundary u32* bits() { return reinterpret_cast(reinterpret_cast(this) + 0x1000);