diff options
Diffstat (limited to 'packages/kernel/src')
| -rw-r--r-- | packages/kernel/src/rmlui_renderer_gl3.cpp | 33 | ||||
| -rw-r--r-- | packages/kernel/src/rmlui_renderer_gl3.h | 24 | ||||
| -rw-r--r-- | packages/kernel/src/server.cpp | 15 | ||||
| -rw-r--r-- | packages/kernel/src/server_impl.hpp | 1 | ||||
| -rw-r--r-- | packages/kernel/src/ui_substrate.cpp | 359 | ||||
| -rw-r--r-- | packages/kernel/src/ui_substrate.hpp | 44 |
6 files changed, 475 insertions, 1 deletions
diff --git a/packages/kernel/src/rmlui_renderer_gl3.cpp b/packages/kernel/src/rmlui_renderer_gl3.cpp index 7e192e3..dfb455f 100644 --- a/packages/kernel/src/rmlui_renderer_gl3.cpp +++ b/packages/kernel/src/rmlui_renderer_gl3.cpp @@ -1210,8 +1210,37 @@ struct TGAHeader { // Restore packing #pragma pack() +// unbox (slice-10 preview spike): map an externally-owned GL texture to a +// source URI so LoadTexture() resolves it. See header. +void RenderInterface_GL3::register_preview_texture(const Rml::String& source, unsigned int texture_id, Rml::Vector2i dimensions) +{ + preview_textures[source] = PreviewTexture{texture_id, dimensions}; + preview_texture_ids.insert(texture_id); +} + +void RenderInterface_GL3::unregister_preview_texture(const Rml::String& source) +{ + auto it = preview_textures.find(source); + if (it == preview_textures.end()) + return; + preview_texture_ids.erase(it->second.texture_id); + preview_textures.erase(it); +} + Rml::TextureHandle RenderInterface_GL3::LoadTexture(Rml::Vector2i& texture_dimensions, const Rml::String& source) { + // unbox: a preview URI resolves to its registered, externally-owned texture + // (the substrate's imported dmabuf->EGLImage->texture). Returns the GL id as + // the handle; ReleaseTexture() will skip deleting it (caller owns it). + { + auto it = preview_textures.find(source); + if (it != preview_textures.end()) + { + texture_dimensions = it->second.dimensions; + return (Rml::TextureHandle)it->second.texture_id; + } + } + Rml::FileInterface* file_interface = Rml::GetFileInterface(); Rml::FileHandle file_handle = file_interface->Open(source); if (!file_handle) @@ -1486,6 +1515,10 @@ void RenderInterface_GL3::RenderBlur(float sigma, const Gfx::FramebufferData& so void RenderInterface_GL3::ReleaseTexture(Rml::TextureHandle texture_handle) { + // unbox: never delete an externally-owned preview texture — its GL lifetime + // belongs to the substrate's Preview, which deletes it on its own teardown. + if (preview_texture_ids.count((unsigned int)texture_handle) != 0) + return; glDeleteTextures(1, (GLuint*)&texture_handle); } diff --git a/packages/kernel/src/rmlui_renderer_gl3.h b/packages/kernel/src/rmlui_renderer_gl3.h index 06cd152..73825ce 100644 --- a/packages/kernel/src/rmlui_renderer_gl3.h +++ b/packages/kernel/src/rmlui_renderer_gl3.h @@ -3,6 +3,8 @@ #include <RmlUi/Core/RenderInterface.h> #include <RmlUi/Core/Types.h> #include <bitset> +#include <unordered_map> +#include <unordered_set> enum class ProgramId; enum class UniformId; @@ -88,6 +90,17 @@ public: const Rml::Matrix4f& GetTransform() const; void ResetProgram(); + // unbox (slice-10 preview spike): register an externally-OWNED GL texture + // under a "source" string (the "unbox-preview://N" URI). LoadTexture() + // resolves that exact source to `texture_id` + `dimensions`, so an + // <img src="unbox-preview://N"> samples it. The texture's GL lifetime is the + // CALLER's (the substrate's Preview): ReleaseTexture() must NOT delete it + // when RmlUi drops its handle, so registered ids are tracked and skipped + // there. unregister_preview_texture() removes the mapping on Preview + // destruction (the caller deletes the GL texture itself, after). + void register_preview_texture(const Rml::String& source, unsigned int texture_id, Rml::Vector2i dimensions); + void unregister_preview_texture(const Rml::String& source); + private: void UseProgram(ProgramId program_id); int GetUniformLocation(UniformId uniform_id) const; @@ -175,6 +188,17 @@ private: RenderLayerStack render_layers; + // unbox (slice-10 preview spike): externally-owned preview textures, keyed + // by their "unbox-preview://N" source string. LoadTexture() resolves a hit + // here; ReleaseTexture() skips any id present in preview_texture_ids so the + // substrate's Preview keeps sole ownership of the GL texture's lifetime. + struct PreviewTexture { + unsigned int texture_id; + Rml::Vector2i dimensions; + }; + std::unordered_map<Rml::String, PreviewTexture> preview_textures; + std::unordered_set<unsigned int> preview_texture_ids; + struct GLStateBackup { bool enable_cull_face; bool enable_blend; diff --git a/packages/kernel/src/server.cpp b/packages/kernel/src/server.cpp index a5f8365..b302c68 100644 --- a/packages/kernel/src/server.cpp +++ b/packages/kernel/src/server.cpp @@ -81,6 +81,14 @@ auto Server::ui_fence_sync_active() const -> bool { return impl_->substrate != nullptr && impl_->substrate->fence_sync_active(); } +auto Server::ui_preview_import_is_dmabuf() const -> bool { + return impl_->substrate != nullptr && impl_->substrate->preview_import_is_dmabuf(); +} + +auto Server::ui_pixel(int x, int y) const -> unsigned int { + return impl_->substrate != nullptr ? impl_->substrate->surface_pixel(x, y) : 0U; +} + void Server::ui_set_touch_override(UiTouchOverride ov) { if (impl_->substrate == nullptr) { return; @@ -104,6 +112,13 @@ auto PerExtensionUi::create_surface(const UiSurfaceSpec& spec) -> std::unique_pt return server_->substrate->create_surface(id_, parent, spec); } +auto PerExtensionUi::create_preview(wlr_scene_tree* source) -> std::unique_ptr<Preview> { + if (server_->substrate == nullptr) { + return nullptr; + } + return server_->substrate->create_preview(source); +} + auto PerExtensionUi::available() const -> bool { return server_->substrate != nullptr && server_->substrate->available(); } diff --git a/packages/kernel/src/server_impl.hpp b/packages/kernel/src/server_impl.hpp index ef4a479..bcdb693 100644 --- a/packages/kernel/src/server_impl.hpp +++ b/packages/kernel/src/server_impl.hpp @@ -184,6 +184,7 @@ public: PerExtensionUi(Server::Impl* server, ExtensionId id) : server_(server), id_(id) {} auto create_surface(const UiSurfaceSpec& spec) -> std::unique_ptr<UiSurface> override; + auto create_preview(wlr_scene_tree* source) -> std::unique_ptr<Preview> override; auto available() const -> bool override; auto touch_mode() const -> bool override; void set_touch_mode_override(TouchModeOverride ov) override; diff --git a/packages/kernel/src/ui_substrate.cpp b/packages/kernel/src/ui_substrate.cpp index 91029f4..9c53770 100644 --- a/packages/kernel/src/ui_substrate.cpp +++ b/packages/kernel/src/ui_substrate.cpp @@ -353,6 +353,32 @@ struct Surface { int frame_count = 0; }; +// ---- PreviewState ----------------------------------------------------------- +// +// A frozen snapshot of a scene subtree, imported as a sampled GL texture in the +// RMLUi sibling context and registered under an "unbox-preview://N" URI. The +// snapshot is captured into an ARGB8888 LINEAR dmabuf by the wlr renderer +// (wlr_renderer_begin_buffer_pass), then that dmabuf is imported into the +// sibling context exactly like the surface path (EGLImage -> texture), but here +// the texture is SAMPLED by RmlUi rather than used as an FBO color attachment. +// This is the slice-3 bridge run in reverse (wlr pixels -> dmabuf -> EGLImage -> +// RmlUi texture). Lives in Substrate::Impl::previews (stable addresses). +struct PreviewState { + Substrate::Impl* owner = nullptr; + int id = 0; + std::string uri; + + wlr_scene_tree* source = nullptr; // borrow; valid only per call (caller's concern) + int width = 0; + int height = 0; + + // The snapshot dmabuf (held alive for the texture's life) + its import. + wlr_buffer* buffer = nullptr; // ARGB8888 LINEAR dmabuf (own_buffer) + EGLImageKHR image = EGL_NO_IMAGE_KHR; + GLuint tex = 0; // sampled by RmlUi via the URI registration + bool dmabuf = false; // true once a dmabuf import succeeded +}; + // ---- Substrate::Impl -------------------------------------------------------- struct Substrate::Impl { @@ -365,6 +391,12 @@ struct Substrate::Impl { std::list<Surface> surfaces; // stable addresses (handles borrow Surface*) + // Previews (slice-10 spike): stable addresses (PreviewHandle borrows a + // PreviewState*). `next_preview_id` numbers the "unbox-preview://N" URIs. + std::list<PreviewState> previews; + int next_preview_id = 0; + bool last_preview_dmabuf = false; // test probe: last import took the dmabuf path + // Pointer implicit grab: the consumer of the first button press owns the // whole press..release stream (standard seat behavior). `pointer_grab` // (pure) tracks owner + down-count; `pointer_grab_surface` is the ui surface @@ -423,6 +455,15 @@ struct Substrate::Impl { void render_surface(Surface& s); // caller holds context current void destroy_surface(Surface* s); + // Preview snapshot + import (caller holds the sibling context current). + // snapshot_into_buffer composites every WLR_SCENE_NODE_BUFFER under `source` + // into `p.buffer` via the wlr renderer; import_snapshot (re)imports that + // dmabuf as the sampled GL texture and registers the URI. Both return false + // (and clean up) on any failure. + bool snapshot_into_buffer(PreviewState& p); + bool import_snapshot(PreviewState& p); + void destroy_preview(PreviewState* p); + // Forward a synthesized pointer event into a surface's Rml context. Returns // whether RmlUi (or our hit-test) treats it as consumed. void ctx_motion(Surface& s, double lx, double ly); @@ -686,6 +727,218 @@ void Substrate::Impl::ctx_button(Surface& s, bool pressed) { } } +// ---- Preview snapshot + import ---------------------------------------------- +// +// The snapshot is captured by the wlr GLES2 renderer (NOT the sibling RMLUi +// context) into a LINEAR ARGB8888 dmabuf, then that dmabuf is imported into the +// sibling context as a sampled GL texture (slice-3 bridge in reverse). All wlr +// renderer work happens on the WLR EGL context; all import/texture work after +// the snapshot happens on the sibling context (the caller makes it current). + +namespace { + +// Recursively composite every enabled WLR_SCENE_NODE_BUFFER under `node` into +// `pass`, offset by the accumulated (ox,oy) from the snapshot tree's origin. +// Single-surface toplevels and simple subsurface stacks composite; per-node +// transform/clip/opacity beyond position is a documented follow-up. +void composite_buffers(wlr_scene_node* node, int ox, int oy, wlr_render_pass* pass, + wlr_renderer* renderer) { + if (!node->enabled) { + return; + } + const int x = ox + node->x; + const int y = oy + node->y; + if (node->type == WLR_SCENE_NODE_BUFFER) { + auto* sb = wlr_scene_buffer_from_node(node); + if (sb->buffer != nullptr) { + wlr_texture* tex = wlr_texture_from_buffer(renderer, sb->buffer); + if (tex != nullptr) { + const int w = sb->dst_width > 0 ? sb->dst_width : static_cast<int>(tex->width); + const int h = sb->dst_height > 0 ? sb->dst_height : static_cast<int>(tex->height); + wlr_render_texture_options opts{}; + opts.texture = tex; + opts.dst_box = wlr_box{x, y, w, h}; + opts.blend_mode = WLR_RENDER_BLEND_MODE_PREMULTIPLIED; + wlr_render_pass_add_texture(pass, &opts); + wlr_texture_destroy(tex); + } + } + } else if (node->type == WLR_SCENE_NODE_TREE) { + auto* tree = wlr_scene_tree_from_node(node); + wlr_scene_node* child = nullptr; + wl_list_for_each(child, &tree->children, link) { + composite_buffers(child, x, y, pass, renderer); + } + } +} + +// Natural pixel extent (max right/bottom of buffer nodes) of the subtree, in +// the tree's own coordinate space (origin 0,0). 0x0 if the tree has no buffers. +void tree_extent(wlr_scene_node* node, int ox, int oy, int& max_w, int& max_h) { + if (!node->enabled) { + return; + } + const int x = ox + node->x; + const int y = oy + node->y; + if (node->type == WLR_SCENE_NODE_BUFFER) { + auto* sb = wlr_scene_buffer_from_node(node); + if (sb->buffer != nullptr) { + const int w = sb->dst_width > 0 ? sb->dst_width : sb->buffer->width; + const int h = sb->dst_height > 0 ? sb->dst_height : sb->buffer->height; + max_w = std::max(max_w, x + w); + max_h = std::max(max_h, y + h); + } + } else if (node->type == WLR_SCENE_NODE_TREE) { + auto* tree = wlr_scene_tree_from_node(node); + wlr_scene_node* child = nullptr; + wl_list_for_each(child, &tree->children, link) { + tree_extent(child, x, y, max_w, max_h); + } + } +} + +} // namespace + +bool Substrate::Impl::snapshot_into_buffer(PreviewState& p) { + // Size the snapshot to the subtree's natural extent (relative to the tree + // origin: children offsets are relative to `source`, so start at 0,0). + int w = 0; + int h = 0; + tree_extent(&p.source->node, -p.source->node.x, -p.source->node.y, w, h); + if (w <= 0 || h <= 0) { + wlr_log(WLR_INFO, "ui-substrate: preview source has no pixels to snapshot"); + return false; + } + + // (Re)allocate the dmabuf if the extent changed (refresh of a resized + // toplevel). The buffer is LINEAR ARGB8888, same format the surface path + // uses, so the same EGL import preconditions apply. + if (p.buffer == nullptr || p.width != w || p.height != h) { + if (p.buffer != nullptr) { + wlr_buffer_drop(p.buffer); + p.buffer = nullptr; + } + wlr_drm_format fmt{}; + fmt.format = kDrmFormatArgb8888; + std::uint64_t modifiers[] = {0 /* DRM_FORMAT_MOD_LINEAR */}; + fmt.len = 1; + fmt.capacity = 1; + fmt.modifiers = modifiers; + p.buffer = wlr_allocator_create_buffer(allocator, w, h, &fmt); + if (p.buffer == nullptr) { + wlr_log(WLR_ERROR, "ui-substrate: preview dmabuf allocation failed"); + return false; + } + p.width = w; + p.height = h; + } + + // Composite the subtree's buffers into the dmabuf via the WLR renderer. This + // runs on the wlr renderer's own EGL context (the caller has the SIBLING + // context current for the import that follows; begin_buffer_pass switches to + // the wlr context internally and restores nothing — so we re-make-current + // the sibling context after submit, in import_snapshot's caller). + wlr_buffer_pass_options pass_opts{}; + wlr_render_pass* pass = wlr_renderer_begin_buffer_pass(renderer, p.buffer, &pass_opts); + if (pass == nullptr) { + wlr_log(WLR_ERROR, "ui-substrate: preview begin_buffer_pass failed"); + return false; + } + // Clear to transparent first (the toplevel may not cover the whole extent). + wlr_render_rect_options clear{}; + clear.box = wlr_box{0, 0, w, h}; + clear.color = wlr_render_color{0.f, 0.f, 0.f, 0.f}; + clear.blend_mode = WLR_RENDER_BLEND_MODE_NONE; + wlr_render_pass_add_rect(pass, &clear); + composite_buffers(&p.source->node, -p.source->node.x, -p.source->node.y, pass, renderer); + if (!wlr_render_pass_submit(pass)) { + wlr_log(WLR_ERROR, "ui-substrate: preview render_pass_submit failed"); + return false; + } + return true; +} + +bool Substrate::Impl::import_snapshot(PreviewState& p) { + // The caller holds the sibling context current. Re-import the dmabuf as a + // sampled texture (EGLImage -> glEGLImageTargetTexture2DOES), then register + // it under the URI so RmlUi's LoadTexture resolves <img src="unbox-...">. + if (!gl.dmabuf_import_ok || gl.egl_create_image == nullptr || + gl.gl_image_target_texture == nullptr) { + return false; + } + wlr_dmabuf_attributes attribs{}; + if (!wlr_buffer_get_dmabuf(p.buffer, &attribs) || attribs.n_planes < 1) { + wlr_log(WLR_ERROR, "ui-substrate: preview buffer has no dmabuf"); + return false; + } + EGLint ia[] = { + EGL_WIDTH, attribs.width, + EGL_HEIGHT, attribs.height, + EGL_LINUX_DRM_FOURCC_EXT, static_cast<EGLint>(attribs.format), + EGL_DMA_BUF_PLANE0_FD_EXT, attribs.fd[0], + EGL_DMA_BUF_PLANE0_OFFSET_EXT, static_cast<EGLint>(attribs.offset[0]), + EGL_DMA_BUF_PLANE0_PITCH_EXT, static_cast<EGLint>(attribs.stride[0]), + EGL_NONE, + }; + EGLImageKHR img = + gl.egl_create_image(gl.egl_display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr, ia); + if (img == EGL_NO_IMAGE_KHR) { + wlr_log(WLR_ERROR, "ui-substrate: preview eglCreateImageKHR failed (0x%x)", eglGetError()); + return false; + } + GLuint tex = 0; + glGenTextures(1, &tex); + glBindTexture(GL_TEXTURE_2D, tex); + gl.gl_image_target_texture(GL_TEXTURE_2D, static_cast<GLeglImageOES>(img)); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glBindTexture(GL_TEXTURE_2D, 0); + + // Replace any prior import (refresh): drop old registration + GL objects. + if (gl.render_iface) { + gl.render_iface->unregister_preview_texture(p.uri); + } + if (p.tex != 0) { + glDeleteTextures(1, &p.tex); + } + if (p.image != EGL_NO_IMAGE_KHR && gl.egl_destroy_image != nullptr) { + gl.egl_destroy_image(gl.egl_display, p.image); + } + p.tex = tex; + p.image = img; + p.dmabuf = true; + if (gl.render_iface) { + gl.render_iface->register_preview_texture(p.uri, p.tex, + Rml::Vector2i(p.width, p.height)); + } + return true; +} + +void Substrate::Impl::destroy_preview(PreviewState* p) { + const bool cur = gl.make_current(); + if (gl.render_iface) { + gl.render_iface->unregister_preview_texture(p->uri); + } + if (p->tex != 0) { + glDeleteTextures(1, &p->tex); + p->tex = 0; + } + if (p->image != EGL_NO_IMAGE_KHR && gl.egl_destroy_image != nullptr) { + gl.egl_destroy_image(gl.egl_display, p->image); + p->image = EGL_NO_IMAGE_KHR; + } + if (cur) { + gl.restore_current(); + } + if (p->buffer != nullptr) { + wlr_buffer_drop(p->buffer); + p->buffer = nullptr; + } + previews.remove_if([p](const PreviewState& e) { return &e == p; }); +} + // ---- Substrate (private surface) -------------------------------------------- auto Substrate::create(EGLDisplay egl_display, wlr_allocator* allocator, wlr_renderer* renderer, @@ -701,7 +954,14 @@ auto Substrate::create(EGLDisplay egl_display, wlr_allocator* allocator, wlr_ren Substrate::Substrate(std::unique_ptr<Impl> impl) : impl_(std::move(impl)) {} Substrate::~Substrate() { - // Destroy surfaces (GL + scene nodes) then the shared bridge. + // Destroy previews (imported texture+EGLImage+dmabuf+URI registration) and + // surfaces (GL + scene nodes) before the shared bridge. A surviving Preview + // handle would dangle after this, but the contract (ui.hpp) is that the + // substrate outlives every Preview an extension holds (it is kernel-owned + // and torn down after extensions in Server::Impl::shutdown). + while (!impl_->previews.empty()) { + impl_->destroy_preview(&impl_->previews.front()); + } while (!impl_->surfaces.empty()) { impl_->destroy_surface(&impl_->surfaces.front()); } @@ -771,6 +1031,47 @@ auto Substrate::create_surface(ExtensionId who, wlr_scene_tree* parent, const Ui return std::make_unique<SurfaceHandle>(this, &s); } +auto Substrate::create_preview(wlr_scene_tree* source) -> std::unique_ptr<Preview> { + impl_->last_preview_dmabuf = false; + if (!impl_->available() || source == nullptr) { + return nullptr; + } + + impl_->previews.emplace_back(); + PreviewState& p = impl_->previews.back(); + p.owner = impl_.get(); + p.id = ++impl_->next_preview_id; + p.uri = "unbox-preview://" + std::to_string(p.id); + p.source = source; + + // 1) Composite the subtree into our LINEAR ARGB8888 dmabuf via the WLR + // renderer (its own EGL context). NO sibling context current here: + // begin_buffer_pass drives the wlr renderer's GL. + if (!impl_->snapshot_into_buffer(p)) { + impl_->destroy_preview(&p); + return nullptr; + } + + // 2) Import the dmabuf into the sibling RMLUi context as a sampled texture + // and register the URI. The sibling context must be current for the + // EGLImage/texture/RmlUi-registration work; save+restore the wlr context. + if (!impl_->gl.make_current()) { + impl_->destroy_preview(&p); + return nullptr; + } + const bool imported = impl_->import_snapshot(p); + impl_->gl.restore_current(); + if (!imported) { + impl_->destroy_preview(&p); + return nullptr; + } + + impl_->last_preview_dmabuf = p.dmabuf; + return std::make_unique<PreviewHandle>(this, &p); +} + +auto Substrate::preview_import_is_dmabuf() const -> bool { return impl_->last_preview_dmabuf; } + void Substrate::tick_all() { if (!impl_->available() || impl_->surfaces.empty()) { return; @@ -937,6 +1238,35 @@ auto Substrate::fence_sync_active() const -> bool { return impl_->gl.fence_ok && impl_->gl.dmabuf_import_ok; } +auto Substrate::surface_pixel(int x, int y) const -> std::uint32_t { + // Read the first rendered surface's current FBO at (x,y) via glReadPixels on + // the sibling context — works for BOTH the shm and dmabuf paths (the FBO's + // color attachment is the surface's submitted texture in either case), so + // this probe is independent of the per-surface path choice. row0 = GL + // bottom; the buffer is top-first (the renderer V-flips on composite), so + // map document-y -> GL-y = (h-1-y). R,G,B,A. + for (const Surface& s : impl_->surfaces) { + if (s.fbo == 0 || s.frame_count == 0) { + continue; + } + if (x < 0 || y < 0 || x >= s.width || y >= s.height) { + return 0; + } + const bool cur = impl_->gl.make_current(); + std::uint8_t rgba[4] = {0, 0, 0, 0}; + glBindFramebuffer(GL_FRAMEBUFFER, s.fbo); + glReadPixels(x, s.height - 1 - y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, rgba); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + if (cur) { + impl_->gl.restore_current(); + } + return (static_cast<std::uint32_t>(rgba[0]) << 24) | + (static_cast<std::uint32_t>(rgba[1]) << 16) | + (static_cast<std::uint32_t>(rgba[2]) << 8) | static_cast<std::uint32_t>(rgba[3]); + } + return 0; +} + auto Substrate::orientation() const -> int { for (const Surface& s : impl_->surfaces) { if (s.dmabuf || s.shm == nullptr || s.frame_count == 0) { @@ -1094,4 +1424,31 @@ void SurfaceHandle::dirty() { } } +// ---- PreviewHandle (public Preview impl) ------------------------------------ + +PreviewHandle::~PreviewHandle() { substrate_->impl_->destroy_preview(state_); } + +auto PreviewHandle::source_uri() const -> std::string { return state_->uri; } +auto PreviewHandle::source_width() const -> int { return state_->width; } +auto PreviewHandle::source_height() const -> int { return state_->height; } + +void PreviewHandle::refresh() { + // Re-snapshot from the original source IF it is still valid. Borrow validity + // is the caller's concern (ui.hpp: refresh after source destruction is UB); + // we guard only against an obviously unusable substrate. A failed re-snapshot + // or re-import leaves the previous frozen snapshot + URI registration intact. + Substrate::Impl& impl = *substrate_->impl_; + if (!impl.available() || state_->source == nullptr) { + return; + } + if (!impl.snapshot_into_buffer(*state_)) { + return; + } + if (!impl.gl.make_current()) { + return; + } + impl.import_snapshot(*state_); + impl.gl.restore_current(); +} + } // namespace unbox::kernel diff --git a/packages/kernel/src/ui_substrate.hpp b/packages/kernel/src/ui_substrate.hpp index 082417a..cdba997 100644 --- a/packages/kernel/src/ui_substrate.hpp +++ b/packages/kernel/src/ui_substrate.hpp @@ -49,6 +49,32 @@ class Substrate; // the concrete UiSubstrate, defined in ui_substrate.cpp // list of them and the public SurfaceHandle can borrow one. struct Surface; +// One preview's private state (snapshot dmabuf + imported EGLImage/texture + +// RmlUi URI registration). Defined in ui_substrate.cpp; declared here so the +// public PreviewHandle can borrow one out of the Substrate's list. +struct PreviewState; + +// Concrete Preview handed to an extension. A thin, owning handle over a +// PreviewState that lives in the Substrate's list; destruction frees the GL +// texture + EGLImage + dmabuf and unregisters the URI. +class PreviewHandle final : public Preview { +public: + PreviewHandle(Substrate* substrate, PreviewState* state) + : substrate_(substrate), state_(state) {} + ~PreviewHandle() override; + PreviewHandle(const PreviewHandle&) = delete; + auto operator=(const PreviewHandle&) -> PreviewHandle& = delete; + + [[nodiscard]] auto source_uri() const -> std::string override; + [[nodiscard]] auto source_width() const -> int override; + [[nodiscard]] auto source_height() const -> int override; + void refresh() override; + +private: + Substrate* substrate_; + PreviewState* state_; +}; + // Concrete UiSurface handed to an extension. A thin, owning handle over a // Surface that lives in the Substrate's list; destruction removes the Surface // (document + scene node). Per-extension (carries no id itself — its Surface @@ -103,6 +129,12 @@ public: auto create_surface(ExtensionId who, wlr_scene_tree* parent, const UiSurfaceSpec& spec) -> std::unique_ptr<UiSurface>; + // Snapshot the pixels under `source` into a dmabuf imported as a sampled GL + // texture in the RMLUi context, registered under an "unbox-preview://N" + // URI. Returns nullptr if unavailable or the snapshot/import failed. Never + // throws. (See ui.hpp UiSubstrate::create_preview for the public contract.) + auto create_preview(wlr_scene_tree* source) -> std::unique_ptr<Preview>; + // Render every dirty surface (called from the output frame handler). void tick_all(); @@ -136,6 +168,17 @@ public: // (no glFinish on the hot path) — lets the suite assert the production sync. [[nodiscard]] auto fence_sync_active() const -> bool; + // ---- preview test instrumentation (kernel suite only) ---- + // Whether the last create_preview imported the snapshot via the dmabuf -> + // EGLImage -> sampled texture path (Plan A) rather than failing. Lets the + // suite assert the spike's GO path engaged on hardware that supports it. + [[nodiscard]] auto preview_import_is_dmabuf() const -> bool; + // Packed 0xRRGGBBAA of the first shm-path surface's submitted buffer at + // layout pixel (x,y) (readback row 0 = top). 0 if no shm surface / no frame + // / out of bounds. A position-aware probe (like orientation()) so the suite + // can assert a preview's known source color landed at the expected spot. + [[nodiscard]] auto surface_pixel(int x, int y) const -> std::uint32_t; + struct Impl; private: @@ -143,6 +186,7 @@ private: std::unique_ptr<Impl> impl_; friend class SurfaceHandle; + friend class PreviewHandle; }; } // namespace unbox::kernel |
