split renderer works!!! no keyboard input any more though lolllll
This commit is contained in:
parent
c54027e5f6
commit
a36d161939
2
src/connect/mod.rs
Normal file
2
src/connect/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
mod renderer;
|
||||||
|
pub use renderer::Renderer;
|
7
src/connect/renderer.rs
Normal file
7
src/connect/renderer.rs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
pub trait Renderer {
|
||||||
|
fn prepare(&mut self, width: usize, height: usize);
|
||||||
|
|
||||||
|
fn display(&mut self, buffer: &[u32]);
|
||||||
|
|
||||||
|
fn set_title(&mut self, _title: String) {}
|
||||||
|
}
|
18
src/lib.rs
18
src/lib.rs
|
@ -10,8 +10,8 @@ use crate::{
|
||||||
processor::memory::Memory,
|
processor::memory::Memory,
|
||||||
util::{pause, print_cycles},
|
util::{pause, print_cycles},
|
||||||
};
|
};
|
||||||
|
use connect::Renderer;
|
||||||
use gilrs::Gilrs;
|
use gilrs::Gilrs;
|
||||||
use minifb::Window;
|
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use processor::{memory::Rom, Cpu};
|
use processor::{memory::Rom, Cpu};
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -20,6 +20,7 @@ use std::{
|
||||||
};
|
};
|
||||||
use util::pause_then_step;
|
use util::pause_then_step;
|
||||||
|
|
||||||
|
pub mod connect;
|
||||||
mod constants;
|
mod constants;
|
||||||
mod processor;
|
mod processor;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
@ -31,7 +32,6 @@ pub struct Options {
|
||||||
pub verbose: bool,
|
pub verbose: bool,
|
||||||
pub step_by: Option<usize>,
|
pub step_by: Option<usize>,
|
||||||
pub cycle_count: bool,
|
pub cycle_count: bool,
|
||||||
pub factor: usize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static mut PAUSE_ENABLED: bool = false;
|
static mut PAUSE_ENABLED: bool = false;
|
||||||
|
@ -41,13 +41,13 @@ static VERBOSE: OnceCell<bool> = OnceCell::new();
|
||||||
|
|
||||||
pub const WIDTH: usize = 160;
|
pub const WIDTH: usize = 160;
|
||||||
pub const HEIGHT: usize = 144;
|
pub const HEIGHT: usize = 144;
|
||||||
static FACTOR: OnceCell<usize> = OnceCell::new();
|
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
pub fn init(
|
||||||
pub fn init(options: Options, mut window: Window, tile_window: bool) {
|
options: Options,
|
||||||
|
mut window: Box<dyn Renderer>,
|
||||||
|
tile_window: Option<Box<dyn Renderer>>,
|
||||||
|
) {
|
||||||
VERBOSE.set(options.verbose).unwrap();
|
VERBOSE.set(options.verbose).unwrap();
|
||||||
FACTOR.set(options.factor).unwrap();
|
|
||||||
crate::processor::memory::mmio::gpu::init_statics();
|
|
||||||
|
|
||||||
let rom: Rom = match fs::read(options.rom_path) {
|
let rom: Rom = match fs::read(options.rom_path) {
|
||||||
Ok(data) => Rom::load(data),
|
Ok(data) => Rom::load(data),
|
||||||
|
@ -56,7 +56,9 @@ pub fn init(options: Options, mut window: Window, tile_window: bool) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
window.set_title(format!("{} on {}", rom.get_title(), rom.mbc_type()).as_str());
|
|
||||||
|
window.prepare(WIDTH, HEIGHT);
|
||||||
|
window.set_title(format!("{} on {}", rom.get_title(), rom.mbc_type()));
|
||||||
|
|
||||||
let bootrom_enabled = options.bootrom_path.is_some();
|
let bootrom_enabled = options.bootrom_path.is_some();
|
||||||
let bootrom: Option<Vec<u8>> = if let Some(path) = options.bootrom_path {
|
let bootrom: Option<Vec<u8>> = if let Some(path) = options.bootrom_path {
|
||||||
|
|
80
src/main.rs
80
src/main.rs
|
@ -1,5 +1,5 @@
|
||||||
use clap::{ArgGroup, Parser};
|
use clap::{ArgGroup, Parser};
|
||||||
use gb_emu::{HEIGHT, WIDTH};
|
use gb_emu::{connect::Renderer, util::scale_buffer};
|
||||||
|
|
||||||
use minifb::{Window, WindowOptions};
|
use minifb::{Window, WindowOptions};
|
||||||
|
|
||||||
|
@ -50,20 +50,6 @@ fn main() {
|
||||||
3
|
3
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut window = Window::new(
|
|
||||||
"emu",
|
|
||||||
WIDTH * factor,
|
|
||||||
HEIGHT * factor,
|
|
||||||
WindowOptions::default(),
|
|
||||||
)
|
|
||||||
.unwrap_or_else(|e| {
|
|
||||||
panic!("{e}");
|
|
||||||
});
|
|
||||||
|
|
||||||
window.set_position(500, 50);
|
|
||||||
|
|
||||||
window.topmost(true);
|
|
||||||
|
|
||||||
let options = gb_emu::Options {
|
let options = gb_emu::Options {
|
||||||
rom_path: args.rom,
|
rom_path: args.rom,
|
||||||
bootrom_path: args.bootrom,
|
bootrom_path: args.bootrom,
|
||||||
|
@ -71,8 +57,68 @@ fn main() {
|
||||||
verbose: args.verbose,
|
verbose: args.verbose,
|
||||||
step_by: args.step_by,
|
step_by: args.step_by,
|
||||||
cycle_count: args.cycle_count,
|
cycle_count: args.cycle_count,
|
||||||
factor,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
gb_emu::init(options, window, args.tile_window);
|
let tile_window: Option<Box<dyn Renderer>> = if args.tile_window {
|
||||||
|
Some(Box::new(WindowRenderer::new(factor)))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
gb_emu::init(options, Box::new(WindowRenderer::new(factor)), tile_window);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct WindowRenderer {
|
||||||
|
window: Option<Window>,
|
||||||
|
scaled_buf: Vec<u32>,
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
factor: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WindowRenderer {
|
||||||
|
fn new(factor: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
window: None,
|
||||||
|
scaled_buf: vec![],
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
factor,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Renderer for WindowRenderer {
|
||||||
|
fn prepare(&mut self, width: usize, height: usize) {
|
||||||
|
self.width = width;
|
||||||
|
self.height = height;
|
||||||
|
self.window = Some(
|
||||||
|
Window::new(
|
||||||
|
"Gameboy",
|
||||||
|
width * self.factor,
|
||||||
|
height * self.factor,
|
||||||
|
WindowOptions::default(),
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display(&mut self, buffer: &[u32]) {
|
||||||
|
if let Some(ref mut window) = self.window {
|
||||||
|
self.scaled_buf = scale_buffer(buffer, self.width, self.height, self.factor);
|
||||||
|
window
|
||||||
|
.update_with_buffer(
|
||||||
|
&self.scaled_buf,
|
||||||
|
self.width * self.factor,
|
||||||
|
self.height * self.factor,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_title(&mut self, title: String) {
|
||||||
|
if let Some(ref mut window) = self.window {
|
||||||
|
window.set_title(&title);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
use self::mmio::{Apu, Gpu, Joypad, Serial, Timer};
|
use self::mmio::{Apu, Gpu, Joypad, Serial, Timer};
|
||||||
pub use self::rom::Rom;
|
pub use self::rom::Rom;
|
||||||
use crate::{processor::SplitRegister, verbose_println, Cpu};
|
use crate::{connect::Renderer, processor::SplitRegister, verbose_println, Cpu};
|
||||||
use gilrs::Gilrs;
|
use gilrs::Gilrs;
|
||||||
use minifb::{Key, Window};
|
|
||||||
|
|
||||||
mod interrupts;
|
mod interrupts;
|
||||||
pub use interrupts::{Interrupt, Interrupts};
|
pub use interrupts::{Interrupt, Interrupts};
|
||||||
|
@ -33,9 +32,9 @@ impl Memory {
|
||||||
pub fn init(
|
pub fn init(
|
||||||
bootrom: Option<Vec<u8>>,
|
bootrom: Option<Vec<u8>>,
|
||||||
rom: Rom,
|
rom: Rom,
|
||||||
window: Window,
|
window: Box<dyn Renderer>,
|
||||||
connect_serial: bool,
|
connect_serial: bool,
|
||||||
enable_tile_window: bool,
|
tile_window: Option<Box<dyn Renderer>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let serial = if connect_serial {
|
let serial = if connect_serial {
|
||||||
Serial::default().connected()
|
Serial::default().connected()
|
||||||
|
@ -53,7 +52,7 @@ impl Memory {
|
||||||
ime_scheduled: 0x0,
|
ime_scheduled: 0x0,
|
||||||
dma_addr: 0xFF,
|
dma_addr: 0xFF,
|
||||||
joypad: Joypad::default(),
|
joypad: Joypad::default(),
|
||||||
gpu: Gpu::new(window, enable_tile_window),
|
gpu: Gpu::new(window, tile_window),
|
||||||
apu: Apu::init_default(),
|
apu: Apu::init_default(),
|
||||||
serial,
|
serial,
|
||||||
timers: Timer::init(),
|
timers: Timer::init(),
|
||||||
|
@ -185,8 +184,8 @@ impl Memory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_pressed_keys(&mut self, keys: Vec<Key>, gamepads: &mut Gilrs) -> bool {
|
pub fn update_pressed_keys(&mut self, gamepads: &mut Gilrs) -> bool {
|
||||||
self.joypad.update_pressed_keys(keys, gamepads)
|
self.joypad.update_pressed_keys(gamepads)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn cpu_ram_init(&mut self) {
|
pub(super) fn cpu_ram_init(&mut self) {
|
||||||
|
@ -247,9 +246,7 @@ impl Cpu {
|
||||||
.set_interrupt(Interrupt::LcdStat, gpu_interrupts.lcd_stat);
|
.set_interrupt(Interrupt::LcdStat, gpu_interrupts.lcd_stat);
|
||||||
|
|
||||||
if gpu_interrupts.vblank {
|
if gpu_interrupts.vblank {
|
||||||
let joypad_interrupt = self
|
let joypad_interrupt = self.memory.update_pressed_keys(&mut self.gamepad_handler);
|
||||||
.memory
|
|
||||||
.update_pressed_keys(self.memory.gpu.window.get_keys(), &mut self.gamepad_handler);
|
|
||||||
self.memory
|
self.memory
|
||||||
.interrupts
|
.interrupts
|
||||||
.set_interrupt(Interrupt::Joypad, joypad_interrupt);
|
.set_interrupt(Interrupt::Joypad, joypad_interrupt);
|
||||||
|
|
|
@ -6,12 +6,11 @@ use self::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
|
connect::Renderer,
|
||||||
processor::SplitRegister,
|
processor::SplitRegister,
|
||||||
util::{clear_bit, get_bit},
|
util::{clear_bit, get_bit},
|
||||||
FACTOR, HEIGHT, WIDTH,
|
HEIGHT, WIDTH,
|
||||||
};
|
};
|
||||||
use minifb::{Window, WindowOptions};
|
|
||||||
use once_cell::sync::OnceCell;
|
|
||||||
|
|
||||||
mod addresses;
|
mod addresses;
|
||||||
mod tile_window;
|
mod tile_window;
|
||||||
|
@ -19,24 +18,12 @@ mod types;
|
||||||
|
|
||||||
const TILE_WINDOW_WIDTH: usize = 16 * 8;
|
const TILE_WINDOW_WIDTH: usize = 16 * 8;
|
||||||
const TILE_WINDOW_HEIGHT: usize = 24 * 8;
|
const TILE_WINDOW_HEIGHT: usize = 24 * 8;
|
||||||
static TILE_WINDOW_WIDTH_SCALED: OnceCell<usize> = OnceCell::new();
|
|
||||||
static TILE_WINDOW_HEIGHT_SCALED: OnceCell<usize> = OnceCell::new();
|
|
||||||
|
|
||||||
pub fn init_statics() {
|
|
||||||
TILE_WINDOW_WIDTH_SCALED
|
|
||||||
.set(TILE_WINDOW_WIDTH * FACTOR.get().unwrap())
|
|
||||||
.unwrap();
|
|
||||||
TILE_WINDOW_HEIGHT_SCALED
|
|
||||||
.set(TILE_WINDOW_HEIGHT * FACTOR.get().unwrap())
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Gpu {
|
pub struct Gpu {
|
||||||
pub buffer: Vec<u32>,
|
pub buffer: Vec<u32>,
|
||||||
pub vram: Vram,
|
pub vram: Vram,
|
||||||
pub oam: Oam,
|
pub oam: Oam,
|
||||||
pub window: Window,
|
pub window: Box<dyn Renderer>,
|
||||||
scaled_buffer: Vec<u32>,
|
|
||||||
lcdc: Lcdc,
|
lcdc: Lcdc,
|
||||||
stat: Stat,
|
stat: Stat,
|
||||||
mode_clock: usize,
|
mode_clock: usize,
|
||||||
|
@ -55,21 +42,10 @@ pub struct Gpu {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Gpu {
|
impl Gpu {
|
||||||
pub fn new(window: Window, enable_tile_window: bool) -> Self {
|
pub fn new(window: Box<dyn Renderer>, tile_window_renderer: Option<Box<dyn Renderer>>) -> Self {
|
||||||
let tile_window = if enable_tile_window {
|
let tile_window = if let Some(mut tile_window_renderer) = tile_window_renderer {
|
||||||
let mut window = Window::new(
|
tile_window_renderer.prepare(TILE_WINDOW_WIDTH, TILE_WINDOW_HEIGHT);
|
||||||
"Tiles",
|
Some(TileWindow::new(tile_window_renderer))
|
||||||
*TILE_WINDOW_WIDTH_SCALED.get().unwrap(),
|
|
||||||
*TILE_WINDOW_HEIGHT_SCALED.get().unwrap(),
|
|
||||||
WindowOptions::default(),
|
|
||||||
)
|
|
||||||
.unwrap_or_else(|e| {
|
|
||||||
panic!("{e}");
|
|
||||||
});
|
|
||||||
|
|
||||||
window.set_position((550 + (WIDTH * FACTOR.get().unwrap())) as isize, 50);
|
|
||||||
window.topmost(true);
|
|
||||||
Some(TileWindow::new(window))
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
@ -79,7 +55,6 @@ impl Gpu {
|
||||||
vram: Vram::default(),
|
vram: Vram::default(),
|
||||||
oam: Oam::default(),
|
oam: Oam::default(),
|
||||||
window,
|
window,
|
||||||
scaled_buffer: vec![0; WIDTH * HEIGHT * 4],
|
|
||||||
lcdc: Lcdc::default(),
|
lcdc: Lcdc::default(),
|
||||||
stat: Stat::default(),
|
stat: Stat::default(),
|
||||||
mode_clock: 0,
|
mode_clock: 0,
|
||||||
|
@ -370,27 +345,6 @@ impl Gpu {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_window(&mut self) {
|
fn render_window(&mut self) {
|
||||||
self.scaled_buffer = scale_buffer(&self.buffer, WIDTH, HEIGHT, *FACTOR.get().unwrap());
|
self.window.display(&self.buffer);
|
||||||
self.window
|
|
||||||
.update_with_buffer(
|
|
||||||
&self.scaled_buffer,
|
|
||||||
WIDTH * FACTOR.get().unwrap(),
|
|
||||||
HEIGHT * FACTOR.get().unwrap(),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scale_buffer(buffer: &[u32], width: usize, height: usize, factor: usize) -> Vec<u32> {
|
|
||||||
let mut v = vec![];
|
|
||||||
for y in 0..height {
|
|
||||||
for _ in 0..factor {
|
|
||||||
for x in 0..width {
|
|
||||||
for _ in 0..factor {
|
|
||||||
v.push(buffer[(y * width) + x]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
v
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,32 +1,21 @@
|
||||||
use minifb::Window;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
processor::memory::mmio::gpu::{
|
connect::Renderer,
|
||||||
scale_buffer, Palette, TiledataArea, TILE_WINDOW_HEIGHT, TILE_WINDOW_HEIGHT_SCALED,
|
processor::memory::mmio::gpu::{Palette, TiledataArea, TILE_WINDOW_HEIGHT, TILE_WINDOW_WIDTH},
|
||||||
TILE_WINDOW_WIDTH, TILE_WINDOW_WIDTH_SCALED,
|
|
||||||
},
|
|
||||||
util::get_bit,
|
util::get_bit,
|
||||||
FACTOR,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::types::Vram;
|
use super::types::Vram;
|
||||||
|
|
||||||
pub(super) struct TileWindow {
|
pub(super) struct TileWindow {
|
||||||
sprite_buffer: Vec<u32>,
|
sprite_buffer: Vec<u32>,
|
||||||
sprite_buffer_scaled: Vec<u32>,
|
sprite_renderer: Box<dyn Renderer>,
|
||||||
sprite_window: Window,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TileWindow {
|
impl TileWindow {
|
||||||
pub(super) fn new(window: Window) -> Self {
|
pub(super) fn new(window: Box<dyn Renderer>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
sprite_buffer: vec![0; TILE_WINDOW_WIDTH * TILE_WINDOW_HEIGHT],
|
sprite_buffer: vec![0; TILE_WINDOW_WIDTH * TILE_WINDOW_HEIGHT],
|
||||||
sprite_buffer_scaled: vec![
|
sprite_renderer: window,
|
||||||
0;
|
|
||||||
TILE_WINDOW_WIDTH_SCALED.get().unwrap()
|
|
||||||
* TILE_WINDOW_HEIGHT_SCALED.get().unwrap()
|
|
||||||
],
|
|
||||||
sprite_window: window,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,19 +41,7 @@ impl TileWindow {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.sprite_buffer_scaled = scale_buffer(
|
self.sprite_renderer.display(&self.sprite_buffer);
|
||||||
&self.sprite_buffer,
|
|
||||||
TILE_WINDOW_WIDTH,
|
|
||||||
TILE_WINDOW_HEIGHT,
|
|
||||||
*FACTOR.get().unwrap(),
|
|
||||||
);
|
|
||||||
self.sprite_window
|
|
||||||
.update_with_buffer(
|
|
||||||
&self.sprite_buffer_scaled,
|
|
||||||
*TILE_WINDOW_WIDTH_SCALED.get().unwrap(),
|
|
||||||
*TILE_WINDOW_HEIGHT_SCALED.get().unwrap(),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_row(
|
fn draw_row(
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use crate::util::{clear_bit, get_bit};
|
use crate::util::{clear_bit, get_bit};
|
||||||
use gilrs::{Button, Gilrs};
|
use gilrs::{Button, Gilrs};
|
||||||
use minifb::Key;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub struct Joypad {
|
pub struct Joypad {
|
||||||
|
@ -57,7 +56,7 @@ impl Joypad {
|
||||||
self.sel_direction = !get_bit(data, 4);
|
self.sel_direction = !get_bit(data, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_pressed_keys(&mut self, keys: Vec<Key>, gamepad_handler: &mut Gilrs) -> bool {
|
pub fn update_pressed_keys(&mut self, gamepad_handler: &mut Gilrs) -> bool {
|
||||||
let old = *self;
|
let old = *self;
|
||||||
self.clear_buttons();
|
self.clear_buttons();
|
||||||
|
|
||||||
|
@ -87,14 +86,6 @@ impl Joypad {
|
||||||
self.a |= pad.is_pressed(Button::East);
|
self.a |= pad.is_pressed(Button::East);
|
||||||
self.b |= pad.is_pressed(Button::South);
|
self.b |= pad.is_pressed(Button::South);
|
||||||
}
|
}
|
||||||
self.down |= keys.contains(&Key::Down) || keys.contains(&Key::S);
|
|
||||||
self.up |= keys.contains(&Key::Up) || keys.contains(&Key::W);
|
|
||||||
self.left |= keys.contains(&Key::Left) || keys.contains(&Key::A);
|
|
||||||
self.right |= keys.contains(&Key::Right) || keys.contains(&Key::D);
|
|
||||||
self.start |= keys.contains(&Key::Equal);
|
|
||||||
self.select |= keys.contains(&Key::Minus);
|
|
||||||
self.a |= keys.contains(&Key::Apostrophe);
|
|
||||||
self.b |= keys.contains(&Key::Semicolon);
|
|
||||||
*self != old
|
*self != old
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
14
src/util.rs
14
src/util.rs
|
@ -137,3 +137,17 @@ impl Nibbles for u8 {
|
||||||
*self = (*self & 0x0F) | (val << 4);
|
*self = (*self & 0x0F) | (val << 4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn scale_buffer(buffer: &[u32], width: usize, height: usize, factor: usize) -> Vec<u32> {
|
||||||
|
let mut v = vec![];
|
||||||
|
for y in 0..height {
|
||||||
|
for _ in 0..factor {
|
||||||
|
for x in 0..width {
|
||||||
|
for _ in 0..factor {
|
||||||
|
v.push(buffer[(y * width) + x]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue