surfman

repository·main·Indexed 41 days ago

https://github.com/servo/surfman

A cross-platform, low-level Rust toolkit for GPU surface management. It provides efficient, thread-safe, and low-copy graphics infrastructure for managing GPU image surfaces across multiple platforms, including Windows, macOS, Linux, and Android. It supports OpenGL (via native WGL, CGL, GLX, or ANGLE), Metal, and CPU rendering, enabling cross-thread texture sharing and surface presentation for projects like the Servo browser.

Tokens
6K
Snippets
9
Records
26
Agent score
50%

What's inside surfman

  1. Overview of surfman

    main

    surfman is a low-level, cross-platform Rust library designed for managing surfaces—blocks of image data residing in GPU memory. It serves as the graphics infrastructure for the Servo browser, enabling the porting of WebGL and WebXR code across different platforms.

    Key capabilities include:

    • CPU Rendering: Drawing to a window (e.g., using winit) on the CPU.
    • OpenGL Rendering: Rendering to an existing window or an off-screen surface.
    • Cross-thread Texture Sharing: Using a surface created on one thread as an OpenGL texture on another thread.
    • Platform-specific GPU APIs: Drawing to a surface using APIs like Metal.
  2. What surfman is not

    main

    To use surfman correctly, understand its boundaries:

    • Not a full GPU rendering API: It does not abstract over rendering libraries like OpenGL, Metal, or Direct3D. For full rendering abstraction, use gfx-rs.
    • Not a windowing solution: It cannot open windows itself. You must pair it with a windowing crate like winit to manage window creation and lifecycle.
    • Not a UI toolkit: It is not a replacement for toolkits like GTK+. It is intended to be used alongside UI toolkits to integrate GPU rendering efficiently.
  3. What is surfman?

    main

    surfman is a cross-platform GPU device and surface management library. It is designed to allow multithreaded graphics applications to share textures efficiently across multiple threads.

    Unlike libraries such as SDL, GLFW, winit, or Glutin, surfman is a lightweight framework focused specifically on initializing rendering. It does not manage windowing or the event loop.

  4. What are Surfaces and SurfaceTextures in surfman?

    main

    In surfman, pixel buffers are managed through two primary abstractions: Surface and SurfaceTexture.

    Surface

    A Surface represents a hardware buffer of pixels. It can be rendered to via the CPU or GPU and is used for either displaying in a native widget or being bound to a texture.

    There are two types of surfaces:

    • Generic Surfaces: Can be bound to a texture for reading, but cannot be displayed in a widget directly.
    • Widget Surfaces: Can be displayed in a widget, but cannot be bound to a texture.

    Key constraints:

    • Surfaces are tied to a specific Context. You cannot render to a surface from a different context than the one used to create it.
    • Manual Destruction Required: You must call destroy_surface() to clean up a surface. If the surface is dropped without being explicitly destroyed, the application will panic.

    SurfaceTexture

    A SurfaceTexture is an OpenGL texture that wraps a Surface.

    Key constraints:

    • Read-only: Reading from the associated OpenGL texture reads from the underlying surface. Writing to this texture (e.g., binding it to a framebuffer and rendering to it) is undefined behavior.
    • Context Flexibility: While the Surface is tied to a specific context, a SurfaceTexture can be used in a different context, provided that context shares the same adapter and connection.
    • Manual Destruction Required: You must call destroy_surface_texture() to clean up. Failure to do so will result in a panic.
  5. How context and surface management work together

    main

    The Device abstraction coordinates the relationship between Context and Surface.

    1. Ownership: A Surface is owned by the Device and must be bound to a Context to be used for rendering.
    2. Rendering Loop:
      • Make Context current.
      • Bind Surface to Context.
      • Get SurfaceInfo to find the framebuffer object.
      • Bind the framebuffer via glBindFramebuffer.
      • Issue draw calls.
      • Unbind Surface (to flush) or call present_surface (for widgets).
    3. Inter-context communication: To move data between contexts, a generic Surface is converted into a SurfaceTexture via create_surface_texture. This allows one context to treat the output of another as a standard OpenGL texture.
  6. How Surfaces and SurfaceTextures work together

    main

    A Surface is a hardware pixel buffer that can be rendered to via CPU or GPU. Surfaces are categorized into two types:

    1. Generic Surfaces: Can be bound to a texture for reading by other contexts but cannot be displayed directly in a native widget.
    2. Widget Surfaces: Can be displayed in a native widget but cannot be bound to a texture.

    Key Lifecycle and Access Rules:

    • Rendering: A Surface can only be rendered to by the specific Context used to create it.
    • Reading: While rendering is restricted to the owning context, a generic Surface can be read from by any context (on any thread) if it is wrapped in a SurfaceTexture.
    • Memory Management: Both Surface and SurfaceTexture must be explicitly destroyed using destroy_surface() and destroy_surface_texture() respectively. Failing to call these methods will result in a panic during drop.
    • Double Buffering: Depending on the platform, surfaces may be internally double-buffered. For widget surfaces, you must call present_surface() to make changes visible in the associated widget.
  7. Configure surface CPU access with SurfaceAccess

    main

    When creating or managing a surface, you must specify its SurfaceAccess level. This determines whether the CPU has direct memory access to the surface's pixel data.

    • GPUOnly: The surface is accessible by the GPU only. This is the recommended setting for better performance as it allows the GPU to use texture swizzling. Calling lock_surface_data() on a GPUOnly surface will return a SurfaceDataInaccessible error.
    • GPUCPU: The surface data is accessible by both the GPU and the CPU.
    • GPUCPUWriteCombined: The surface is accessible by both, with optimizations for sequential writes (filling every byte in a range). This is optimized for writing, not reading. Note that while this doesn't affect correctness on x86, failing to follow sequential write patterns can cause severe performance penalties.
    use surfman::SurfaceAccess;
    
    // Example of choosing access levels
    let access = SurfaceAccess::GPUOnly; // Recommended for performance
    let access = SurfaceAccess::GPUCPU; // Use if CPU needs to read/write pixels
    let access = SurfaceAccess::GPUCPUWriteCombined; // Use for optimized sequential CPU writes
  8. Core surfman types and abstractions

    main

    The surfman public API provides abstractions for managing GPU connections, devices, contexts, and surfaces. The library uses a default backend pattern that varies by platform (e.g., angle on Windows, cgl on macOS, wayland on Linux, or hardware_buffer on Android/OHOS).

    Key exported types include:

    • Connection: The primary interface to the graphics subsystem.
    • Device & Adapter: Represent the GPU hardware and its capabilities.
    • Context & ContextDescriptor: Manage the state and configuration of the rendering context.
    • Surface & SurfaceTexture: Manage the presentation surfaces and the textures associated with them.
    • NativeWidget: Represents the platform-specific window or widget used for surface presentation.
  9. How Contexts and ContextDescriptors work

    main

    In surfman, a Context represents an OpenGL rendering context that allows you to issue rendering commands to a Surface. A Context is local to a single thread and device.

    To create a context, you must first create a ContextDescriptor (often referred to as a "config" or "pixel format") using the Device. The ContextDescriptor contains the specific attributes required by the underlying API for that device.

    Key Lifecycle Rules:

    • Surface Attachment: A new context has no attached surface. You must use bind_surface_to_context to attach a surface before rendering commands will have any effect.
    • Ownership: A context takes ownership of the surfaces attached to it. To perform non-rendering operations on a surface (like presenting it to a window), you must first detach it using unbind_surface_from_context.
    • Thread Locality: Contexts are local to a single thread. Use make_context_current to make a context active for the current thread.
    • Destruction: Contexts must be explicitly destroyed using destroy_context(); failing to do so will cause a panic.
    • Sharing: You can create a context that shares resources with another by passing an existing context to the share_with parameter in create_context.
    // Conceptual workflow:
    let descriptor = device.create_context_descriptor(&attributes)?;
    let mut context = device.create_context(&descriptor, None)?;
    device.make_context_current(&mut context)?;
    
    device.bind_surface_to_context(&mut context, surface)?;
    // ... perform rendering ...
    device.present_bound_surface(&mut context)?;
    
    device.unbind_surface_from_context(&mut context)?;
    device.destroy_context(&mut context)?;
  10. Supported platforms and graphics APIs

    main

    surfman provides support for the following platform/API combinations:

    PlatformGraphics API
    WindowsOpenGL via native WGL
    WindowsOpenGL via Google's ANGLE
    macOSOpenGL via native CGL
    macOSMetal
    Linux/UnixOpenGL on Wayland
    Linux/UnixOpenGL on X11 via GLX
    Android (P and up)OpenGL
    GenericCPU rendering of OpenGL via OSMesa framework
  11. Reference: surfman public types

    main

    The following types are exported by the surfman crate for managing GPU resources and platform surfaces.

    pub use crate::context::{ContextAttributeFlags, ContextAttributes, ContextID};
    pub use crate::error::{Error, WindowingApiError};
    pub use crate::info::{GLApi, GLVersion};
    pub use crate::surface::{SurfaceAccess, SurfaceID, SurfaceInfo, SurfaceType, SystemSurfaceInfo};
    pub use default::connection::Connection;
    pub use default::context::{Context, ContextDescriptor};
    pub use default::device::{Adapter, Device};
    pub use default::surface::{NativeWidget, Surface, SurfaceTexture};
    
    // macOS specific system types
    #[cfg(target_os = "macos")]
    pub use base::io_surface::connection::Connection as SystemConnection;
    #[cfg(target_os = "macos")]
    pub use base::io_surface::device::{Adapter as SystemAdapter, Device as SystemDevice};
    #[cfg(target_os = "macos")]
    pub use base::io_surface::surface::Surface as SystemSurface;
  12. Get OpenGL texture details from a SurfaceTexture

    main

    When using a SurfaceTexture, you can retrieve the underlying OpenGL texture and its target for sampling.

    • surface_texture_object(&self, surface_texture: &SurfaceTexture) -> Option<Texture>: Returns the OpenGL texture object. Warning: This texture is read-only; writing to it is undefined behavior.
    • surface_gl_texture_target(&self) -> u32: Returns the required OpenGL texture target (e.g., GL_TEXTURE_2D or GL_TEXTURE_RECTANGLE).