Skip to content

Latest commit

 

History

History
189 lines (136 loc) · 7.42 KB

README.md

File metadata and controls

189 lines (136 loc) · 7.42 KB

Euc

crates.io crates.io License actions-badge

Utah teapot, rendered with shadow-mapping and phong shading at 60 fps

Example

// A type that specifies a rendering pipeline.
// You can add fields to this type, like uniforms in traditional GPU shader programs.
struct Triangle;

impl<'r> Pipeline<'r> for Triangle {
    type Vertex = [f32; 2]; // Each vertex has an x and y component
    type VertexData = Unit; // No data is passed from the vertex shader to the fragment shader
    type Primitives = TriangleList; // Our vertices come in the form of a list of triangles
    type Fragment = [u8; 3]; // Each fragment is 3 bytes: red, green, and blue
    type Pixel = [u8; 3]; // Color buffer pixels have the same format as fragments.

    // Vertex shader (determines the screen-space position of each vertex)
    fn vertex(&self, pos: &Self::Vertex<'_>) -> ([f32; 4], Self::VertexData) {
        ([pos[0], pos[1], 0.0, 1.0], Unit)
    }

    // Fragment shader (determines the colour of each triangle fragment)
    fn fragment(&self, _: Self::VertexData) -> Self::Fragment {
        [255, 0, 0] // Paint the triangle red
    }

    // Blend shader (determines how to blend new fragments into the existing colour buffer)
    fn blend(&self, _: Self::Pixel, col: Self::Fragment) -> Self::Pixel {
        col // Just replace the color buffer's previous pixel
    }
}

// Create a new color buffer to render to
let mut color = Buffer2d::new([640, 480], [0; 3]);

Triangle.render(
    // Specify the coordinates of the triangle's corners
    &[[-1.0, -1.0], [1.0, -1.0], [0.0, 1.0]],
    // Specify the color buffer to render to
    &mut color,
    // We have no need for a depth buffer, so use `Empty` as a substitute
    &mut Empty::default(),
);

What is euc?

euc is a software-rendering crate for rendering 3D scenes on the CPU. The API is designed to be clean, powerful, and heavily leans on Rust's type system to ensure correct usage.

euc is fast enough to render simple scenes in real-time and supports thread-based parallelism to accelerate rendering.

Features

  • Write shaders in Rust (vertex, geometry, fragment and blend shaders)
  • Multithreading support for parallel rendering acceleration
  • Many supported primitives and vertex formats (triangle lists, line pairs, etc.)
  • N-dimensional textures and samplers (including support for filtering, clamping, tiling, mirroring, etc.)
  • Customisable coordinate space (choose compatibility with OpenGL, Vulkan, DirectX, or Metal)
  • Built-in support for index buffers
  • MSAA (Multi-Sample Anti-Aliasing)

Why?

Below are a few circumstances in which you might want to use euc.

Learning and experimentation

Modern graphics APIs are complex, verbose beasts. The code required to set them up properly requires a lot of bureaucratic mumbo jumbo. This problem has only become worse with the latest iteration of graphics APIs. Vulkan's canonical 'Hello Triangle' example is, when shader code is included, 994 lines of code. Compare that to euc's 34. This is obviously not without tradeoff: Vulkan is a powerful modern graphics API that's designed for high-performance GPGPU on a vast array of hardware while euc is simply a humble software-renderer.

However, for someone new to 3D rendering that wants to explore new techniques without all of the bureaucratic cruft, euc might well serve a useful purpose.

Static images, pre-renders, and UI

euc may not be well-suited to high-performance real-time graphics, but there are many applications that don't require this. For example, Veloren currently uses euc to pre-render 3D item models as icons to be displayed in-game later.

euc is also more than fast enough for soft real-time applications such as UI rendering, particularly for state-driven UIs that only update when events occur. In addition, tricky rendering problems like font rasterization become much simpler to solve on the CPU.

Embedded

euc doesn't require std to run. Are you writing a toy OS? Do you want a pretty UI with a 3D graphics API but you don't want to write your own GPU drivers? euc has you covered, even for simple real-time graphics.

Testing

euc doesn't need a GPU to run. Most servers and CI machines don't have access to one either. If you want to check the consistency of some rendered output, euc is perfect for the job.

Unconventional environments

Access to render surfaces for most modern graphics APIs is done through a window manager or similar such component that manages access to framebuffers. Don't have access to that because you're writing a 3D command-line game? euc might be what you're looking for. In addition, euc uses Rust's type system to allow rendering to unconventional framebuffer formats. Now you can render to a char buffer!

Relaxed data access patterns

GPUs are brilliantly fast, but there's a reason that we don't use them for everything: they're also heavily optimised around very specific kinds of data manipulation. CPUs are more general-purpose and as a result, euc allows much more dynamic access to rendering resources.

Coordinate System

By default, euc uses a left-handed coordinate system with 0-1 z clipping (like Vulkan). However, both of these properties can be changed independently and euc provides coordinate system constants that correspond to those of common graphics APIs such as:

  • CoordinateMode::VULKAN
  • CoordinateMode::OPENGL
  • CoordinateMode::METAL
  • CoordinateMode::DIRECTX

Note that using these constants do not change the coordinates of things like texture samplers, yet.

Release Mode

Cargo, by default, compiles Rust code in debug mode. In this mode, very few optimisations are made upon the code, and as a result the performance of software rendering tends to suffer. To experience this project with good performance, make sure to compile with the --release flag.

no_std

euc can be compiled on platforms that lack standard library support. This makes it ideal for rendering 3D graphics on embedded devices. You can enable no_std support by disabling the default features and enabling the libm feature in your Cargo.toml file like so:

[dependencies]
euc = { version = "x.y.z", default-features = false, features = ["libm"] }

Goals

  • Support programmable shaders written in Rust.

  • Support common pipeline features such as texture samplers, multiple rendering passes, uniform data, etc.

  • Simple, elegant API that scales well and doesn't get in the way.

  • Correctness, where doing so doesn't significantly compromise performance.

Non-Goals

  • Extreme optimisation (although obvious low-hanging fruit will be picked).

  • Compliance/compatibility with an existing API (i.e: OpenGL).

License

euc is distributed under either of:

at the discretion of the user.