1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
use crate::{
app::Config,
core::Nes,
utils::{
self, check_error, create_f32_slice_vao, create_program, create_screen_texture, set_uniform,
},
};
use glow::{Context, HasContext, NativeProgram, NativeTexture, NativeVertexArray};
use sdl2::{video::GLContext, VideoSubsystem};
/// The window of the emulator app.
///
/// Responsible for the actual graphical output of the emualator.
/// Acts as the bridge between an [Nes]'s RGB output array, and an SDL window.
/// Takes the RGB output, pipes that data into on OpenGL texture, and renders that texture
/// to the screen of the window.
/// See [Window::render].
pub struct Window {
window: sdl2::video::Window,
gl_context: GLContext,
// screen: Screen,
gl: Context,
// Stuff for rendering to screen
screen_texture: NativeTexture,
screen_program: NativeProgram,
screen_vao: NativeVertexArray,
// Program for rendering a primitve using wireframe
wireframe_program: NativeProgram,
wireframe_vao: NativeVertexArray,
// Screen data buffer
screen_buffer: [[[u8; 3]; 256]; 240],
}
impl Window {
/// Create a new [Window] from an SDL video subsystem
pub fn from_sdl_video(video: &mut VideoSubsystem) -> Window {
// Set up openGL
let gl_attr = video.gl_attr();
gl_attr.set_context_profile(sdl2::video::GLProfile::Core);
gl_attr.set_context_version(3, 3);
// Init SDL window
let window_width = 256 * 3;
let window_height = 240 * 3;
let (window, gl_context, gl) =
utils::create_window(video, "Y.A.N.E.", window_width, window_height);
unsafe {
let (screen_vao, screen_program, screen_texture) =
create_screen_texture(&gl, (256, 240));
let wireframe_program = create_program(
&gl,
include_str!("../shaders/wireframe.vert"),
include_str!("../shaders/color.frag"),
);
let wireframe_vao = create_f32_slice_vao(
&gl,
[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]].as_flattened(),
2,
);
Window {
window,
gl_context,
// screen,
gl,
screen_program,
screen_texture,
screen_vao,
wireframe_program,
wireframe_vao,
screen_buffer: [[[0; 3]; 256]; 240],
}
}
}
/// Render the [Nes]'s video output to the window.
pub fn render(&mut self, nes: &Nes, config: &Config) {
self.window.gl_make_current(&self.gl_context).unwrap();
unsafe {
self.gl.disable(glow::STENCIL_TEST);
self.gl.disable(glow::DEPTH_TEST);
self.gl.bind_framebuffer(glow::FRAMEBUFFER, None);
self.gl.clear_color(0.0, 0.0, 0.0, 1.0);
self.gl.clear(glow::COLOR_BUFFER_BIT);
check_error!(self.gl);
self.gl.use_program(Some(self.screen_program));
const TEX_NUM: i32 = 1;
self.gl.active_texture(glow::TEXTURE0 + TEX_NUM as u32);
self.gl
.bind_texture(glow::TEXTURE_2D, Some(self.screen_texture));
// Copy output
nes.ppu.rgb_output_buf(&mut self.screen_buffer);
// Flatten to a single slice
let texture_data: &[u8] = self.screen_buffer.as_flattened().as_flattened();
// Pipe to texture
self.gl.tex_image_2d(
glow::TEXTURE_2D,
0,
glow::RGB as i32,
256,
240,
0,
glow::RGB,
glow::UNSIGNED_BYTE,
Some(texture_data),
);
set_uniform!(
self.gl,
self.screen_program,
"screenSize",
uniform_2_f32,
config.screen_size.0 as f32,
config.screen_size.1 as f32
);
check_error!(self.gl);
let loc = self
.gl
.get_uniform_location(self.screen_program, "renderedTexture");
self.gl.uniform_1_i32(loc.as_ref(), TEX_NUM);
check_error!(self.gl);
self.gl.viewport(
0,
0,
self.window.size().0 as i32,
self.window.size().1 as i32,
);
check_error!(self.gl);
self.gl.bind_vertex_array(Some(self.screen_vao));
check_error!(self.gl);
self.gl.draw_arrays(glow::TRIANGLES, 0, 6);
check_error!(self.gl);
// Render wireframe box around each OAM object
if config.oam_debug {
self.gl.use_program(Some(self.wireframe_program));
self.gl.bind_vertex_array(Some(self.wireframe_vao));
check_error!(self.gl);
set_uniform!(
self.gl,
self.wireframe_program,
"screenSize",
uniform_2_f32,
config.screen_size.0 as f32,
config.screen_size.1 as f32
);
check_error!(self.gl);
nes.ppu.oam.chunks(4).enumerate().for_each(|(i, obj)| {
set_uniform!(
self.gl,
self.wireframe_program,
"position",
uniform_2_f32,
obj[3] as f32,
obj[0] as f32 + 1.0
);
let color = if i == 0 {
[0.0, 1.0, 0.0]
} else {
[1.0, 0.0, 0.0]
};
set_uniform!(
self.gl,
self.wireframe_program,
"inColor",
uniform_3_f32,
color[0],
color[1],
color[2]
);
set_uniform!(
self.gl,
self.wireframe_program,
"sizes",
uniform_2_f32,
1.0,
if nes.ppu.is_8x16_sprites() { 2.0 } else { 1.0 }
);
self.gl.draw_arrays(glow::LINE_LOOP, 0, 4);
});
}
}
self.window.gl_swap_window();
}
/// Get a mutable reference to the SDL [Window][sdl2::video::Window]
///
/// Allows configuration of the underlying SDL window such as changing title, icon, etc
pub fn sdl_window(&mut self) -> &mut sdl2::video::Window {
&mut self.window
}
}