1use crate::{
30 format_buf, g_vec2, g_vertex2, iif, miniquad, vec_ as vec, Box, MiniquadEventHandlerExt,
31 MiniquadWindow, Vec,
32};
33use ::miniquad::{
34 Bindings, BufferLayout, EventHandler, FilterMode, MipmapFilterMode, Pipeline, PipelineParams,
35 RenderingBackend, TextureFormat, TextureId, TextureParams, VertexAttribute, VertexFormat,
36};
37
38pub struct MiniquadPixels {
40 ctx: Option<Box<dyn RenderingBackend>>,
41 pipeline: Option<Pipeline>,
42 bindings: Option<Bindings>,
43 texture: Option<TextureId>,
44
45 pub pixels: Vec<u8>,
48 width: u32,
49 height: u32,
50 viewport: (f32, f32, f32, f32), interpolation: bool,
53 maintain_aspect_ratio: bool,
54}
55
56impl core::fmt::Debug for MiniquadPixels {
57 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
58 let mut buf = [0u8; 20];
59
60 f.debug_struct("MiniquadPixels")
61 .field("pixels", &format_buf![&mut buf, "{}B", self.pixels.len()].unwrap())
66 .field("width", &self.width)
67 .field("height", &self.height)
68 .field("interpolation", &self.interpolation)
69 .field("maintain_aspect_ratio", &self.maintain_aspect_ratio)
70 .finish()
71 }
72}
73
74impl MiniquadPixels {
75 pub fn new(width: u32, height: u32) -> Self {
79 Self {
80 ctx: None,
81 pipeline: None,
82 bindings: None,
83 texture: None,
84 pixels: vec![0; width as usize * height as usize * 4],
86 width,
87 height,
88 viewport: (0.0, 0.0, width as f32, height as f32),
89 interpolation: false,
90 maintain_aspect_ratio: true,
91 }
92 }
93
94 pub fn viewport(&self) -> (f32, f32, f32, f32) {
97 self.viewport
98 }
99
100 pub fn init(mut self) -> Self {
113 let mut ctx: Box<dyn RenderingBackend> = MiniquadWindow::new_rendering_backend();
114
115 #[rustfmt::skip]
116 let vertices: [g_vertex2; 4] = [
117 g_vertex2 { pos : g_vec2 { x: -1.0, y: -1.0 }, uv: g_vec2 { x: 0., y: 0. } },
118 g_vertex2 { pos : g_vec2 { x: 1.0, y: -1.0 }, uv: g_vec2 { x: 1., y: 0. } },
119 g_vertex2 { pos : g_vec2 { x: 1.0, y: 1.0 }, uv: g_vec2 { x: 1., y: 1. } },
120 g_vertex2 { pos : g_vec2 { x: -1.0, y: 1.0 }, uv: g_vec2 { x: 0., y: 1. } },
121 ];
122 let indices: [u16; 6] = [0, 1, 2, 0, 2, 3];
123 let (vbuf, ibuf) = miniquad![new_vertices_indices(ctx, Immutable, &vertices, &indices)];
124
125 let interp = iif![self.interpolation; FilterMode::Linear; FilterMode::Nearest];
126
127 let texture = ctx.new_render_texture(TextureParams {
130 width: self.width,
131 height: self.height,
132 format: TextureFormat::RGBA8,
133 mag_filter: interp,
134 min_filter: interp,
135 ..Default::default()
136 });
137
138 let bindings = miniquad![bindings(vec![vbuf], ibuf, vec![texture])];
139 let shader = miniquad![new_shader(ctx, VERTEX, FRAGMENT, METAL, shader_meta())].unwrap();
140 let pipeline = ctx.new_pipeline(
144 &[BufferLayout::default()],
145 &[
146 VertexAttribute::new("in_pos", VertexFormat::Float2),
147 VertexAttribute::new("in_uv", VertexFormat::Float2),
148 ],
149 shader,
150 PipelineParams::default(),
151 );
152
153 self.ctx = Some(ctx);
154 self.pipeline = Some(pipeline);
155 self.bindings = Some(bindings);
156 self.texture = Some(texture);
157 self
158 }
159}
160
161#[rustfmt::skip]
162impl MiniquadEventHandlerExt for MiniquadPixels {
163 fn init(self) -> Self { self.init() }
164 fn interpolation(&self) -> bool { self.interpolation }
165 fn set_interpolation(&mut self, set: bool) {
166 self.interpolation = set;
167 if let Some(ctx) = &mut self.ctx {
168 if let Some(texture) = self.texture {
169 let f = iif![set; FilterMode::Linear; FilterMode::Nearest];
170 ctx.texture_set_filter(texture, f, MipmapFilterMode::None);
171 }
172 }
173 }
174 fn maintain_aspect_ratio(&self) -> bool { self.maintain_aspect_ratio }
175 fn set_maintain_aspect_ratio(&mut self, set: bool) { self.maintain_aspect_ratio = set; }
176}
177impl EventHandler for MiniquadPixels {
178 fn update(&mut self) {}
179 fn draw(&mut self) {
180 if self.maintain_aspect_ratio {
181 let ctx = self.ctx.as_mut().unwrap();
182
183 let (sw, sh) = MiniquadWindow::get_size();
184 let (fw, fh) = (self.width as f32, self.height as f32);
185
186 let desired_ratio = fw / fh;
187 let current_ratio = sw / sh;
188 let scale = if current_ratio > desired_ratio {
189 sh / fh } else {
191 sw / fw };
193
194 let vp_w = (scale * fw).round();
195 let vp_h = (scale * fh).round();
196 let vp_x = ((sw - vp_w) / 2.0).round();
198 let vp_y = ((sh - vp_h) / 2.0).round();
199
200 self.viewport = (vp_x, vp_y, vp_w, vp_h);
201
202 ctx.texture_update(self.texture.unwrap(), &self.pixels);
204 ctx.begin_default_pass(Default::default());
206
207 ctx.apply_viewport(vp_x as i32, vp_y as i32, vp_w as i32, vp_h as i32);
209
210 ctx.apply_pipeline(&self.pipeline.unwrap());
212 ctx.apply_bindings(self.bindings.as_ref().unwrap());
213 ctx.draw(0, 6, 1);
215
216 ctx.apply_viewport(0, 0, sw as i32, sh as i32);
218
219 ctx.end_render_pass();
220 ctx.commit_frame();
221 } else {
222 let ctx = self.ctx.as_mut().unwrap();
223
224 let (sw, sh) = MiniquadWindow::get_size();
225 self.viewport = (0.0, 0.0, sw, sh);
227
228 ctx.texture_update(self.texture.unwrap(), &self.pixels);
230 ctx.begin_default_pass(Default::default());
232
233 ctx.apply_pipeline(&self.pipeline.unwrap());
235 ctx.apply_bindings(self.bindings.as_ref().unwrap());
236 ctx.draw(0, 6, 1);
238
239 ctx.end_render_pass();
240 ctx.commit_frame();
241 }
242 }
243}
244
245use shader::{shader_meta, FRAGMENT, METAL, VERTEX};
246mod shader {
247 use crate::{vec_ as vec, ToString};
248 use ::miniquad::{ShaderMeta, UniformBlockLayout};
249
250 pub fn shader_meta() -> ShaderMeta {
252 ShaderMeta {
253 images: vec!["tex".to_string()],
254 uniforms: UniformBlockLayout { uniforms: vec![] },
255 }
256 }
257
258 pub const VERTEX: &str = r#"#version 100
260 attribute vec2 in_pos;
261 attribute vec2 in_uv;
262 uniform vec2 offset;
263 varying lowp vec2 texcoord;
264 void main() {
265 gl_Position = vec4(in_pos + offset, 0, 1);
266 // Flip y axis: convert texture coordinates from bottom-left to top-left origin
267 texcoord = vec2(in_uv.x, 1.0 - in_uv.y);
268 // texcoord = in_uv; // no flipping
269 }"#;
270
271 pub const FRAGMENT: &str = r#"#version 100
273 varying lowp vec2 texcoord;
274 uniform sampler2D tex;
275 void main() {
276 gl_FragColor = texture2D(tex, texcoord);
277 }"#;
278
279 pub const METAL: &str = r#"#include <metal_stdlib>
281 using namespace metal;
282
283 struct VertexIn {
284 float2 in_pos [[attribute(0)]];
285 float2 in_uv [[attribute(1)]];
286 };
287 struct Uniforms {
288 float2 offset;
289 };
290 struct VertexOut {
291 float4 position [[position]];
292 float2 texcoord;
293 };
294
295 vertex VertexOut vertex_main(VertexIn in [[stage_in]],
296 constant Uniforms &uniforms [[buffer(1)]])
297 {
298 VertexOut out;
299 float2 pos = in.in_pos + uniforms.offset;
300 out.position = float4(pos, 0.0, 1.0);
301 out.texcoord = float2(in.in_uv.x, 1.0 - in.in_uv.y);
302 return out;
303 }
304
305 fragment float4 fragment_main(VertexOut in [[stage_in]],
306 texture2d<float> tex [[texture(0)]],
307 sampler samp [[sampler(0)]])
308 {
309 return tex.sample(samp, in.texcoord);
310 }
311 "#;
312}