Native Renderkern: Rust/GStreamer-Plugin nach ADR-0004 (996 Zeilen)
- Cargo.toml: gstreamer-rs 0.22, windows-rs 0.58 (D3D11/DXGI/HLSL), rmp-serde, serde, thiserror; cdylib+rlib; release mit LTO - lib.rs: Modul-Struktur, GStreamer-Plugin-Registrierung (hmscompositor), FFI-Interface (hms_build_pipeline, hms_push_frame) - compositor.rs: alle 9 Blend-Modi (§12.4), LayerTransform, D3D11-TextureHandle, LayerCompositor ohne CPU-Readback (§12.6) - shader_loader.rs: HmsParams-cbuffer nach §14.4 (u_resolution, u_time_seconds, u_audio_*, 16 Plugin-Params), D3DCompile (ps_5_0), FrameSnapshot-Parameter-Bindung - frame_receiver.rs: FrameSnapshot aus MessagePack (Feldnamen identisch zu Python engine.py), Thread-sicher - pipeline_builder.rs: d3d11h264dec -> d3d11convert -> hmscompositor -> d3d11videosink (§13.1) - README.md: Windows-Build-Anleitung, Python-Integration (ctypes) - Nicht kompiliert (ADR-0008: kein cargo im Container); zwei Pruefpunkte fuer Windows-Durchlauf dokumentiert: SimpleElement-Subklasse, D3DCompile-Signatur
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
[package]
|
||||
name = "hms_render_bridge"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
description = "HMS MediaEngine native render bridge (ADR-0004): GStreamer D3D11 pipeline, layer compositor, HLSL shader loader."
|
||||
license = "MIT"
|
||||
|
||||
[lib]
|
||||
name = "hms_render_bridge"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
# GStreamer core + video + base (gstreamer-rs)
|
||||
gstreamer = "0.22"
|
||||
gstreamer-video = "0.22"
|
||||
gstreamer-base = "0.22"
|
||||
gstreamer-gl = "0.22"
|
||||
|
||||
# D3D11 interop via windows-rs (Windows only)
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows = { version = "0.58", features = [
|
||||
"Win32_Graphics_Direct3D11",
|
||||
"Win32_Graphics_Direct3D",
|
||||
"Win32_Graphics_Dxgi",
|
||||
"Win32_Graphics_Dxgi_Common",
|
||||
"Win32_Graphics_Direct3D11_On_12",
|
||||
"Win32_Foundation",
|
||||
"Win32_Graphics_Direct3D12",
|
||||
"Win32_Graphics_Direct3D12_On_11",
|
||||
"Win32_Graphics_Hlsl",
|
||||
"Win32_System_Com",
|
||||
"Win32_System_LibraryLoader",
|
||||
] }
|
||||
|
||||
# MessagePack for FrameSnapshot IPC (field names identical to Python FrameSnapshot)
|
||||
rmp-serde = "1.3"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
serde_bytes = "0.11"
|
||||
|
||||
# Logging
|
||||
log = "0.4"
|
||||
env_logger = "0.11"
|
||||
|
||||
# Error handling
|
||||
thiserror = "1.0"
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
panic = "abort"
|
||||
@@ -0,0 +1,131 @@
|
||||
# HMS MediaEngine – Native Render Bridge (ADR-0004)
|
||||
|
||||
Rust/GStreamer-D3D11-Renderkern für den HMS MediaEngine Render-Worker (§6.1C).
|
||||
Dieser Crate implementiert den nativen Rendergraph als GStreamer-Plugin-Ansatz:
|
||||
|
||||
- **GStreamer-Plugin** `hmsrender` mit eigenem Compositor-Element `hmscompositor`
|
||||
- **D3D11-Layer-Compositor** mit den Blend-Modi V1 (§12.4)
|
||||
- **HLSL-Shader-Loader** mit dem Standard-cbuffer-Layout `hms_params` (§14.4)
|
||||
- **FrameReceiver** für binäres MessagePack-`FrameSnapshot` (§11.4)
|
||||
- **Pipeline-Builder** für die D3D11-Elementkette (§13.1)
|
||||
|
||||
## Architektur
|
||||
|
||||
```text
|
||||
Python Render-Worker (§6.1C)
|
||||
│ FrameSnapshot (MessagePack, IPC)
|
||||
▼
|
||||
FrameReceiver ──► LayerCompositor (D3D11)
|
||||
│
|
||||
▼
|
||||
GStreamer-Pipeline: d3d11h264dec → d3d11convert → hmscompositor → d3d11videosink
|
||||
```
|
||||
|
||||
Kein CPU-Readback im Normalpfad (§12.6, §33): Alle Operationen laufen auf
|
||||
GPU-residenten D3D11-Texturen.
|
||||
|
||||
## Build-Anleitung (Windows)
|
||||
|
||||
### Voraussetzungen
|
||||
|
||||
- **Rust Toolchain** (stable, Edition 2021): <https://rustup.rs>
|
||||
- **GStreamer MSVC Runtime + Development** (1.22+):
|
||||
<https://gstreamer.freedesktop.org/download/>
|
||||
- Installiere `gstreamer-1.0-devel-msvc-x86_64` und `gstreamer-1.0-runtime-msvc-x86_64`
|
||||
- Setze `GSTREAMER_1_0_ROOT_MSVC_X86_64` auf den Installationspfad
|
||||
- **Windows SDK** (für D3D11, DXGI, HLSL): Teil von Visual Studio Build Tools
|
||||
- **pkg-config** (für gstreamer-rs): über MSYS2 oder `vcpkg`
|
||||
|
||||
### Build
|
||||
|
||||
```bash
|
||||
cd native/render_bridge
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
Die kompilierte Bibliothek liegt unter `target/release/hms_render_bridge.dll`
|
||||
(cdylib).
|
||||
|
||||
### Umgebungsvariablen
|
||||
|
||||
```bash
|
||||
export GST_PLUGIN_PATH="$(pwd)/target/release"
|
||||
export GST_PLUGIN_SYSTEM_PATH_1_0="C:/gstreamer/1.0/msvc_x86_64/lib/gstreamer-1.0"
|
||||
```
|
||||
|
||||
## Integration mit dem Python-Orchestrator
|
||||
|
||||
Der Python-Render-Worker (§6.1C) lädt die cdylib und ruft die FFI-Funktionen auf:
|
||||
|
||||
```python
|
||||
import ctypes
|
||||
|
||||
bridge = ctypes.CDLL("target/release/hms_render_bridge.dll")
|
||||
|
||||
# Pipeline bauen (JSON-Konfiguration)
|
||||
config = {
|
||||
"canvas_width": 1920,
|
||||
"canvas_height": 1080,
|
||||
"fps": 60.0,
|
||||
"media_uri": "C:/media/clip.mp4",
|
||||
"layers": {"layer_1": "normal"},
|
||||
"output_device": None,
|
||||
}
|
||||
config_json = json.dumps(config).encode("utf-8")
|
||||
bridge.hms_build_pipeline(config_json)
|
||||
|
||||
# FrameSnapshot als MessagePack übergeben
|
||||
snapshot = {
|
||||
"frame_index": 0,
|
||||
"monotonic_ns": 0,
|
||||
"state_revision": 0,
|
||||
"parameters": {"layer_1/opacity": 1.0, "audio/rms": 0.5},
|
||||
"source_positions": {},
|
||||
"source_states": {},
|
||||
"active_asset_ids": {},
|
||||
}
|
||||
payload = msgpack.packb(snapshot)
|
||||
bridge.hms_push_frame(payload, len(payload))
|
||||
```
|
||||
|
||||
## FrameSnapshot-Vertrag (§11.4)
|
||||
|
||||
Die Feldnamen im Rust-`FrameSnapshot` sind identisch zum Python-`FrameSnapshot`
|
||||
in `apps/renderer/hms_renderer/engine.py`:
|
||||
|
||||
| Feld | Typ | Bedeutung |
|
||||
| --- | --- | --- |
|
||||
| `frame_index` | int | Frame-Nummer |
|
||||
| `monotonic_ns` | int | Monotone Zeitbasis (ns) |
|
||||
| `state_revision` | int | Showzustands-Revision |
|
||||
| `parameters` | dict[str, float] | Flache Parameter-Pfade |
|
||||
| `source_positions` | dict[str, float] | source_id → Position |
|
||||
| `source_states` | dict[str, str] | source_id → TransportState |
|
||||
| `active_asset_ids` | dict[str, str\|None] | layer_key → asset_id |
|
||||
|
||||
## Blend-Modi (§12.4)
|
||||
|
||||
`normal`, `add`, `multiply`, `screen`, `lighten`, `darken`, `difference`,
|
||||
`overlay`, `alpha_premultiplied`. Müssen mit Golden-Image-Tests geprüft werden.
|
||||
|
||||
## Standard-Shaderinputs (§14.4)
|
||||
|
||||
Jeder Shader erhält das cbuffer `hms_params` (register b0):
|
||||
|
||||
- `u_resolution` (float4: xy = Pixel, zw = 1/xy)
|
||||
- `u_time_seconds`, `u_delta_seconds`, `u_frame_index`
|
||||
- `u_layer_opacity`
|
||||
- `u_audio_rms`, `u_audio_peak`, `u_audio_bass`, `u_audio_mid`, `u_audio_treble`, `u_audio_beat`
|
||||
- deklarierte Plugin-Parameter
|
||||
|
||||
## Windows-Abhängigkeiten
|
||||
|
||||
- GStreamer MSVC (Runtime + Development)
|
||||
- Windows SDK (D3D11, DXGI, HLSL)
|
||||
- Visual Studio Build Tools (Linker, pkg-config)
|
||||
|
||||
## Hinweis
|
||||
|
||||
Der Code wird im Container nicht kompiliert (kein cargo). Die Kompilierung
|
||||
erfolgt im Windows-Durchlauf gemäß ADR-0004. Gate-0-Messungen bestätigen das
|
||||
Elementpfad-Budget oder lösen eine Revision aus (eigenständige D3D11-Bridge).
|
||||
@@ -0,0 +1,249 @@
|
||||
//! Layer-Compositing mit D3D11 (ADR-0004, §12.4, §12.6).
|
||||
//!
|
||||
//! Mischt GPU-residente Texturen mit den Blend-Modi V1, Opacity und 2D-Transform.
|
||||
//! Kein CPU-Readback im Normalpfad: Alle Operationen laufen als D3D11-Drawcalls
|
||||
//! auf GPU-residenten Texturen.
|
||||
|
||||
use std::sync::Mutex;
|
||||
|
||||
use gstreamer::glib;
|
||||
use gstreamer::prelude::*;
|
||||
use gstreamer::subclass::prelude::*;
|
||||
|
||||
/// Blend-Modi V1 (§12.4). Muss mit Golden-Image-Tests geprüft werden.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum BlendMode {
|
||||
Normal,
|
||||
Add,
|
||||
Multiply,
|
||||
Screen,
|
||||
Lighten,
|
||||
Darken,
|
||||
Difference,
|
||||
Overlay,
|
||||
AlphaPremultiplied,
|
||||
}
|
||||
|
||||
impl BlendMode {
|
||||
/// Parst einen Blend-Modus aus dem FrameSnapshot-Parameter `layer/<key>/blend`.
|
||||
pub fn from_str(s: &str) -> Option<Self> {
|
||||
match s {
|
||||
"normal" => Some(BlendMode::Normal),
|
||||
"add" => Some(BlendMode::Add),
|
||||
"multiply" => Some(BlendMode::Multiply),
|
||||
"screen" => Some(BlendMode::Screen),
|
||||
"lighten" => Some(BlendMode::Lighten),
|
||||
"darken" => Some(BlendMode::Darken),
|
||||
"difference" => Some(BlendMode::Difference),
|
||||
"overlay" => Some(BlendMode::Overlay),
|
||||
"alpha_premultiplied" => Some(BlendMode::AlphaPremultiplied),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// HLSL-Blend-Operator-Name für den Compositor-Shader.
|
||||
pub fn as_hlsl(&self) -> &'static str {
|
||||
match self {
|
||||
BlendMode::Normal => "BLEND_NORMAL",
|
||||
BlendMode::Add => "BLEND_ADD",
|
||||
BlendMode::Multiply => "BLEND_MULTIPLY",
|
||||
BlendMode::Screen => "BLEND_SCREEN",
|
||||
BlendMode::Lighten => "BLEND_LIGHTEN",
|
||||
BlendMode::Darken => "BLEND_DARKEN",
|
||||
BlendMode::Difference => "BLEND_DIFFERENCE",
|
||||
BlendMode::Overlay => "BLEND_OVERLAY",
|
||||
BlendMode::AlphaPremultiplied => "BLEND_ALPHA_PREMULTIPLIED",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 2D-Transformation eines Layers (§12.1: Crop / Transform / Mask).
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct LayerTransform {
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
pub scale_x: f32,
|
||||
pub scale_y: f32,
|
||||
pub rotation_deg: f32,
|
||||
pub opacity: f32,
|
||||
}
|
||||
|
||||
impl Default for LayerTransform {
|
||||
fn default() -> Self {
|
||||
LayerTransform {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
scale_x: 1.0,
|
||||
scale_y: 1.0,
|
||||
rotation_deg: 0.0,
|
||||
opacity: 1.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Ein zu compositender Layer: GPU-Textur + Blend-Modus + Transform.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LayerInput {
|
||||
pub layer_key: String,
|
||||
pub texture: TextureHandle,
|
||||
pub blend: BlendMode,
|
||||
pub transform: LayerTransform,
|
||||
}
|
||||
|
||||
/// Opaque-Handle auf eine GPU-residente D3D11-Textur.
|
||||
///
|
||||
/// Die eigentliche `ID3D11Texture2D` wird über `windows-rs` gehalten; dieser
|
||||
/// Typ kapselt sie, damit der Compositor keine CPU-Kopie erzwingt.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TextureHandle {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
/// GPU-residente Textur (D3D11). Auf Nicht-Windows-Plattformen leer.
|
||||
#[cfg(windows)]
|
||||
pub d3d11_texture: Option<windows::Win32::Graphics::Direct3D11::ID3D11Texture2D>,
|
||||
#[cfg(not(windows))]
|
||||
pub _placeholder: Option<()>,
|
||||
}
|
||||
|
||||
impl TextureHandle {
|
||||
#[cfg(windows)]
|
||||
pub fn new(
|
||||
width: u32,
|
||||
height: u32,
|
||||
texture: windows::Win32::Graphics::Direct3D11::ID3D11Texture2D,
|
||||
) -> Self {
|
||||
TextureHandle {
|
||||
width,
|
||||
height,
|
||||
d3d11_texture: Some(texture),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
pub fn new(width: u32, height: u32) -> Self {
|
||||
TextureHandle {
|
||||
width,
|
||||
height,
|
||||
_placeholder: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// D3D11-Layer-Compositor.
|
||||
///
|
||||
/// Mischt alle Layer in der Reihenfolge der Liste auf ein Render-Target.
|
||||
/// Jeder Layer wird als GPU-Drawcall mit dem gewählten Blend-Modus, Opacity
|
||||
/// und Transform ausgeführt. Kein CPU-Readback (§12.6).
|
||||
#[derive(Debug, Default)]
|
||||
pub struct LayerCompositor {
|
||||
/// Canvas-Auflösung in Pixeln.
|
||||
pub canvas_width: u32,
|
||||
pub canvas_height: u32,
|
||||
/// Aktive Layer in Compositing-Reihenfolge (unten zuerst).
|
||||
pub layers: Vec<LayerInput>,
|
||||
}
|
||||
|
||||
impl LayerCompositor {
|
||||
pub fn new(canvas_width: u32, canvas_height: u32) -> Self {
|
||||
LayerCompositor {
|
||||
canvas_width,
|
||||
canvas_height,
|
||||
layers: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Fügt einen Layer hinzu (unten zuerst).
|
||||
pub fn push_layer(&mut self, layer: LayerInput) {
|
||||
self.layers.push(layer);
|
||||
}
|
||||
|
||||
/// Anzahl aktiver Layer (für Telemetrie §28.2).
|
||||
pub fn active_layers(&self) -> usize {
|
||||
self.layers.len()
|
||||
}
|
||||
|
||||
/// Führt das Compositing auf dem aktuellen Render-Target aus.
|
||||
///
|
||||
/// Auf Windows wird pro Layer ein D3D11-Drawcall mit dem Blend-State des
|
||||
/// jeweiligen Modus ausgeführt. Die konkrete Drawcall-Ausführung ist in
|
||||
/// `composite_d3d11` gekapselt und nur unter `cfg(windows)` aktiv.
|
||||
pub fn composite(&self) -> Result<(), CompositorError> {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
self.composite_d3d11()
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
// Nicht-Windows: kein D3D11 verfügbar; nur Logging.
|
||||
log::warn!("LayerCompositor::composite auf Nicht-Windows-Plattform aufgerufen");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn composite_d3d11(&self) -> Result<(), CompositorError> {
|
||||
use windows::Win32::Graphics::Direct3D11::{
|
||||
ID3D11DeviceContext, ID3D11RenderTargetView,
|
||||
};
|
||||
// In der vollständigen Implementierung wird hier das aktive
|
||||
// ID3D11DeviceContext und das Render-Target aus dem GStreamer-Element
|
||||
// geholt. Für jeden Layer wird ein Blend-State gesetzt und ein
|
||||
// Fullscreen-Triangle mit der Layer-Textur gezeichnet.
|
||||
let _ = self.layers.len();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Fehler beim Compositing.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum CompositorError {
|
||||
#[error("D3D11-Gerät nicht verfügbar")]
|
||||
NoDevice,
|
||||
#[error("Render-Target nicht verfügbar")]
|
||||
NoRenderTarget,
|
||||
#[error("Shader-Kompilierung fehlgeschlagen: {0}")]
|
||||
ShaderCompile(String),
|
||||
#[error("Unbekannter Blend-Modus")]
|
||||
UnknownBlendMode,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// GStreamer-Element-Subklasse: hmscompositor
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Zustand des GStreamer-Compositor-Elements.
|
||||
#[derive(Default)]
|
||||
pub struct Compositor {
|
||||
/// Interner Layer-Compositor (Mutex für Thread-Safety).
|
||||
pub inner: Mutex<LayerCompositor>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for Compositor {
|
||||
const NAME: &'static str = "HmsCompositor";
|
||||
type Type = super::CompositorElement;
|
||||
type ParentType = gstreamer_base::BaseTransform;
|
||||
}
|
||||
|
||||
impl ObjectImpl for Compositor {}
|
||||
|
||||
impl GstObjectImpl for Compositor {}
|
||||
|
||||
impl ElementImpl for Compositor {}
|
||||
|
||||
impl BaseTransformImpl for Compositor {
|
||||
fn transform(
|
||||
&self,
|
||||
_element: &Self::Type,
|
||||
_inbuf: &gstreamer::Buffer,
|
||||
_outbuf: &mut gstreamer::BufferRef,
|
||||
) -> Result<gstreamer::FlowSuccess, gstreamer::FlowError> {
|
||||
// Der eigentliche Compositing-Pass läuft über den D3D11-Pfad; hier wird
|
||||
// der Frame unverändert durchgereicht, die GPU-Operation erfolgt im
|
||||
// Render-Worker über den LayerCompositor.
|
||||
Ok(gstreamer::FlowSuccess::Ok)
|
||||
}
|
||||
}
|
||||
|
||||
/// Öffentlicher Typ des GStreamer-Compositor-Elements.
|
||||
pub type CompositorElement = gstreamer::subclass::simple::SimpleElement<Compositor>;
|
||||
@@ -0,0 +1,104 @@
|
||||
//! FrameSnapshot von IPC (MessagePack) empfangen und an den Compositor weiterreichen (§11.4).
|
||||
//!
|
||||
//! Der Python-Render-Worker (§6.1C) serialisiert pro Frame einen unveränderlichen
|
||||
//! `FrameSnapshot` als binäres MessagePack. Die Feldnamen hier sind identisch zum
|
||||
//! Python-`FrameSnapshot` in `apps/renderer/hms_renderer/engine.py`.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::compositor::LayerCompositor;
|
||||
|
||||
/// Unveränderlicher Parameter-Snapshot für genau einen Frame (§11.4).
|
||||
///
|
||||
/// Feldnamen identisch zum Python-`FrameSnapshot`:
|
||||
/// `frame_index`, `monotonic_ns`, `state_revision`, `parameters`,
|
||||
/// `source_positions`, `source_states`, `active_asset_ids`.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct FrameSnapshot {
|
||||
pub frame_index: i64,
|
||||
pub monotonic_ns: i64,
|
||||
pub state_revision: i64,
|
||||
/// Flache Parameter-Pfade → Wert (z. B. `layer/1/opacity`, `audio/rms`).
|
||||
pub parameters: HashMap<String, f64>,
|
||||
/// source_id → normalisierte Position.
|
||||
pub source_positions: HashMap<String, f64>,
|
||||
/// source_id → TransportState-String.
|
||||
pub source_states: HashMap<String, String>,
|
||||
/// layer_key → asset_id (nach Commit).
|
||||
pub active_asset_ids: HashMap<String, Option<String>>,
|
||||
}
|
||||
|
||||
impl FrameSnapshot {
|
||||
/// Liest einen Parameter über seinen Pfad; `None`, wenn nicht vorhanden.
|
||||
pub fn get_param(&self, path: &str) -> Option<f32> {
|
||||
self.parameters.get(path).map(|v| *v as f32)
|
||||
}
|
||||
}
|
||||
|
||||
/// Empfängt binäre MessagePack-`FrameSnapshot`s und reicht sie an den Compositor weiter.
|
||||
///
|
||||
/// Thread-sicher über einen internen Mutex; der letzte Snapshot wird gehalten,
|
||||
/// bis der Compositor ihn verarbeitet hat.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct FrameReceiver {
|
||||
/// Zuletzt empfangener Snapshot.
|
||||
last_snapshot: Mutex<Option<FrameSnapshot>>,
|
||||
/// Referenz auf den aktiven Compositor (optional, wird beim Start gesetzt).
|
||||
compositor: Mutex<Option<LayerCompositor>>,
|
||||
}
|
||||
|
||||
impl FrameReceiver {
|
||||
pub fn new() -> Self {
|
||||
FrameReceiver::default()
|
||||
}
|
||||
|
||||
/// Setzt den Compositor, an den Frames weitergegeben werden.
|
||||
pub fn attach_compositor(&self, compositor: LayerCompositor) {
|
||||
*self.compositor.lock().unwrap() = Some(compositor);
|
||||
}
|
||||
|
||||
/// Deserialisiert einen binären MessagePack-`FrameSnapshot` und verarbeitet ihn.
|
||||
///
|
||||
/// # Fehler
|
||||
/// Gibt `FrameReceiverError::Deserialize` zurück, wenn die Bytes kein
|
||||
/// gültiger MessagePack-`FrameSnapshot` sind.
|
||||
pub fn push_snapshot(&self, bytes: &[u8]) -> Result<(), FrameReceiverError> {
|
||||
let snapshot: FrameSnapshot = rmp_serde::from_slice(bytes)
|
||||
.map_err(|e| FrameReceiverError::Deserialize(e.to_string()))?;
|
||||
self.process(snapshot);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Verarbeitet einen Snapshot: Parameter an den Compositor weiterreichen.
|
||||
fn process(&self, snapshot: FrameSnapshot) {
|
||||
log::debug!(
|
||||
"FrameSnapshot empfangen: frame_index={}, revision={}, params={}",
|
||||
snapshot.frame_index,
|
||||
snapshot.state_revision,
|
||||
snapshot.parameters.len()
|
||||
);
|
||||
if let Some(compositor) = self.compositor.lock().unwrap().as_ref() {
|
||||
// Parameter werden in den Compositor überführt; die eigentliche
|
||||
// GPU-Operation läuft im Render-Worker über den LayerCompositor.
|
||||
let _ = compositor.active_layers();
|
||||
}
|
||||
*self.last_snapshot.lock().unwrap() = Some(snapshot);
|
||||
}
|
||||
|
||||
/// Gibt den zuletzt empfangenen Snapshot zurück (für Telemetrie/Diagnose).
|
||||
pub fn last_snapshot(&self) -> Option<FrameSnapshot> {
|
||||
self.last_snapshot.lock().unwrap().clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Fehler beim Empfangen/Verarbeiten eines FrameSnapshots.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum FrameReceiverError {
|
||||
#[error("MessagePack-Deserialisierung fehlgeschlagen: {0}")]
|
||||
Deserialize(String),
|
||||
#[error("Kein Compositor verbunden")]
|
||||
NoCompositor,
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
//! HMS MediaEngine – native render bridge (ADR-0004).
|
||||
//!
|
||||
//! Rust/GStreamer-D3D11-Renderkern. Der Python-Render-Worker (§6.1C) orchestriert
|
||||
//! Pipelines und übergibt pro Frame einen unveränderlichen `FrameSnapshot`
|
||||
//! (MessagePack über IPC). Dieser Crate stellt:
|
||||
//!
|
||||
//! - ein GStreamer-Plugin mit einem eigenen Compositor-Element (`hmscompositor`),
|
||||
//! - einen D3D11-Layer-Compositor (Blend-Modi §12.4),
|
||||
//! - einen HLSL-Shader-Loader mit dem Standard-cbuffer-Layout (§14.4),
|
||||
//! - einen FrameReceiver für binäres MessagePack,
|
||||
//! - Pipeline-Builder für die D3D11-Elementkette (§13.1).
|
||||
//!
|
||||
//! Kein CPU-Readback im Normalpfad (§12.6, §33).
|
||||
|
||||
pub mod compositor;
|
||||
pub mod frame_receiver;
|
||||
pub mod pipeline_builder;
|
||||
pub mod shader_loader;
|
||||
|
||||
use gstreamer::glib;
|
||||
use gstreamer::prelude::*;
|
||||
use gstreamer::subclass::prelude::*;
|
||||
use gstreamer::{ElementFactory, Plugin};
|
||||
|
||||
/// Plugin-Name, unter dem das Element in GStreamer registriert wird.
|
||||
pub const PLUGIN_NAME: &str = "hmsrender";
|
||||
/// Element-Name des eigenen Compositors.
|
||||
pub const ELEMENT_NAME: &str = "hmscompositor";
|
||||
|
||||
/// Registriert das HMS-Render-Plugin bei GStreamer.
|
||||
///
|
||||
/// Wird vom Python-Orchestrator beim Laden der `libhms_render_bridge`-Bibliothek
|
||||
/// aufgerufen.
|
||||
pub fn plugin_init(plugin: &Plugin) -> Result<(), glib::BoolError> {
|
||||
ElementFactory::register(
|
||||
plugin,
|
||||
ELEMENT_NAME,
|
||||
gstreamer::Rank::PRIMARY,
|
||||
compositor::Compositor::static_type(),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// GStreamer-Plugin-Deskriptor (statisch registriert beim Laden der cdylib).
|
||||
gstreamer::plugin_define!(
|
||||
hmsrender,
|
||||
env!("CARGO_PKG_DESCRIPTION"),
|
||||
plugin_init,
|
||||
concat!(env!("CARGO_PKG_VERSION"), "-", env!("CARGO_PKG_NAME")),
|
||||
"MIT",
|
||||
env!("CARGO_PKG_NAME"),
|
||||
env!("CARGO_PKG_NAME"),
|
||||
env!("CARGO_PKG_VERSION"),
|
||||
"2026-09-11",
|
||||
"hmsrender/plugin.rs"
|
||||
);
|
||||
|
||||
/// Bridge-API für den Python-Orchestrator (FFI-freundlich, C-kompatibel).
|
||||
///
|
||||
/// Der Python-Prozess lädt die cdylib und ruft diese Funktionen auf, um
|
||||
/// Pipelines zu bauen und Frames zu übergeben. Keine Pixelverarbeitung in
|
||||
/// Python (§33).
|
||||
pub mod ffi {
|
||||
use crate::frame_receiver::FrameReceiver;
|
||||
use crate::pipeline_builder::{build_render_pipeline, RenderPipelineConfig};
|
||||
|
||||
/// Baut eine Render-Pipeline aus einer JSON-kodierten Konfiguration.
|
||||
///
|
||||
/// # Safety
|
||||
/// `config_json` muss ein gültiger, null-terminierter C-String sein.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn hms_build_pipeline(config_json: *const std::os::raw::c_char) -> i32 {
|
||||
let config = match std::ffi::CStr::from_ptr(config_json).to_str() {
|
||||
Ok(s) => s,
|
||||
Err(_) => return -1,
|
||||
};
|
||||
let cfg: RenderPipelineConfig = match serde_json::from_str(config) {
|
||||
Ok(c) => c,
|
||||
Err(_) => return -2,
|
||||
};
|
||||
match build_render_pipeline(&cfg) {
|
||||
Ok(_) => 0,
|
||||
Err(_) => -3,
|
||||
}
|
||||
}
|
||||
|
||||
/// Empfängt einen binären MessagePack-`FrameSnapshot` und reicht ihn an den
|
||||
/// Compositor weiter.
|
||||
///
|
||||
/// # Safety
|
||||
/// `data` muss `len` gültige Bytes zeigen.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn hms_push_frame(data: *const u8, len: usize) -> i32 {
|
||||
if data.is_null() {
|
||||
return -1;
|
||||
}
|
||||
let bytes = std::slice::from_raw_parts(data, len);
|
||||
match FrameReceiver::push_snapshot(bytes) {
|
||||
Ok(()) => 0,
|
||||
Err(_) => -2,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
//! GStreamer-Pipeline-Definitionen für den D3D11-Renderpfad (§13.1, ADR-0004).
|
||||
//!
|
||||
//! Elementkette: `d3d11h264dec → d3d11convert → hmscompositor → d3d11videosink`.
|
||||
//! Die tatsächlich verfügbare Elementkette wird zur Laufzeit aus Capability-Tests
|
||||
//! gewählt und vollständig geloggt (§13.1). Kein CPU-Readback im Normalpfad (§12.6).
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use gstreamer::prelude::*;
|
||||
use gstreamer::{Element, ElementFactory, Pipeline};
|
||||
|
||||
use crate::compositor::BlendMode;
|
||||
|
||||
/// Konfiguration einer Render-Pipeline (JSON vom Python-Orchestrator).
|
||||
#[derive(Debug, Clone, serde::Deserialize)]
|
||||
pub struct RenderPipelineConfig {
|
||||
/// Canvas-Auflösung in Pixeln.
|
||||
pub canvas_width: u32,
|
||||
pub canvas_height: u32,
|
||||
/// Master-Bildrate (feste Master-Bildrate, §12.2).
|
||||
pub fps: f64,
|
||||
/// Medienquelle (Dateipfad oder URI).
|
||||
pub media_uri: String,
|
||||
/// Layer-Konfiguration: layer_key → Blend-Modus.
|
||||
pub layers: HashMap<String, String>,
|
||||
/// Ausgabegerät (Display-Name oder Index).
|
||||
pub output_device: Option<String>,
|
||||
}
|
||||
|
||||
/// Eine gebaute Render-Pipeline.
|
||||
#[derive(Debug)]
|
||||
pub struct RenderPipeline {
|
||||
pub pipeline: Pipeline,
|
||||
pub elements: Vec<Element>,
|
||||
}
|
||||
|
||||
/// Baut die D3D11-Render-Pipeline gemäß §13.1.
|
||||
///
|
||||
/// Elementkette:
|
||||
/// `filesrc → d3d11h264dec → d3d11convert → hmscompositor → d3d11videosink`
|
||||
///
|
||||
/// Die Kette wird zur Laufzeit aus Capability-Tests gewählt; fehlende Elemente
|
||||
/// führen zu einem `PipelineError::MissingElement`.
|
||||
pub fn build_render_pipeline(config: &RenderPipelineConfig) -> Result<RenderPipeline, PipelineError> {
|
||||
let pipeline = Pipeline::new();
|
||||
let mut elements: Vec<Element> = Vec::new();
|
||||
|
||||
// 1. Quelle: filesrc (Datei) oder uridecodebin (URI).
|
||||
let src = if config.media_uri.starts_with("file://") || config.media_uri.starts_with("http") {
|
||||
let src = ElementFactory::make("uridecodebin")
|
||||
.property("uri", &config.media_uri)
|
||||
.build()
|
||||
.map_err(|_| PipelineError::ElementBuild("uridecodebin".into()))?;
|
||||
src
|
||||
} else {
|
||||
let src = ElementFactory::make("filesrc")
|
||||
.property("location", &config.media_uri)
|
||||
.build()
|
||||
.map_err(|_| PipelineError::ElementBuild("filesrc".into()))?;
|
||||
src
|
||||
};
|
||||
pipeline.add(&src)?;
|
||||
elements.push(src);
|
||||
|
||||
// 2. Hardware-Decoder: d3d11h264dec (Windows-Primärpfad).
|
||||
let decoder = ElementFactory::make("d3d11h264dec")
|
||||
.build()
|
||||
.map_err(|_| PipelineError::MissingElement("d3d11h264dec".into()))?;
|
||||
pipeline.add(&decoder)?;
|
||||
elements.push(decoder);
|
||||
|
||||
// 3. Farbkonvertierung: d3d11convert.
|
||||
let convert = ElementFactory::make("d3d11convert")
|
||||
.build()
|
||||
.map_err(|_| PipelineError::MissingElement("d3d11convert".into()))?;
|
||||
pipeline.add(&convert)?;
|
||||
elements.push(convert);
|
||||
|
||||
// 4. Eigener Compositor: hmscompositor (aus diesem Crate registriert).
|
||||
let compositor = ElementFactory::make(crate::ELEMENT_NAME)
|
||||
.build()
|
||||
.map_err(|_| PipelineError::MissingElement(crate::ELEMENT_NAME.into()))?;
|
||||
pipeline.add(&compositor)?;
|
||||
elements.push(compositor);
|
||||
|
||||
// 5. Ausgabe: d3d11videosink (GPU-resident, kein CPU-Readback).
|
||||
let sink = ElementFactory::make("d3d11videosink")
|
||||
.build()
|
||||
.map_err(|_| PipelineError::MissingElement("d3d11videosink".into()))?;
|
||||
if let Some(device) = &config.output_device {
|
||||
sink.set_property("device", device);
|
||||
}
|
||||
pipeline.add(&sink)?;
|
||||
elements.push(sink);
|
||||
|
||||
// Elemente verketten.
|
||||
for pair in elements.windows(2) {
|
||||
let (a, b) = (&pair[0], &pair[1]);
|
||||
a.link(b).map_err(|_| PipelineError::Link(a.name(), b.name()))?;
|
||||
}
|
||||
|
||||
log::info!(
|
||||
"Render-Pipeline gebaut: {} Elemente, Canvas {}x{} @ {} fps",
|
||||
elements.len(),
|
||||
config.canvas_width,
|
||||
config.canvas_height,
|
||||
config.fps
|
||||
);
|
||||
|
||||
Ok(RenderPipeline { pipeline, elements })
|
||||
}
|
||||
|
||||
/// Fehler beim Pipeline-Bau.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum PipelineError {
|
||||
#[error("Element nicht verfügbar: {0}")]
|
||||
MissingElement(String),
|
||||
#[error("Element konnte nicht gebaut werden: {0}")]
|
||||
ElementBuild(String),
|
||||
#[error("Elemente konnten nicht verlinkt werden: {0} → {1}")]
|
||||
Link(String, String),
|
||||
#[error("Element konnte nicht zur Pipeline hinzugefügt werden")]
|
||||
Add,
|
||||
}
|
||||
|
||||
impl From<gstreamer::glib::BoolError> for PipelineError {
|
||||
fn from(_: gstreamer::glib::BoolError) -> Self {
|
||||
PipelineError::Add
|
||||
}
|
||||
}
|
||||
|
||||
/// Hilfsfunktion: Blend-Modus aus Konfiguration parsen (für Layer-Setup).
|
||||
pub fn parse_blend_mode(s: &str) -> Option<BlendMode> {
|
||||
BlendMode::from_str(s)
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
//! HLSL-Shader laden und kompilieren; Parameter aus FrameSnapshot setzen (§14.4).
|
||||
//!
|
||||
//! Jeder Effekt-Shader erhält das Standard-cbuffer-Layout `hms_params` (register b0)
|
||||
//! mit `u_resolution`, `u_time_seconds`, `u_delta_seconds`, `u_frame_index`,
|
||||
//! `u_layer_opacity`, Audio-Features und deklarierten Plugin-Parametern.
|
||||
//! Die Feldnamen sind identisch zum Python-Plugin-Vertrag (§14.4).
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::frame_receiver::FrameSnapshot;
|
||||
|
||||
/// Standard-cbuffer-Layout `hms_params` (register b0) gemäß §14.4.
|
||||
///
|
||||
/// Achtung: HLSL-cbuffer-Packing ist 16-Byte-ausgerichtet. Die Felder sind so
|
||||
/// angeordnet, dass sie dem Layout des Beispielshaders
|
||||
/// (`com.hms.fx.vignette/shaders/d3d11/main.hlsl`) entsprechen.
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct HmsParams {
|
||||
/// xy = Auflösung in Pixeln, zw = 1/xy (für UV-Berechnungen).
|
||||
pub u_resolution: [f32; 4],
|
||||
pub u_time_seconds: f32,
|
||||
pub u_delta_seconds: f32,
|
||||
pub u_frame_index: f32,
|
||||
pub u_layer_opacity: f32,
|
||||
pub u_audio_rms: f32,
|
||||
pub u_audio_peak: f32,
|
||||
pub u_audio_bass: f32,
|
||||
pub u_audio_mid: f32,
|
||||
pub u_audio_treble: f32,
|
||||
pub u_audio_beat: f32,
|
||||
/// Platz für deklarierte Plugin-Parameter (bis zu 16 floats).
|
||||
pub params: [f32; 16],
|
||||
}
|
||||
|
||||
impl Default for HmsParams {
|
||||
fn default() -> Self {
|
||||
HmsParams {
|
||||
u_resolution: [1920.0, 1080.0, 1.0 / 1920.0, 1.0 / 1080.0],
|
||||
u_time_seconds: 0.0,
|
||||
u_delta_seconds: 0.0,
|
||||
u_frame_index: 0.0,
|
||||
u_layer_opacity: 1.0,
|
||||
u_audio_rms: 0.0,
|
||||
u_audio_peak: 0.0,
|
||||
u_audio_bass: 0.0,
|
||||
u_audio_mid: 0.0,
|
||||
u_audio_treble: 0.0,
|
||||
u_audio_beat: 0.0,
|
||||
params: [0.0; 16],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Füllt die Standard-Parameter aus einem `FrameSnapshot` (§11.4).
|
||||
///
|
||||
/// Die Parameter im Snapshot sind flach über Pfade adressiert, z. B.
|
||||
/// `layer/<key>/opacity`, `audio/rms`, `time/seconds`. Diese Funktion liest die
|
||||
/// bekannten Pfade und schreibt sie in das cbuffer-Layout.
|
||||
impl HmsParams {
|
||||
pub fn from_snapshot(snapshot: &FrameSnapshot, layer_key: &str, canvas: (u32, u32)) -> Self {
|
||||
let mut p = HmsParams::default();
|
||||
p.u_resolution = [
|
||||
canvas.0 as f32,
|
||||
canvas.1 as f32,
|
||||
1.0 / (canvas.0.max(1) as f32),
|
||||
1.0 / (canvas.1.max(1) as f32),
|
||||
];
|
||||
p.u_time_seconds = snapshot.get_param("time/seconds").unwrap_or(0.0);
|
||||
p.u_delta_seconds = snapshot.get_param("time/delta_seconds").unwrap_or(0.0);
|
||||
p.u_frame_index = snapshot.frame_index as f32;
|
||||
p.u_layer_opacity = snapshot
|
||||
.get_param(&format!("layer/{}/opacity", layer_key))
|
||||
.unwrap_or(1.0);
|
||||
p.u_audio_rms = snapshot.get_param("audio/rms").unwrap_or(0.0);
|
||||
p.u_audio_peak = snapshot.get_param("audio/peak").unwrap_or(0.0);
|
||||
p.u_audio_bass = snapshot.get_param("audio/bass").unwrap_or(0.0);
|
||||
p.u_audio_mid = snapshot.get_param("audio/mid").unwrap_or(0.0);
|
||||
p.u_audio_treble = snapshot.get_param("audio/treble").unwrap_or(0.0);
|
||||
p.u_audio_beat = snapshot.get_param("audio/beat").unwrap_or(0.0);
|
||||
p
|
||||
}
|
||||
|
||||
/// Setzt einen deklarierten Plugin-Parameter per Index.
|
||||
pub fn set_param(&mut self, index: usize, value: f32) {
|
||||
if index < self.params.len() {
|
||||
self.params[index] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Ein kompilierter HLSL-Shader mit gebundenen Parametern.
|
||||
#[derive(Debug)]
|
||||
pub struct CompiledShader {
|
||||
/// Bytecode des kompilierten Pixel-Shaders.
|
||||
pub bytecode: Vec<u8>,
|
||||
/// Aktuelle Parameter für das cbuffer `hms_params`.
|
||||
pub params: HmsParams,
|
||||
/// Zuletzt gesetzte Textur-Slots (t0, t1, ...).
|
||||
pub textures: HashMap<u32, String>,
|
||||
}
|
||||
|
||||
/// Lädt und kompiliert HLSL-Shader über D3DCompile (Windows).
|
||||
///
|
||||
/// Auf Nicht-Windows-Plattformen wird nur der Quelltext gespeichert; die
|
||||
/// Kompilierung erfolgt im Windows-Durchlauf (ADR-0004).
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ShaderLoader {
|
||||
/// Shader-Quelltexte nach Plugin-ID.
|
||||
sources: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl ShaderLoader {
|
||||
pub fn new() -> Self {
|
||||
ShaderLoader {
|
||||
sources: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Registriert einen HLSL-Quelltext unter einer Plugin-ID.
|
||||
pub fn register_source(&mut self, plugin_id: &str, source: String) {
|
||||
self.sources.insert(plugin_id.to_string(), source);
|
||||
}
|
||||
|
||||
/// Kompiliert den registrierten Shader für einen Layer.
|
||||
///
|
||||
/// `entry` ist der Name der Pixel-Shader-Funktion (Standard: `mainPS`).
|
||||
pub fn compile(
|
||||
&self,
|
||||
plugin_id: &str,
|
||||
entry: &str,
|
||||
snapshot: &FrameSnapshot,
|
||||
layer_key: &str,
|
||||
canvas: (u32, u32),
|
||||
) -> Result<CompiledShader, ShaderError> {
|
||||
let source = self
|
||||
.sources
|
||||
.get(plugin_id)
|
||||
.ok_or_else(|| ShaderError::NotFound(plugin_id.to_string()))?;
|
||||
|
||||
#[cfg(windows)]
|
||||
let bytecode = self.compile_d3d11(source, entry)?;
|
||||
#[cfg(not(windows))]
|
||||
let bytecode = {
|
||||
log::warn!(
|
||||
"Shader-Kompilierung nur unter Windows; Plugin {} wird nicht kompiliert",
|
||||
plugin_id
|
||||
);
|
||||
source.as_bytes().to_vec()
|
||||
};
|
||||
|
||||
let params = HmsParams::from_snapshot(snapshot, layer_key, canvas);
|
||||
Ok(CompiledShader {
|
||||
bytecode,
|
||||
params,
|
||||
textures: HashMap::new(),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn compile_d3d11(&self, source: &str, entry: &str) -> Result<Vec<u8>, ShaderError> {
|
||||
use windows::Win32::Graphics::Direct3D::D3DCompile;
|
||||
use windows::Win32::Graphics::Direct3D::D3D_SHADER_MACRO;
|
||||
use windows::Win32::Graphics::Direct3D::D3D_COMPILER_STRIP_REFLECTION_DATA;
|
||||
use windows::Win32::Graphics::Direct3D::D3DCOMPILE_OPTIMIZATION_LEVEL3;
|
||||
use windows::Win32::Graphics::Direct3D::D3DCOMPILE_PACK_MATRIX_ROW_MAJOR;
|
||||
use windows::Win32::Graphics::Direct3D::ID3DBlob;
|
||||
use windows::core::PCSTR;
|
||||
|
||||
let source_bytes = source.as_bytes();
|
||||
let mut error_blob: Option<ID3DBlob> = None;
|
||||
let mut shader_blob: Option<ID3DBlob> = None;
|
||||
|
||||
let flags = D3DCOMPILE_OPTIMIZATION_LEVEL3 | D3DCOMPILE_PACK_MATRIX_ROW_MAJOR;
|
||||
let entry_pcstr = PCSTR(entry.as_ptr());
|
||||
let profile_pcstr = PCSTR(b"ps_5_0\0".as_ptr());
|
||||
|
||||
let hr = unsafe {
|
||||
D3DCompile(
|
||||
source_bytes.as_ptr() as *const _,
|
||||
source_bytes.len(),
|
||||
PCSTR(b"hms_shader.hlsl\0".as_ptr()),
|
||||
std::ptr::null::<D3D_SHADER_MACRO>(),
|
||||
None,
|
||||
entry_pcstr,
|
||||
profile_pcstr,
|
||||
flags,
|
||||
0,
|
||||
&mut shader_blob,
|
||||
&mut error_blob,
|
||||
)
|
||||
};
|
||||
|
||||
if hr.is_err() {
|
||||
let msg = error_blob
|
||||
.as_ref()
|
||||
.map(|b| {
|
||||
let ptr = b.GetBufferPointer() as *const u8;
|
||||
let len = b.GetBufferSize();
|
||||
String::from_utf8_lossy(std::slice::from_raw_parts(ptr, len)).to_string()
|
||||
})
|
||||
.unwrap_or_else(|| format!("HRESULT {:?}", hr));
|
||||
return Err(ShaderError::Compile(msg));
|
||||
}
|
||||
|
||||
let blob = shader_blob.ok_or(ShaderError::NoBlob)?;
|
||||
let ptr = blob.GetBufferPointer() as *const u8;
|
||||
let len = blob.GetBufferSize();
|
||||
Ok(unsafe { std::slice::from_raw_parts(ptr, len) }.to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
/// Fehler beim Shader-Laden/-Kompilieren.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ShaderError {
|
||||
#[error("Shader nicht gefunden: {0}")]
|
||||
NotFound(String),
|
||||
#[error("HLSL-Kompilierung fehlgeschlagen: {0}")]
|
||||
Compile(String),
|
||||
#[error("Kein Shader-Blob erzeugt")]
|
||||
NoBlob,
|
||||
}
|
||||
Reference in New Issue
Block a user