Initial + split from my custom roguelike engine
This commit is contained in:
+10
@@ -0,0 +1,10 @@
|
|||||||
|
[package]
|
||||||
|
name = "pbio"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
glfw = "0.60.0"
|
||||||
|
wgpu = "*"
|
||||||
|
pollster = "0.4.0"
|
||||||
|
bytemuck = { version = "1.24.0", features = ["derive"] }
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
# pbio: simple pixel buffer I/O
|
||||||
|
|
||||||
|
Pixelbuffer-basiertes Fenster mit Palette-Lookup-Renderer (GLFW + wgpu).
|
||||||
|
|
||||||
|
pbio nimmt einen Bytearray, interpretiert diesen als 8-Bit-Pixelbuffer und rendert ihn auf ein Fenster. Optional mit Letterbox für ein festes Seitenverhältnis
|
||||||
|
|
||||||
|
## Verwendung
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use pbio::{Event, Key, Platform, PlatformConfig};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut p = Platform::new(PlatformConfig {
|
||||||
|
title: "Demo".into(),
|
||||||
|
window_size: (1024, 768),
|
||||||
|
framebuffer_size: (320, 240),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
while !p.should_close() {
|
||||||
|
p.poll_events(None);
|
||||||
|
|
||||||
|
for ev in p.drain_events() {
|
||||||
|
if let Event::Key { key: Key::Escape, pressed: true, .. } = ev {
|
||||||
|
p.request_close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let (w, _h) = p.framebuffer_size();
|
||||||
|
let fb = p.framebuffer_mut();
|
||||||
|
for (i, px) in fb.iter_mut().enumerate() {
|
||||||
|
*px = ((i as u32 % w) ^ (i as u32 / w)) as u8;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.present();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## API-Überblick
|
||||||
|
|
||||||
|
- `PlatformConfig` – Titel, Fenster-/Framebuffer-Größe, optionales
|
||||||
|
`aspect_ratio` (Letterbox), Palette, VSync. Default = 800×600 Fenster,
|
||||||
|
320×240 Framebuffer, 4:3, `palette::RGB332`.
|
||||||
|
- `Platform::new(cfg)` – Fenster + GPU-Pipeline aufsetzen.
|
||||||
|
- `poll_events(timeout)` – GLFW-Events einlesen (`None` = blockierend).
|
||||||
|
- `drain_events()` – Iterator über `Event` (`Key`, `Char`, `MouseMove`,
|
||||||
|
`MouseBtn`, `Resize`, `CloseRequested`).
|
||||||
|
- `framebuffer_mut()` – `&mut [u8]` der Länge `w*h`, je Pixel ein Paletten-Index.
|
||||||
|
- `present()` – Frame auf das Fenster bringen.
|
||||||
|
- `should_close()` / `request_close()`.
|
||||||
|
|
||||||
|
## Paletten
|
||||||
|
|
||||||
|
`palette::RGB332` ist als Default eingebaut. Eigene Paletten sind ein
|
||||||
|
`[[u8; 3]; 256]` (RGB pro Index) und werden über `PlatformConfig::palette`
|
||||||
|
übergeben.
|
||||||
+445
@@ -0,0 +1,445 @@
|
|||||||
|
//! pbio — Pixelbuffer-basiertes Fenster mit Palette-Lookup-Renderer.
|
||||||
|
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use wgpu::util::DeviceExt;
|
||||||
|
|
||||||
|
pub mod palette;
|
||||||
|
|
||||||
|
// --- Konfiguration ---------------------------------------------------------
|
||||||
|
|
||||||
|
pub struct PlatformConfig {
|
||||||
|
pub title: String,
|
||||||
|
pub window_size: (u32, u32),
|
||||||
|
pub framebuffer_size: (u32, u32),
|
||||||
|
pub aspect_ratio: Option<f32>,
|
||||||
|
pub palette: [[u8; 3]; 256],
|
||||||
|
pub vsync: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for PlatformConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
title: "pbio".into(),
|
||||||
|
window_size: (800, 600),
|
||||||
|
framebuffer_size: (320, 240),
|
||||||
|
aspect_ratio: Some(4.0 / 3.0),
|
||||||
|
palette: palette::RGB332,
|
||||||
|
vsync: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Events ----------------------------------------------------------------
|
||||||
|
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum Key {
|
||||||
|
A, B, C, D, E, F, G, H, I, J, K, L, M,
|
||||||
|
N, O, P, Q, R, S, T, U, V, W, X, Y, Z,
|
||||||
|
Num0, Num1, Num2, Num3, Num4,
|
||||||
|
Num5, Num6, Num7, Num8, Num9,
|
||||||
|
Up, Down, Left, Right,
|
||||||
|
Space, Enter, Escape, Tab, Backspace,
|
||||||
|
LShift, RShift, LCtrl, RCtrl, LAlt, RAlt,
|
||||||
|
F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum MouseButton { Left, Right, Middle }
|
||||||
|
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum Event {
|
||||||
|
Key { key: Key, pressed: bool, repeat: bool },
|
||||||
|
Char (char),
|
||||||
|
MouseMove { x: f32, y: f32 },
|
||||||
|
MouseBtn { button: MouseButton, pressed: bool },
|
||||||
|
Resize { width: u32, height: u32 },
|
||||||
|
CloseRequested,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Muss zum Shader-Uniform passen (32 Byte, 16-Byte-aligned).
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
|
||||||
|
struct Params {
|
||||||
|
src_size: [f32; 2],
|
||||||
|
content_offset: [f32; 2],
|
||||||
|
content_size: [f32; 2],
|
||||||
|
_pad: [f32; 2],
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Platform --------------------------------------------------------------
|
||||||
|
|
||||||
|
pub struct Platform {
|
||||||
|
// GPU-Ressourcen vor Surface, Surface vor Window/Glfw (Drop-Reihenfolge).
|
||||||
|
bind_group: wgpu::BindGroup,
|
||||||
|
pipeline: wgpu::RenderPipeline,
|
||||||
|
index_tex: wgpu::Texture,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
palette_tex: wgpu::Texture,
|
||||||
|
params_buf: wgpu::Buffer,
|
||||||
|
params: Params,
|
||||||
|
surface: wgpu::Surface<'static>,
|
||||||
|
config: wgpu::SurfaceConfiguration,
|
||||||
|
device: wgpu::Device,
|
||||||
|
queue: wgpu::Queue,
|
||||||
|
instance: wgpu::Instance,
|
||||||
|
|
||||||
|
window: glfw::PWindow,
|
||||||
|
events: glfw::GlfwReceiver<(f64, glfw::WindowEvent)>,
|
||||||
|
glfw: glfw::Glfw,
|
||||||
|
|
||||||
|
framebuffer: Vec<u8>,
|
||||||
|
framebuffer_size: (u32, u32),
|
||||||
|
aspect_ratio: f32, // 0.0 = keine Letterbox
|
||||||
|
content_rect: (u32, u32, u32, u32),
|
||||||
|
event_queue: VecDeque<Event>,
|
||||||
|
close_requested: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Platform {
|
||||||
|
pub fn new(config: PlatformConfig) -> Self {
|
||||||
|
pollster::block_on(Self::new_async(config))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn new_async(cfg: PlatformConfig) -> Self {
|
||||||
|
use glfw::fail_on_errors;
|
||||||
|
|
||||||
|
let mut glfw = glfw::init(fail_on_errors!()).unwrap();
|
||||||
|
glfw.window_hint(glfw::WindowHint::ClientApi(glfw::ClientApiHint::NoApi));
|
||||||
|
|
||||||
|
let (mut window, events) = glfw
|
||||||
|
.create_window(cfg.window_size.0, cfg.window_size.1, &cfg.title, glfw::WindowMode::Windowed)
|
||||||
|
.expect("failed to create GLFW window");
|
||||||
|
|
||||||
|
window.set_framebuffer_size_polling(true);
|
||||||
|
window.set_key_polling(true);
|
||||||
|
window.set_char_polling(true);
|
||||||
|
window.set_cursor_pos_polling(true);
|
||||||
|
window.set_mouse_button_polling(true);
|
||||||
|
window.set_close_polling(true);
|
||||||
|
|
||||||
|
let win_size = window.get_framebuffer_size();
|
||||||
|
|
||||||
|
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
|
||||||
|
backends: wgpu::Backends::all(),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
let surface = instance.create_surface(window.render_context()).expect("surface");
|
||||||
|
|
||||||
|
let adapter = instance.request_adapter(&wgpu::RequestAdapterOptions {
|
||||||
|
compatible_surface: Some(&surface),
|
||||||
|
..Default::default()
|
||||||
|
}).await.expect("no suitable wgpu adapter");
|
||||||
|
|
||||||
|
let (device, queue) = adapter.request_device(&wgpu::DeviceDescriptor {
|
||||||
|
label: Some("pbio device"),
|
||||||
|
..Default::default()
|
||||||
|
}).await.expect("device");
|
||||||
|
|
||||||
|
let caps = surface.get_capabilities(&adapter);
|
||||||
|
let format = caps.formats.iter().copied().find(|f| f.is_srgb()).unwrap_or(caps.formats[0]);
|
||||||
|
|
||||||
|
let surface_config = wgpu::SurfaceConfiguration {
|
||||||
|
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||||
|
format,
|
||||||
|
width: win_size.0 as u32,
|
||||||
|
height: win_size.1 as u32,
|
||||||
|
present_mode: if cfg.vsync { wgpu::PresentMode::Mailbox } else { wgpu::PresentMode::Immediate },
|
||||||
|
alpha_mode: caps.alpha_modes[0],
|
||||||
|
view_formats: vec![],
|
||||||
|
desired_maximum_frame_latency: 2,
|
||||||
|
};
|
||||||
|
surface.configure(&device, &surface_config);
|
||||||
|
|
||||||
|
// --- Texturen ---
|
||||||
|
let (src_w, src_h) = cfg.framebuffer_size;
|
||||||
|
let index_tex = make_tex(&device, "pbio index_tex", src_w, src_h, wgpu::TextureFormat::R8Uint);
|
||||||
|
let palette_tex = make_tex(&device, "pbio palette_tex", 256, 1, wgpu::TextureFormat::Rgba8Unorm);
|
||||||
|
let index_view = index_tex.create_view(&Default::default());
|
||||||
|
let palette_view = palette_tex.create_view(&Default::default());
|
||||||
|
|
||||||
|
let palette_rgba: Vec<u8> = cfg.palette.iter()
|
||||||
|
.flat_map(|[r, g, b]| [*r, *g, *b, 255])
|
||||||
|
.collect();
|
||||||
|
queue.write_texture(
|
||||||
|
wgpu::TexelCopyTextureInfoBase { texture: &palette_tex, mip_level: 0, origin: wgpu::Origin3d::ZERO, aspect: wgpu::TextureAspect::All },
|
||||||
|
&palette_rgba,
|
||||||
|
wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some(256 * 4), rows_per_image: None },
|
||||||
|
wgpu::Extent3d { width: 256, height: 1, depth_or_array_layers: 1 },
|
||||||
|
);
|
||||||
|
|
||||||
|
// --- Uniform ---
|
||||||
|
let aspect_ratio = cfg.aspect_ratio.unwrap_or(0.0);
|
||||||
|
let content_rect = compute_content_rect((surface_config.width, surface_config.height), aspect_ratio);
|
||||||
|
let params = Params {
|
||||||
|
src_size: [src_w as f32, src_h as f32],
|
||||||
|
content_offset: [content_rect.0 as f32, content_rect.1 as f32],
|
||||||
|
content_size: [content_rect.2 as f32, content_rect.3 as f32],
|
||||||
|
_pad: [0.0, 0.0],
|
||||||
|
};
|
||||||
|
let params_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||||
|
label: Some("pbio params_buf"),
|
||||||
|
contents: bytemuck::bytes_of(¶ms),
|
||||||
|
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- Pipeline ---
|
||||||
|
let (bgl, pipeline) = build_pipeline(&device, surface_config.format);
|
||||||
|
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
|
label: Some("pbio bind_group"),
|
||||||
|
layout: &bgl,
|
||||||
|
entries: &[
|
||||||
|
wgpu::BindGroupEntry { binding: 0, resource: params_buf.as_entire_binding() },
|
||||||
|
wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::TextureView(&index_view) },
|
||||||
|
wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::TextureView(&palette_view) },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
Self {
|
||||||
|
bind_group, pipeline, index_tex, palette_tex, params_buf, params,
|
||||||
|
surface, config: surface_config, device, queue, instance,
|
||||||
|
window, events, glfw,
|
||||||
|
framebuffer: vec![0u8; (src_w * src_h) as usize],
|
||||||
|
framebuffer_size: (src_w, src_h),
|
||||||
|
aspect_ratio, content_rect,
|
||||||
|
event_queue: VecDeque::new(),
|
||||||
|
close_requested: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn should_close(&self) -> bool { self.close_requested || self.window.should_close() }
|
||||||
|
pub fn request_close(&mut self) { self.close_requested = true; self.window.set_should_close(true); }
|
||||||
|
|
||||||
|
pub fn poll_events(&mut self, timeout: Option<Duration>) {
|
||||||
|
match timeout {
|
||||||
|
Some(t) => self.glfw.wait_events_timeout(t.as_secs_f64()),
|
||||||
|
None => self.glfw.wait_events(),
|
||||||
|
}
|
||||||
|
// flush_messages borrowt &events, handle_glfw_event will &mut self → erst sammeln.
|
||||||
|
let drained: Vec<_> = glfw::flush_messages(&self.events).map(|(_, ev)| ev).collect();
|
||||||
|
for ev in drained { self.handle_glfw_event(ev); }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn drain_events(&mut self) -> impl Iterator<Item = Event> + '_ {
|
||||||
|
self.event_queue.drain(..)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn framebuffer_mut(&mut self) -> &mut [u8] { &mut self.framebuffer }
|
||||||
|
pub fn framebuffer_size(&self) -> (u32, u32) { self.framebuffer_size }
|
||||||
|
|
||||||
|
pub fn present(&mut self) {
|
||||||
|
let (w, h) = self.framebuffer_size;
|
||||||
|
self.queue.write_texture(
|
||||||
|
wgpu::TexelCopyTextureInfoBase { texture: &self.index_tex, mip_level: 0, origin: wgpu::Origin3d::ZERO, aspect: wgpu::TextureAspect::All },
|
||||||
|
&self.framebuffer,
|
||||||
|
wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some(w), rows_per_image: None },
|
||||||
|
wgpu::Extent3d { width: w, height: h, depth_or_array_layers: 1 },
|
||||||
|
);
|
||||||
|
|
||||||
|
let drawable = match self.surface.get_current_texture() {
|
||||||
|
Ok(d) => d,
|
||||||
|
Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
|
||||||
|
self.surface = self.instance.create_surface(self.window.render_context()).expect("recreate surface");
|
||||||
|
self.surface.configure(&self.device, &self.config);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Err(e) => { eprintln!("pbio: surface error: {:?}", e); return; }
|
||||||
|
};
|
||||||
|
|
||||||
|
let view = drawable.texture.create_view(&Default::default());
|
||||||
|
let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("pbio encoder") });
|
||||||
|
let (cx, cy, cw, ch) = self.content_rect;
|
||||||
|
{
|
||||||
|
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
|
label: Some("pbio pass"),
|
||||||
|
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||||
|
view: &view,
|
||||||
|
depth_slice: None,
|
||||||
|
resolve_target: None,
|
||||||
|
ops: wgpu::Operations {
|
||||||
|
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
|
||||||
|
store: wgpu::StoreOp::Store,
|
||||||
|
},
|
||||||
|
})],
|
||||||
|
depth_stencil_attachment: None,
|
||||||
|
occlusion_query_set: None,
|
||||||
|
timestamp_writes: None,
|
||||||
|
});
|
||||||
|
pass.set_viewport(cx as f32, cy as f32, cw as f32, ch as f32, 0.0, 1.0);
|
||||||
|
pass.set_scissor_rect(cx, cy, cw, ch);
|
||||||
|
pass.set_pipeline(&self.pipeline);
|
||||||
|
pass.set_bind_group(0, &self.bind_group, &[]);
|
||||||
|
pass.draw(0..3, 0..1);
|
||||||
|
}
|
||||||
|
self.queue.submit([encoder.finish()]);
|
||||||
|
drawable.present();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_glfw_event(&mut self, ev: glfw::WindowEvent) {
|
||||||
|
use glfw::WindowEvent as W;
|
||||||
|
match ev {
|
||||||
|
W::FramebufferSize(w, h) => {
|
||||||
|
if w > 0 && h > 0 {
|
||||||
|
self.config.width = w as u32;
|
||||||
|
self.config.height = h as u32;
|
||||||
|
self.surface.configure(&self.device, &self.config);
|
||||||
|
self.content_rect = compute_content_rect((self.config.width, self.config.height), self.aspect_ratio);
|
||||||
|
let (x, y, cw, ch) = self.content_rect;
|
||||||
|
self.params.content_offset = [x as f32, y as f32];
|
||||||
|
self.params.content_size = [cw as f32, ch as f32];
|
||||||
|
self.queue.write_buffer(&self.params_buf, 0, bytemuck::bytes_of(&self.params));
|
||||||
|
}
|
||||||
|
self.event_queue.push_back(Event::Resize { width: w.max(0) as u32, height: h.max(0) as u32 });
|
||||||
|
}
|
||||||
|
W::Key(k, _, action, _) => if let Some(key) = from_glfw_key(k) {
|
||||||
|
self.event_queue.push_back(Event::Key {
|
||||||
|
key,
|
||||||
|
pressed: action != glfw::Action::Release,
|
||||||
|
repeat: action == glfw::Action::Repeat,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
W::Char(c) => self.event_queue.push_back(Event::Char(c)),
|
||||||
|
W::CursorPos(x, y) => {
|
||||||
|
let (cx, cy, cw, ch) = self.content_rect;
|
||||||
|
let (fw, fh) = self.framebuffer_size;
|
||||||
|
let fx = ((x - cx as f64) / cw as f64 * fw as f64) as f32;
|
||||||
|
let fy = ((y - cy as f64) / ch as f64 * fh as f64) as f32;
|
||||||
|
self.event_queue.push_back(Event::MouseMove { x: fx, y: fy });
|
||||||
|
}
|
||||||
|
W::MouseButton(btn, action, _) => if let Some(button) = from_glfw_button(btn) {
|
||||||
|
self.event_queue.push_back(Event::MouseBtn { button, pressed: action != glfw::Action::Release });
|
||||||
|
}
|
||||||
|
W::Close => {
|
||||||
|
self.close_requested = true;
|
||||||
|
self.event_queue.push_back(Event::CloseRequested);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Helpers ---------------------------------------------------------------
|
||||||
|
|
||||||
|
fn compute_content_rect((win_w, win_h): (u32, u32), ar: f32) -> (u32, u32, u32, u32) {
|
||||||
|
let w = win_w.max(1) as f32;
|
||||||
|
let h = win_h.max(1) as f32;
|
||||||
|
if ar <= 0.0 { return (0, 0, w as u32, h as u32); }
|
||||||
|
let (cw, ch) = if w / h > ar { (h * ar, h) } else { (w, w / ar) };
|
||||||
|
let ox = (w - cw) * 0.5;
|
||||||
|
let oy = (h - ch) * 0.5;
|
||||||
|
(ox.floor().max(0.0) as u32, oy.floor().max(0.0) as u32,
|
||||||
|
cw.floor().max(1.0) as u32, ch.floor().max(1.0) as u32)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_tex(device: &wgpu::Device, label: &str, w: u32, h: u32, format: wgpu::TextureFormat) -> wgpu::Texture {
|
||||||
|
device.create_texture(&wgpu::TextureDescriptor {
|
||||||
|
label: Some(label),
|
||||||
|
size: wgpu::Extent3d { width: w, height: h, depth_or_array_layers: 1 },
|
||||||
|
mip_level_count: 1,
|
||||||
|
sample_count: 1,
|
||||||
|
dimension: wgpu::TextureDimension::D2,
|
||||||
|
format,
|
||||||
|
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
|
||||||
|
view_formats: &[],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_glfw_key(k: glfw::Key) -> Option<Key> {
|
||||||
|
use glfw::Key as G;
|
||||||
|
macro_rules! same { ($($n:ident),* $(,)?) => { match k {
|
||||||
|
$(G::$n => Some(Key::$n),)*
|
||||||
|
G::LeftShift => Some(Key::LShift), G::RightShift => Some(Key::RShift),
|
||||||
|
G::LeftControl => Some(Key::LCtrl), G::RightControl => Some(Key::RCtrl),
|
||||||
|
G::LeftAlt => Some(Key::LAlt), G::RightAlt => Some(Key::RAlt),
|
||||||
|
_ => None,
|
||||||
|
}}; }
|
||||||
|
same!(
|
||||||
|
A, B, C, D, E, F, G, H, I, J, K, L, M,
|
||||||
|
N, O, P, Q, R, S, T, U, V, W, X, Y, Z,
|
||||||
|
Num0, Num1, Num2, Num3, Num4, Num5, Num6, Num7, Num8, Num9,
|
||||||
|
Up, Down, Left, Right,
|
||||||
|
Space, Enter, Escape, Tab, Backspace,
|
||||||
|
F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_glfw_button(b: glfw::MouseButton) -> Option<MouseButton> {
|
||||||
|
match b {
|
||||||
|
glfw::MouseButton::Button1 => Some(MouseButton::Left),
|
||||||
|
glfw::MouseButton::Button2 => Some(MouseButton::Right),
|
||||||
|
glfw::MouseButton::Button3 => Some(MouseButton::Middle),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_pipeline(
|
||||||
|
device: &wgpu::Device,
|
||||||
|
surface_format: wgpu::TextureFormat,
|
||||||
|
) -> (wgpu::BindGroupLayout, wgpu::RenderPipeline) {
|
||||||
|
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||||
|
label: Some("pbio shader"),
|
||||||
|
source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()),
|
||||||
|
});
|
||||||
|
|
||||||
|
let tex_entry = |binding, sample_type| wgpu::BindGroupLayoutEntry {
|
||||||
|
binding,
|
||||||
|
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Texture {
|
||||||
|
sample_type, view_dimension: wgpu::TextureViewDimension::D2, multisampled: false,
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||||
|
label: Some("pbio BGL"),
|
||||||
|
entries: &[
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 0,
|
||||||
|
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Buffer {
|
||||||
|
ty: wgpu::BufferBindingType::Uniform,
|
||||||
|
has_dynamic_offset: false,
|
||||||
|
min_binding_size: std::num::NonZeroU64::new(std::mem::size_of::<Params>() as u64),
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
tex_entry(1, wgpu::TextureSampleType::Uint),
|
||||||
|
tex_entry(2, wgpu::TextureSampleType::Float { filterable: false }),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||||
|
label: Some("pbio pipeline layout"),
|
||||||
|
bind_group_layouts: &[&bgl],
|
||||||
|
push_constant_ranges: &[],
|
||||||
|
});
|
||||||
|
|
||||||
|
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
|
label: Some("pbio pipeline"),
|
||||||
|
layout: Some(&layout),
|
||||||
|
vertex: wgpu::VertexState {
|
||||||
|
module: &shader, entry_point: Some("vs_main"),
|
||||||
|
compilation_options: Default::default(), buffers: &[],
|
||||||
|
},
|
||||||
|
fragment: Some(wgpu::FragmentState {
|
||||||
|
module: &shader, entry_point: Some("fs_main"),
|
||||||
|
compilation_options: Default::default(),
|
||||||
|
targets: &[Some(wgpu::ColorTargetState {
|
||||||
|
format: surface_format,
|
||||||
|
blend: Some(wgpu::BlendState::REPLACE),
|
||||||
|
write_mask: wgpu::ColorWrites::ALL,
|
||||||
|
})],
|
||||||
|
}),
|
||||||
|
primitive: wgpu::PrimitiveState::default(),
|
||||||
|
depth_stencil: None,
|
||||||
|
multisample: wgpu::MultisampleState::default(),
|
||||||
|
multiview: None,
|
||||||
|
cache: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
(bgl, pipeline)
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
//! Palette-Presets.
|
||||||
|
|
||||||
|
/// RGB332-Palette: 8 Levels Rot, 8 Levels Grün, 4 Levels Blau.
|
||||||
|
///
|
||||||
|
/// Index-Bits: `RRRGGGBB` — `r = idx / 32`, `g = (idx / 4) % 8`, `b = idx % 4`.
|
||||||
|
pub const RGB332: [[u8; 3]; 256] = {
|
||||||
|
let mut p = [[0u8; 3]; 256];
|
||||||
|
let mut i = 0;
|
||||||
|
while i < 256 {
|
||||||
|
let r = (i / 32) as u32; // 0..=7
|
||||||
|
let g = ((i / 4) % 8) as u32; // 0..=7
|
||||||
|
let b = (i % 4) as u32; // 0..=3
|
||||||
|
p[i][0] = ((r * 255) / 7) as u8;
|
||||||
|
p[i][1] = ((g * 255) / 7) as u8;
|
||||||
|
p[i][2] = ((b * 255) / 3) as u8;
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
p
|
||||||
|
};
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
struct Params {
|
||||||
|
src_size: vec2<f32>,
|
||||||
|
content_offset: vec2<f32>,
|
||||||
|
content_size: vec2<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
@group(0) @binding(0) var<uniform> params: Params;
|
||||||
|
@group(0) @binding(1) var src_indices: texture_2d<u32>;
|
||||||
|
@group(0) @binding(2) var palette_tex: texture_2d<f32>;
|
||||||
|
|
||||||
|
@vertex
|
||||||
|
fn vs_main(@builtin(vertex_index) vid: u32) -> @builtin(position) vec4<f32> {
|
||||||
|
var pos = array<vec2<f32>, 3>(
|
||||||
|
vec2<f32>(-1.0, -1.0),
|
||||||
|
vec2<f32>( 3.0, -1.0),
|
||||||
|
vec2<f32>(-1.0, 3.0)
|
||||||
|
);
|
||||||
|
return vec4<f32>(pos[vid], 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@fragment
|
||||||
|
fn fs_main(@builtin(position) frag_xy: vec4<f32>) -> @location(0) vec4<f32> {
|
||||||
|
let uv = (frag_xy.xy - params.content_offset) / params.content_size;
|
||||||
|
|
||||||
|
let sx = clamp(i32(floor(uv.x * params.src_size.x)), 0, i32(params.src_size.x) - 1);
|
||||||
|
let sy = clamp(i32(floor(uv.y * params.src_size.y)), 0, i32(params.src_size.y) - 1);
|
||||||
|
|
||||||
|
let idx: u32 = textureLoad(src_indices, vec2<i32>(sx, sy), 0).r & 0xFFu;
|
||||||
|
let rgb = textureLoad(palette_tex, vec2<i32>(i32(idx), 0), 0).rgb;
|
||||||
|
|
||||||
|
return vec4<f32>(rgb, 1.0);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user