colour formats
This commit is contained in:
parent
3fcaca4654
commit
4bae07f165
|
@ -19,13 +19,13 @@ struct EmuParams {}
|
|||
struct EmuVars {
|
||||
rx: AsyncHeapConsumer<[f32; 2]>,
|
||||
sender: Sender<EmulatorMessage>,
|
||||
emulator_core: EmulatorCore,
|
||||
emulator_core: EmulatorCore<[u8; 4]>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct GameboyEmu {
|
||||
vars: Option<EmuVars>,
|
||||
frame_receiver: Arc<Mutex<Option<Receiver<Vec<u32>>>>>,
|
||||
frame_receiver: Arc<Mutex<Option<Receiver<Vec<[u8; 4]>>>>>,
|
||||
}
|
||||
|
||||
const ROM: &[u8; 32768] = include_bytes!("../../test-roms/Tetris.gb");
|
||||
|
|
|
@ -14,11 +14,11 @@ use nih_plug::prelude::*;
|
|||
use pixels::{Pixels, SurfaceTexture};
|
||||
|
||||
pub struct Emulator {
|
||||
frame_receiver: Arc<Mutex<Option<Receiver<Vec<u32>>>>>,
|
||||
frame_receiver: Arc<Mutex<Option<Receiver<Vec<[u8; 4]>>>>>,
|
||||
}
|
||||
|
||||
impl Emulator {
|
||||
pub fn new(frame_receiver: Arc<Mutex<Option<Receiver<Vec<u32>>>>>) -> Self {
|
||||
pub fn new(frame_receiver: Arc<Mutex<Option<Receiver<Vec<[u8; 4]>>>>>) -> Self {
|
||||
Self { frame_receiver }
|
||||
}
|
||||
}
|
||||
|
@ -61,11 +61,14 @@ impl Editor for Emulator {
|
|||
pub struct EmulatorWindow {
|
||||
pix: Pixels,
|
||||
scale: usize,
|
||||
frame_receiver: Arc<Mutex<Option<Receiver<Vec<u32>>>>>,
|
||||
frame_receiver: Arc<Mutex<Option<Receiver<Vec<[u8; 4]>>>>>,
|
||||
}
|
||||
|
||||
impl EmulatorWindow {
|
||||
fn new(window: &mut Window, frame_receiver: Arc<Mutex<Option<Receiver<Vec<u32>>>>>) -> Self {
|
||||
fn new(
|
||||
window: &mut Window,
|
||||
frame_receiver: Arc<Mutex<Option<Receiver<Vec<[u8; 4]>>>>>,
|
||||
) -> Self {
|
||||
let info = WindowInfo::from_logical_size(Size::new(WIDTH as f64, HEIGHT as f64), 1.);
|
||||
|
||||
let (pix, scale) = init_pixbuf(info, window);
|
||||
|
@ -105,7 +108,7 @@ impl WindowHandler for EmulatorWindow {
|
|||
for (pixel, source) in
|
||||
self.pix.get_frame_mut().chunks_exact_mut(4).zip(scaled_buf)
|
||||
{
|
||||
pixel.copy_from_slice(&source.to_be_bytes());
|
||||
pixel.copy_from_slice(&source);
|
||||
}
|
||||
self.pix.render().unwrap();
|
||||
}
|
||||
|
@ -124,21 +127,21 @@ impl WindowHandler for EmulatorWindow {
|
|||
}
|
||||
|
||||
pub struct EmulatorRenderer {
|
||||
tx: Sender<Vec<u32>>,
|
||||
tx: Sender<Vec<[u8; 4]>>,
|
||||
}
|
||||
|
||||
impl EmulatorRenderer {
|
||||
pub(super) fn new() -> (Self, Receiver<Vec<u32>>) {
|
||||
let (tx, rx) = mpsc::channel::<Vec<u32>>();
|
||||
pub(super) fn new() -> (Self, Receiver<Vec<[u8; 4]>>) {
|
||||
let (tx, rx) = mpsc::channel::<Vec<[u8; 4]>>();
|
||||
(Self { tx }, rx)
|
||||
}
|
||||
}
|
||||
|
||||
impl Renderer for EmulatorRenderer {
|
||||
impl Renderer<[u8; 4]> for EmulatorRenderer {
|
||||
fn prepare(&mut self, _width: usize, _height: usize) {}
|
||||
|
||||
#[allow(unused_must_use)]
|
||||
fn display(&mut self, buffer: &[u32]) {
|
||||
fn display(&mut self, buffer: &[[u8; 4]]) {
|
||||
self.tx.send(buffer.to_vec());
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use crate::processor::memory::mmio::gpu::Colour;
|
||||
pub use crate::processor::memory::mmio::joypad::JoypadState;
|
||||
pub use crate::{HEIGHT, WIDTH};
|
||||
use async_ringbuf::{AsyncHeapConsumer, AsyncHeapProducer, AsyncHeapRb};
|
||||
|
@ -12,10 +13,10 @@ pub enum RomFile {
|
|||
Raw(Vec<u8>),
|
||||
}
|
||||
|
||||
pub trait Renderer: Send {
|
||||
pub trait Renderer<Format: From<Colour>>: Send {
|
||||
fn prepare(&mut self, width: usize, height: usize);
|
||||
|
||||
fn display(&mut self, buffer: &[u32]);
|
||||
fn display(&mut self, buffer: &[Format]);
|
||||
|
||||
fn set_title(&mut self, _title: String) {}
|
||||
|
||||
|
|
|
@ -3,13 +3,17 @@
|
|||
let_chains,
|
||||
slice_flatten,
|
||||
async_closure,
|
||||
bigint_helper_methods
|
||||
bigint_helper_methods,
|
||||
associated_type_defaults
|
||||
)]
|
||||
|
||||
use crate::{processor::memory::Memory, util::pause};
|
||||
use connect::{AudioOutput, EmulatorMessage, Renderer, RomFile};
|
||||
use once_cell::sync::OnceCell;
|
||||
use processor::{memory::Rom, Cpu};
|
||||
use processor::{
|
||||
memory::{mmio::gpu::Colour, Rom},
|
||||
Cpu,
|
||||
};
|
||||
use std::{
|
||||
fs::{self},
|
||||
io::{stdout, Write},
|
||||
|
@ -43,20 +47,20 @@ static VERBOSE: OnceCell<bool> = OnceCell::new();
|
|||
pub const WIDTH: usize = 160;
|
||||
pub const HEIGHT: usize = 144;
|
||||
|
||||
pub struct EmulatorCore {
|
||||
pub struct EmulatorCore<ColourFormat: From<Colour> + Clone> {
|
||||
receiver: Receiver<EmulatorMessage>,
|
||||
cpu: Cpu,
|
||||
cpu: Cpu<ColourFormat>,
|
||||
cycle_num: usize,
|
||||
cycle_count: bool,
|
||||
}
|
||||
|
||||
impl EmulatorCore {
|
||||
impl<ColourFormat: From<Colour> + Clone> EmulatorCore<ColourFormat> {
|
||||
pub fn init(
|
||||
receiver: Receiver<EmulatorMessage>,
|
||||
options: Options,
|
||||
mut window: Box<dyn Renderer>,
|
||||
mut window: Box<dyn Renderer<ColourFormat>>,
|
||||
output: AudioOutput,
|
||||
tile_window: Option<Box<dyn Renderer>>,
|
||||
tile_window: Option<Box<dyn Renderer<ColourFormat>>>,
|
||||
) -> Self {
|
||||
VERBOSE.set(options.verbose).unwrap();
|
||||
|
||||
|
@ -116,7 +120,7 @@ impl EmulatorCore {
|
|||
)
|
||||
}
|
||||
|
||||
fn new(receiver: Receiver<EmulatorMessage>, cpu: Cpu, cycle_count: bool) -> Self {
|
||||
fn new(receiver: Receiver<EmulatorMessage>, cpu: Cpu<ColourFormat>, cycle_count: bool) -> Self {
|
||||
Self {
|
||||
receiver,
|
||||
cpu,
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use crate::{
|
||||
processor::{Cpu, Direction, Flags, Reg8, SplitRegister},
|
||||
processor::{memory::mmio::gpu::Colour, Cpu, Direction, Flags, Reg8, SplitRegister},
|
||||
util::{clear_bit, get_bit, set_bit},
|
||||
};
|
||||
|
||||
impl Cpu {
|
||||
impl<ColourFormat: From<Colour> + Clone> Cpu<ColourFormat> {
|
||||
pub(crate) fn and(&mut self, first: u8, second: u8) -> u8 {
|
||||
let result = first & second;
|
||||
self.set_or_clear_flag(Flags::Zero, result == 0x0);
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use crate::{
|
||||
processor::{Cpu, Direction, Flags, SplitRegister},
|
||||
processor::{memory::mmio::gpu::Colour, Cpu, Direction, Flags, SplitRegister},
|
||||
util::{as_signed, get_bit, get_rotation_carry, rotate, Nibbles},
|
||||
};
|
||||
use std::ops::{BitAnd, BitOr};
|
||||
|
||||
impl Cpu {
|
||||
impl<ColourFormat: From<Colour> + Clone> Cpu<ColourFormat> {
|
||||
pub(crate) fn pop_word(&mut self) -> u16 {
|
||||
let mut word: u16 = 0x0;
|
||||
word.set_low(self.memory.get(self.reg.sp));
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use self::mmio::{Apu, Gpu, Joypad, Serial, Timer};
|
||||
use self::mmio::{gpu::Colour, Apu, Gpu, Joypad, Serial, Timer};
|
||||
pub use self::rom::Rom;
|
||||
use crate::{
|
||||
connect::{AudioOutput, JoypadState, Renderer},
|
||||
|
@ -13,7 +13,7 @@ pub(crate) mod rom;
|
|||
|
||||
pub(crate) type Address = u16;
|
||||
|
||||
pub struct Memory {
|
||||
pub struct Memory<ColourFormat: From<Colour> + Clone> {
|
||||
bootrom: Option<Vec<u8>>,
|
||||
rom: Rom,
|
||||
ram: [u8; 8192],
|
||||
|
@ -23,20 +23,20 @@ pub struct Memory {
|
|||
pub(super) ime_scheduled: u8,
|
||||
dma_addr: u8,
|
||||
joypad: Joypad,
|
||||
gpu: Gpu,
|
||||
gpu: Gpu<ColourFormat>,
|
||||
apu: Apu,
|
||||
serial: Serial,
|
||||
timers: Timer,
|
||||
}
|
||||
|
||||
impl Memory {
|
||||
impl<ColourFormat: From<Colour> + Clone> Memory<ColourFormat> {
|
||||
pub fn init(
|
||||
bootrom: Option<Vec<u8>>,
|
||||
rom: Rom,
|
||||
window: Box<dyn Renderer>,
|
||||
window: Box<dyn Renderer<ColourFormat>>,
|
||||
output: AudioOutput,
|
||||
connect_serial: bool,
|
||||
tile_window: Option<Box<dyn Renderer>>,
|
||||
tile_window: Option<Box<dyn Renderer<ColourFormat>>>,
|
||||
) -> Self {
|
||||
let serial = if connect_serial {
|
||||
Serial::default().connected()
|
||||
|
@ -228,7 +228,7 @@ impl Memory {
|
|||
}
|
||||
}
|
||||
|
||||
impl Cpu {
|
||||
impl<ColourFormat: From<Colour> + Clone> Cpu<ColourFormat> {
|
||||
pub fn increment_timers(&mut self, machine_cycles: u8) {
|
||||
let steps = (machine_cycles as usize) * 4;
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use self::{
|
||||
tile_window::TileWindow,
|
||||
types::{
|
||||
Colour, DrawMode, GpuInterrupts, Lcdc, Oam, ObjPalette, ObjSize, Object, ObjectFlags,
|
||||
Palette, Stat, TiledataArea, TilemapArea, Vram,
|
||||
DrawMode, GpuInterrupts, Lcdc, Oam, ObjPalette, ObjSize, Object, ObjectFlags, Palette,
|
||||
Stat, TiledataArea, TilemapArea, Vram,
|
||||
},
|
||||
};
|
||||
use crate::{
|
||||
|
@ -11,6 +11,7 @@ use crate::{
|
|||
util::{clear_bit, get_bit},
|
||||
HEIGHT, WIDTH,
|
||||
};
|
||||
pub use types::Colour;
|
||||
|
||||
mod addresses;
|
||||
mod tile_window;
|
||||
|
@ -19,18 +20,18 @@ mod types;
|
|||
const TILE_WINDOW_WIDTH: usize = 16 * 8;
|
||||
const TILE_WINDOW_HEIGHT: usize = 24 * 8;
|
||||
|
||||
pub struct Gpu {
|
||||
pub buffer: Vec<u32>,
|
||||
pub struct Gpu<Format: From<Colour>> {
|
||||
pub buffer: Vec<Format>,
|
||||
pub vram: Vram,
|
||||
pub oam: Oam,
|
||||
pub window: Box<dyn Renderer>,
|
||||
pub window: Box<dyn Renderer<Format>>,
|
||||
is_bg_zero: Vec<bool>,
|
||||
lcdc: Lcdc,
|
||||
stat: Stat,
|
||||
mode_clock: usize,
|
||||
scanline: u8,
|
||||
lyc: u8,
|
||||
tile_window: Option<TileWindow>,
|
||||
tile_window: Option<TileWindow<Format>>,
|
||||
window_lc: u8,
|
||||
has_window_been_enabled: bool,
|
||||
bg_palette: Palette,
|
||||
|
@ -43,17 +44,21 @@ pub struct Gpu {
|
|||
prev_stat: bool,
|
||||
}
|
||||
|
||||
impl Gpu {
|
||||
pub fn new(window: Box<dyn Renderer>, tile_window_renderer: Option<Box<dyn Renderer>>) -> Self {
|
||||
impl<Format: From<Colour> + Clone> Gpu<Format> {
|
||||
pub fn new(
|
||||
window: Box<dyn Renderer<Format>>,
|
||||
tile_window_renderer: Option<Box<dyn Renderer<Format>>>,
|
||||
) -> Self {
|
||||
let tile_window = if let Some(mut tile_window_renderer) = tile_window_renderer {
|
||||
tile_window_renderer.prepare(TILE_WINDOW_WIDTH, TILE_WINDOW_HEIGHT);
|
||||
Some(TileWindow::new(tile_window_renderer))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let buffer = vec![Colour::Error.into(); WIDTH * HEIGHT];
|
||||
|
||||
Self {
|
||||
buffer: vec![0; WIDTH * HEIGHT],
|
||||
buffer,
|
||||
vram: Vram::default(),
|
||||
oam: Oam::default(),
|
||||
window,
|
||||
|
@ -162,7 +167,7 @@ impl Gpu {
|
|||
*e = true;
|
||||
}
|
||||
for x in 0..WIDTH {
|
||||
self.buffer[(scanline as usize * WIDTH) + x] = Colour::from_u8_rgb(255, 0, 255);
|
||||
self.buffer[(scanline as usize * WIDTH) + x] = Colour::Error.into();
|
||||
}
|
||||
if self.lcdc.bg_window_enable {
|
||||
self.render_scanline_bg(scanline);
|
||||
|
@ -175,7 +180,7 @@ impl Gpu {
|
|||
}
|
||||
} else {
|
||||
for x in 0..WIDTH {
|
||||
self.buffer[(scanline as usize * WIDTH) + x] = Colour::from_u8_rgb(255, 255, 255);
|
||||
self.buffer[(scanline as usize * WIDTH) + x] = Colour::Error.into();
|
||||
}
|
||||
}
|
||||
if self.lcdc.obj_enable {
|
||||
|
@ -295,7 +300,7 @@ impl Gpu {
|
|||
if x_coord < WIDTH {
|
||||
let buffer_index = (scanline as usize * WIDTH) + x_coord;
|
||||
if !object.flags.behind_bg_and_window || self.is_bg_zero[x_coord] {
|
||||
self.buffer[buffer_index] = colour.as_rgb();
|
||||
self.buffer[buffer_index] = colour.into();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -340,7 +345,7 @@ impl Gpu {
|
|||
let (colour, is_zero) = self.bg_palette.map_bits(lsb, msb);
|
||||
self.is_bg_zero[x] = is_zero;
|
||||
|
||||
self.buffer[(scanline as usize * WIDTH) + x] = colour.as_rgb();
|
||||
self.buffer[(scanline as usize * WIDTH) + x] = colour.into();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,10 +2,10 @@ use crate::util::{get_bit, set_or_clear_bit};
|
|||
|
||||
use super::{
|
||||
types::{DrawMode, ObjSize, Palette, TiledataArea, TilemapArea},
|
||||
Gpu,
|
||||
Colour, Gpu,
|
||||
};
|
||||
|
||||
impl Gpu {
|
||||
impl<Format: From<Colour>> Gpu<Format> {
|
||||
pub fn update_lcdc(&mut self, data: u8) {
|
||||
self.lcdc.enable = get_bit(data, 7);
|
||||
self.lcdc.window_tilemap = if get_bit(data, 6) {
|
||||
|
|
|
@ -4,23 +4,24 @@ use crate::{
|
|||
util::get_bit,
|
||||
};
|
||||
|
||||
use super::types::Vram;
|
||||
use super::{types::Vram, Colour};
|
||||
|
||||
pub(super) struct TileWindow {
|
||||
sprite_buffer: Vec<u32>,
|
||||
sprite_renderer: Box<dyn Renderer>,
|
||||
pub(super) struct TileWindow<Format: From<Colour>> {
|
||||
sprite_buffer: Vec<Format>,
|
||||
sprite_renderer: Box<dyn Renderer<Format>>,
|
||||
}
|
||||
|
||||
impl TileWindow {
|
||||
pub(super) fn new(window: Box<dyn Renderer>) -> Self {
|
||||
impl<Format: From<Colour>> TileWindow<Format> {
|
||||
pub(super) fn new(window: Box<dyn Renderer<Format>>) -> Self {
|
||||
let mut sprite_buffer = Vec::new();
|
||||
sprite_buffer.reserve_exact(TILE_WINDOW_WIDTH * TILE_WINDOW_HEIGHT);
|
||||
|
||||
Self {
|
||||
sprite_buffer: vec![0; TILE_WINDOW_WIDTH * TILE_WINDOW_HEIGHT],
|
||||
sprite_buffer,
|
||||
sprite_renderer: window,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TileWindow {
|
||||
pub(super) fn draw_sprite_window(&mut self, palette: Palette, memory: &Vram) {
|
||||
for tile_y in 0..16 {
|
||||
self.draw_row(
|
||||
|
@ -66,7 +67,7 @@ impl TileWindow {
|
|||
let colour = palette.map_bits(lsb, msb);
|
||||
|
||||
self.sprite_buffer[real_px_x + (real_px_y * TILE_WINDOW_WIDTH)] =
|
||||
colour.0.as_rgb();
|
||||
colour.0.into();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -84,28 +84,40 @@ impl Default for Lcdc {
|
|||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub(super) enum Colour {
|
||||
pub enum Colour {
|
||||
White,
|
||||
LightGray,
|
||||
DarkGray,
|
||||
Black,
|
||||
Error,
|
||||
}
|
||||
|
||||
impl From<Colour> for u32 {
|
||||
fn from(value: Colour) -> Self {
|
||||
let rgb = value.rgb_bytes();
|
||||
let (r, g, b) = (rgb.0 as u32, rgb.1 as u32, rgb.2 as u32);
|
||||
(r << 16) | (g << 8) | b
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Colour> for [u8; 4] {
|
||||
fn from(value: Colour) -> Self {
|
||||
let (r, g, b) = value.rgb_bytes();
|
||||
[r, g, b, 0xFF]
|
||||
}
|
||||
}
|
||||
|
||||
impl Colour {
|
||||
pub(super) fn as_rgb(&self) -> u32 {
|
||||
fn rgb_bytes(&self) -> (u8, u8, u8) {
|
||||
match self {
|
||||
Colour::White => Self::from_u8_rgb(0xFF, 0xFF, 0xFF),
|
||||
Colour::LightGray => Self::from_u8_rgb(0xAA, 0xAA, 0xAA),
|
||||
Colour::DarkGray => Self::from_u8_rgb(0x55, 0x55, 0x55),
|
||||
Colour::Black => Self::from_u8_rgb(0x00, 0x00, 0x00),
|
||||
Colour::White => (0xFF, 0xFF, 0xFF),
|
||||
Colour::LightGray => (0xAA, 0xAA, 0xAA),
|
||||
Colour::DarkGray => (0x55, 0x55, 0x55),
|
||||
Colour::Black => (0x00, 0x00, 0x00),
|
||||
Colour::Error => (0xFF, 0x00, 0x00),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn from_u8_rgb(r: u8, g: u8, b: u8) -> u32 {
|
||||
let (r, g, b) = (r as u32, g as u32, b as u32);
|
||||
(r << 16) | (g << 8) | b
|
||||
}
|
||||
|
||||
pub(super) fn from_bits(first: bool, second: bool) -> Colour {
|
||||
match (first, second) {
|
||||
(true, true) => Colour::Black,
|
||||
|
@ -121,6 +133,7 @@ impl Colour {
|
|||
Colour::LightGray => 0b10,
|
||||
Colour::DarkGray => 0b01,
|
||||
Colour::Black => 0b11,
|
||||
Colour::Error => 0b00,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use self::memory::{Interrupt, Memory};
|
||||
use self::memory::{mmio::gpu::Colour, Interrupt, Memory};
|
||||
use crate::verbose_println;
|
||||
|
||||
mod instructions;
|
||||
|
@ -18,8 +18,8 @@ pub(crate) enum Direction {
|
|||
Right,
|
||||
}
|
||||
|
||||
pub struct Cpu {
|
||||
pub memory: Memory,
|
||||
pub struct Cpu<ColourFormat: From<Colour> + Clone> {
|
||||
pub memory: Memory<ColourFormat>,
|
||||
pub reg: Registers,
|
||||
pub last_instruction: u8,
|
||||
last_instruction_addr: u16,
|
||||
|
@ -27,8 +27,8 @@ pub struct Cpu {
|
|||
should_halt_bug: bool,
|
||||
}
|
||||
|
||||
impl Cpu {
|
||||
pub fn new(mut memory: Memory, run_bootrom: bool) -> Self {
|
||||
impl<ColourFormat: From<Colour> + Clone> Cpu<ColourFormat> {
|
||||
pub fn new(mut memory: Memory<ColourFormat>, run_bootrom: bool) -> Self {
|
||||
if !run_bootrom {
|
||||
memory.cpu_ram_init();
|
||||
}
|
||||
|
|
|
@ -6,7 +6,9 @@ use crate::{
|
|||
util::as_signed,
|
||||
};
|
||||
|
||||
impl Cpu {
|
||||
use super::memory::mmio::gpu::Colour;
|
||||
|
||||
impl<ColourFormat: From<Colour> + Clone> Cpu<ColourFormat> {
|
||||
pub fn run_opcode(&mut self, opcode: u8) -> u8 {
|
||||
match opcode {
|
||||
0x00 => {
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
use crate::{processor::Direction, PAUSE_ENABLED, PAUSE_QUEUED, VERBOSE};
|
||||
use crate::{
|
||||
processor::{memory::mmio::gpu::Colour, Direction},
|
||||
PAUSE_ENABLED, PAUSE_QUEUED, VERBOSE,
|
||||
};
|
||||
use std::{io, mem::transmute};
|
||||
|
||||
#[macro_export]
|
||||
|
@ -122,7 +125,12 @@ impl Nibbles for u8 {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn scale_buffer(buffer: &[u32], width: usize, height: usize, factor: usize) -> Vec<u32> {
|
||||
pub fn scale_buffer<T: From<Colour> + Copy>(
|
||||
buffer: &[T],
|
||||
width: usize,
|
||||
height: usize,
|
||||
factor: usize,
|
||||
) -> Vec<T> {
|
||||
let mut v = vec![];
|
||||
for y in 0..height {
|
||||
for _ in 0..factor {
|
||||
|
|
Loading…
Reference in a new issue