Compare commits
3 Commits
a35c316800
...
1aff62163a
Author | SHA1 | Date |
---|---|---|
Robin Hübner | 1aff62163a | |
Robin Hübner | 0a08fa128b | |
Robin Hübner | e09f663b9b |
|
@ -0,0 +1,4 @@
|
||||||
|
[analysis.config.StaticAnalysisConfig]
|
||||||
|
|
||||||
|
undocumented_declaration_check="disabled"
|
||||||
|
long_line_check="disabled"
|
763
source/app.d
763
source/app.d
|
@ -1,560 +1,218 @@
|
||||||
import std.stdio;
|
import std.stdio;
|
||||||
import derelict.sdl2.sdl;
|
import derelict.sdl2.sdl;
|
||||||
import derelict.imgui.imgui;
|
import derelict.imgui.imgui;
|
||||||
import glad.gl.all;
|
|
||||||
|
|
||||||
|
import gl : PixelBuffer;
|
||||||
import window;
|
import window;
|
||||||
import imgui;
|
import imgui;
|
||||||
|
|
||||||
bool checkShaderError(GLuint shader, GLuint flag, bool is_program, in char[] shader_path) nothrow {
|
struct Chip8Status {
|
||||||
|
|
||||||
GLint result;
|
struct Assembly {
|
||||||
|
|
||||||
|
import assembler;
|
||||||
|
|
||||||
(is_program) ? glGetProgramiv(shader, flag, &result)
|
alias OpCode = Assembler.OpCode;
|
||||||
: glGetShaderiv(shader, flag, &result);
|
alias Instr = Assembler.Instruction;
|
||||||
|
alias Arg = Assembler.Argument;
|
||||||
|
|
||||||
if (result == GL_FALSE) {
|
enum ViewMode {
|
||||||
|
Memory,
|
||||||
GLchar[256] log; //FIXME this is potentially fatal
|
Disassembly
|
||||||
(is_program) ? glGetProgramInfoLog(shader, log.sizeof, null, log.ptr)
|
|
||||||
: glGetShaderInfoLog(shader, log.sizeof, null, log.ptr);
|
|
||||||
|
|
||||||
printf("[OpenGL] Error in %s: %s\n", shader_path.ptr, log.ptr);
|
|
||||||
return false;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
|
|
||||||
} // checkShaderError
|
|
||||||
|
|
||||||
struct PixelBuffer {
|
|
||||||
|
|
||||||
struct Shader {
|
|
||||||
|
|
||||||
private {
|
|
||||||
GLuint shader_prog;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@disable this(this);
|
enum DisassemblyMode {
|
||||||
|
Linear,
|
||||||
void compile(const (GLchar*)* vs_source, const (GLchar*)* fs_source) {
|
Recursive,
|
||||||
|
Dynamic
|
||||||
import core.stdc.stdlib : exit;
|
}
|
||||||
|
|
||||||
GLuint new_vs_shader = glCreateShader(GL_VERTEX_SHADER);
|
|
||||||
glShaderSource(new_vs_shader, 1, vs_source, null);
|
|
||||||
glCompileShader(new_vs_shader);
|
|
||||||
if (!checkShaderError(new_vs_shader, GL_COMPILE_STATUS, false, "vertex_shader")) {
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
GLuint new_fs_shader = glCreateShader(GL_FRAGMENT_SHADER);
|
|
||||||
glShaderSource(new_fs_shader, 1, fs_source, null);
|
|
||||||
glCompileShader(new_fs_shader);
|
|
||||||
if (!checkShaderError(new_fs_shader, GL_COMPILE_STATUS, false, "fragment_shader")) {
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
GLuint new_shader = glCreateProgram();
|
|
||||||
glAttachShader(new_shader, new_vs_shader);
|
|
||||||
glAttachShader(new_shader, new_fs_shader);
|
|
||||||
|
|
||||||
// glBindAttribLocation(new_shader, 0, "screen_size");
|
|
||||||
|
|
||||||
glLinkProgram(new_shader);
|
|
||||||
if (!checkShaderError(new_shader, GL_LINK_STATUS, true, "shader_program")) {
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
glValidateProgram(new_shader);
|
|
||||||
if (!checkShaderError(new_shader, GL_VALIDATE_STATUS, true, "shader_program")) {
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
shader_prog = new_shader;
|
|
||||||
|
|
||||||
} // compile
|
|
||||||
|
|
||||||
void setUniforms(int w, int h) {
|
|
||||||
auto attr_loc = glGetUniformLocation(shader_prog, "screen_size");
|
|
||||||
glUniform2f(attr_loc, cast(float)w, cast(float)h);
|
|
||||||
} // setUniforms
|
|
||||||
|
|
||||||
void bind() {
|
|
||||||
glUseProgram(shader_prog);
|
|
||||||
} // bind
|
|
||||||
|
|
||||||
void unbind() {
|
|
||||||
glUseProgram(0);
|
|
||||||
} // unbind
|
|
||||||
|
|
||||||
} // Shader
|
|
||||||
|
|
||||||
struct Texture {
|
|
||||||
|
|
||||||
import derelict.sdl2.sdl;
|
|
||||||
import derelict.sdl2.image;
|
|
||||||
|
|
||||||
private {
|
private {
|
||||||
|
|
||||||
GLuint texture_; //OpenGL handle for texture
|
Chip8* emu_;
|
||||||
GLenum input_format_, output_format_, data_type_;
|
ViewMode mode_;
|
||||||
int width_, height_;
|
int range_start_;
|
||||||
|
int range_end_;
|
||||||
|
|
||||||
|
// disassembly state
|
||||||
|
DisassemblyMode ass_mode_;
|
||||||
|
Instr[] disassembly_;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@property @nogc nothrow {
|
void initialize(Chip8* emu) {
|
||||||
|
mode_ = ViewMode.Disassembly;
|
||||||
|
emu_ = emu;
|
||||||
|
} // initialize
|
||||||
|
|
||||||
|
static const (char)* formatQual(Arg a, bool with_comma = false) {
|
||||||
|
if (with_comma) {
|
||||||
|
switch (a) with (Arg) {
|
||||||
|
case Vx, Vy, V0: return "V%hu,";
|
||||||
|
case nibble: return "%01X,";
|
||||||
|
case beit: return "0x%02X,";
|
||||||
|
case addr: return "0x%03X,";
|
||||||
|
default: return "%s,";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch (a) with (Arg) {
|
||||||
|
case Vx, Vy, V0: return "V%hu";
|
||||||
|
case nibble: return "0x%01X";
|
||||||
|
case beit: return "0x%02X";
|
||||||
|
case addr: return "0x%03X";
|
||||||
|
default: return "%s";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // formatQual
|
||||||
|
|
||||||
int width() const { return width_; }
|
void disassemble() {
|
||||||
int height() const { return height_; }
|
ubyte[] instrs = emu_.ram[range_start_ .. range_end_];
|
||||||
GLuint handle() { return texture_; }
|
disassembly_ = Assembler.disassemble(instrs);
|
||||||
|
} // disassemble
|
||||||
|
|
||||||
}
|
void setRange(ushort start, ushort end) {
|
||||||
|
range_start_ = start;
|
||||||
|
range_end_ = end;
|
||||||
|
} // setRange
|
||||||
|
|
||||||
@disable this(this);
|
void reset() {
|
||||||
|
disassembly_ = null;
|
||||||
|
} // reset
|
||||||
|
|
||||||
nothrow @nogc
|
void draw() {
|
||||||
this(int width, int height, GLenum input_format, GLenum output_format, GLenum unpack_alignment) {
|
|
||||||
|
|
||||||
width_ = width;
|
import std.range : chunks;
|
||||||
height_ = height;
|
|
||||||
input_format_ = input_format;
|
|
||||||
output_format_ = output_format;
|
|
||||||
data_type_ = GL_UNSIGNED_BYTE;
|
|
||||||
|
|
||||||
glGenTextures(1, &texture_);
|
igBegin("Emulator Assembly");
|
||||||
glBindTexture(GL_TEXTURE_2D, texture_);
|
igRadioButton("Memory View", cast(int*)&mode_, cast(int)ViewMode.Memory); igSameLine();
|
||||||
|
igRadioButton("Disassembly View", cast(int*)&mode_, cast(int)ViewMode.Disassembly);
|
||||||
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
if (mode_ == ViewMode.Memory) {
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
} else if (mode_ == ViewMode.Disassembly) {
|
||||||
|
igInputInt("Range Start ", &range_start_);
|
||||||
|
igInputInt("Range End ", &range_end_);
|
||||||
|
igNewLine();
|
||||||
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
igText("Disassembly Mode");
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
igRadioButton("Linear", cast(int*)&ass_mode_, cast(int)DisassemblyMode.Linear); igSameLine();
|
||||||
|
igRadioButton("Recursive", cast(int*)&ass_mode_, cast(int)DisassemblyMode.Recursive); igSameLine();
|
||||||
|
igRadioButton("Dynamic", cast(int*)&ass_mode_, cast(int)DisassemblyMode.Dynamic);
|
||||||
|
if (igButton("Disassemble")) {
|
||||||
|
final switch (ass_mode_) with (DisassemblyMode) {
|
||||||
|
case Linear:
|
||||||
|
disassemble();
|
||||||
|
break;
|
||||||
|
case Recursive:
|
||||||
|
break;
|
||||||
|
case Dynamic:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (range_start_ < 0) range_start_ = 0;
|
||||||
|
if (range_start_ > ushort.max) range_start_ = 0;
|
||||||
|
|
||||||
glPixelStorei(GL_UNPACK_ALIGNMENT, unpack_alignment);
|
if (range_end_ < range_start_) range_end_ = 0;
|
||||||
glTexImage2D(GL_TEXTURE_2D, 0, input_format_, width_, height_, 0, output_format_, GL_UNSIGNED_BYTE, cast(void*)0);
|
if (range_end_ > ushort.max) range_end_ = 0;
|
||||||
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
if (mode_ == ViewMode.Memory) {
|
||||||
|
/*
|
||||||
|
foreach (ref ubyte[] b; emu_.ram[0x200 + range_start_ .. range_end_].chunks(2)) {
|
||||||
|
ushort opcode = (*(cast(ushort*)(b.ptr)));
|
||||||
|
igText("0x%04X", opcode);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
} else if (mode_ == ViewMode.Disassembly) {
|
||||||
|
|
||||||
} //this
|
int cur_offset = range_start_;
|
||||||
|
foreach (ref instr; disassembly_) {
|
||||||
|
|
||||||
nothrow @nogc
|
if (emu_.cpu.pc == cur_offset) {
|
||||||
void create(void* pixels, int width, int height, GLenum input_format = GL_RGB, GLenum output_format = GL_RGB, GLenum data_type = GL_UNSIGNED_BYTE) {
|
igTextColored(ImColor(0, 255, 0), "0x%03X |", cur_offset);
|
||||||
|
cur_offset += 2;
|
||||||
|
igSameLine();
|
||||||
|
} else {
|
||||||
|
igText("0x%03X |", cur_offset);
|
||||||
|
cur_offset += 2;
|
||||||
|
igSameLine();
|
||||||
|
}
|
||||||
|
|
||||||
width_ = width;
|
switch (instr.args) {
|
||||||
height_ = height;
|
case 3:
|
||||||
input_format_ = input_format;
|
|
||||||
output_format_ = output_format;
|
|
||||||
data_type_ = data_type;
|
|
||||||
|
|
||||||
//generate single texture, put handle in texture
|
igText("%s", instr.op);
|
||||||
glGenTextures(1, &texture_);
|
igSameLine();
|
||||||
|
|
||||||
//normal 2d texture, bind to our texture handle
|
Arg a1_arg;
|
||||||
glBindTexture(GL_TEXTURE_2D, texture_);
|
auto a1 = instr.a1(a1_arg);
|
||||||
|
igText(formatQual(a1_arg, true), a1);
|
||||||
|
igSameLine();
|
||||||
|
|
||||||
//set texture parameters in currently bound texture, controls texture wrapping (or GL_CLAMP?)
|
Arg a2_arg;
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
auto a2 = instr.a2(a2_arg);
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
igText(formatQual(a2_arg, true), a2);
|
||||||
|
igSameLine();
|
||||||
|
|
||||||
//linearly interpolate between pixels, MIN if texture is too small for drawing area, MAG if drawing area is smaller than texture
|
Arg a3_arg;
|
||||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
auto a3 = instr.a3(a3_arg);
|
||||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
igText(formatQual(a3_arg), a3);
|
||||||
|
|
||||||
//texture type, level, format to store as, width, height, border, format loaded in
|
break;
|
||||||
glTexImage2D(GL_TEXTURE_2D, 0, input_format_, width_, height_, 0, output_format_, data_type_, pixels);
|
|
||||||
|
|
||||||
//UNBIND
|
case 2:
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
|
igText("%s", instr.op);
|
||||||
|
igSameLine();
|
||||||
|
|
||||||
} // this
|
Arg a1_arg;
|
||||||
|
auto a1 = instr.a1(a1_arg);
|
||||||
|
igText(formatQual(a1_arg, true), a1);
|
||||||
|
igSameLine();
|
||||||
|
|
||||||
~this() nothrow @nogc {
|
Arg a2_arg;
|
||||||
|
auto a2 = instr.a2(a2_arg);
|
||||||
|
igText(formatQual(a2_arg), a2);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
if (texture_ != 0) {
|
case 1:
|
||||||
glDeleteTextures(1, &texture_);
|
|
||||||
|
igText("%s", instr.op);
|
||||||
|
igSameLine();
|
||||||
|
|
||||||
|
Arg a1_arg;
|
||||||
|
auto a1 = instr.a1(a1_arg);
|
||||||
|
igText(formatQual(a1_arg), a1);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0:
|
||||||
|
|
||||||
|
igText("%s", instr.op);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // ~this
|
igEnd();
|
||||||
|
|
||||||
/**
|
|
||||||
* Binds the texture handle, takes an argument for which texture unit to use.
|
|
||||||
*/
|
|
||||||
nothrow @nogc
|
|
||||||
void bind(int unit) {
|
|
||||||
|
|
||||||
assert(unit >= 0 && unit <= 31);
|
|
||||||
glActiveTexture(GL_TEXTURE0 + unit); //since this is sequential, this works
|
|
||||||
glBindTexture(GL_TEXTURE_2D, texture_);
|
|
||||||
|
|
||||||
} // bind
|
|
||||||
|
|
||||||
nothrow @nogc
|
|
||||||
void unbind() {
|
|
||||||
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
|
|
||||||
} // unbind
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the texture in place given the new texture buffer.
|
|
||||||
* Takes an optional offset to update only a part of the texture.
|
|
||||||
**/
|
|
||||||
nothrow @nogc
|
|
||||||
void update(void[] pixels, size_t offset = 0) {
|
|
||||||
|
|
||||||
bind(0);
|
|
||||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width_, height_, input_format_, data_type_, pixels.ptr);
|
|
||||||
unbind();
|
|
||||||
|
|
||||||
} // update
|
|
||||||
|
|
||||||
} // Texture
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generic VertexArray structure, used to upload data of any given vertex type to the GPU.
|
|
||||||
*/
|
|
||||||
struct VertexArray {
|
|
||||||
|
|
||||||
private {
|
|
||||||
|
|
||||||
GLuint vao_;
|
|
||||||
GLuint vbo_;
|
|
||||||
GLenum type_; // type of vertex data, GL_TRIANGLES etc
|
|
||||||
uint num_vertices_;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@disable this(this);
|
|
||||||
|
|
||||||
nothrow @nogc
|
|
||||||
void create(in float[2][] vertices, GLenum draw_type = GL_STATIC_DRAW, GLenum primitive = GL_TRIANGLES) {
|
|
||||||
|
|
||||||
this.num_vertices_ = cast(uint)vertices.length;
|
|
||||||
this.type_ = primitive;
|
|
||||||
|
|
||||||
glGenVertexArrays(1, &vao_);
|
|
||||||
glBindVertexArray(vao_);
|
|
||||||
|
|
||||||
glGenBuffers(1, &vbo_);
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, vbo_);
|
|
||||||
glBufferData(GL_ARRAY_BUFFER, vertices.length * vertices[0].sizeof, vertices.ptr, draw_type);
|
|
||||||
|
|
||||||
// pos
|
|
||||||
glEnableVertexAttribArray(0);
|
|
||||||
glVertexAttribPointer(0,
|
|
||||||
2,
|
|
||||||
GL_FLOAT,
|
|
||||||
GL_FALSE,
|
|
||||||
float.sizeof * 2,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
glBindVertexArray(0);
|
|
||||||
|
|
||||||
} // this
|
|
||||||
|
|
||||||
~this() nothrow @nogc {
|
|
||||||
if (vao_ != 0) {
|
|
||||||
glDeleteVertexArrays(1, &vao_);
|
|
||||||
}
|
|
||||||
} // ~this
|
|
||||||
|
|
||||||
void bind() nothrow @nogc {
|
|
||||||
glBindVertexArray(vao_);
|
|
||||||
} // bind
|
|
||||||
|
|
||||||
void draw() nothrow @nogc {
|
|
||||||
glDrawArrays(type_, 0, num_vertices_);
|
|
||||||
} // draw
|
} // draw
|
||||||
|
|
||||||
void unbind() nothrow @nogc {
|
} // Assembly
|
||||||
glBindVertexArray(0);
|
|
||||||
} // unbind
|
|
||||||
|
|
||||||
} // VertexArray
|
|
||||||
|
|
||||||
const char* vs_shader = q{
|
|
||||||
#version 330 core
|
|
||||||
|
|
||||||
uniform vec2 screen_size;
|
|
||||||
|
|
||||||
layout(location = 0) in vec2 pos;
|
|
||||||
|
|
||||||
out vec2 frag_uv;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_Position = vec4(pos, 0.0, 1.0);
|
|
||||||
frag_uv = clamp(pos, vec2(0.0, 0.0), vec2(1.0, 1.0));
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
const char* fs_shader = q{
|
|
||||||
#version 330 core
|
|
||||||
|
|
||||||
uniform sampler2D tex;
|
|
||||||
|
|
||||||
in vec2 frag_uv;
|
|
||||||
|
|
||||||
out vec4 out_col;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
float v = texture(tex, frag_uv.st).r * 255;
|
|
||||||
out_col = vec4(v, v, v, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
Shader shader;
|
|
||||||
VertexArray vao;
|
|
||||||
Texture tex;
|
|
||||||
|
|
||||||
void create(void* pixels, int w, int h) {
|
|
||||||
|
|
||||||
float[2][6] rect = [
|
|
||||||
[-1.0f, -1.0f], // top left
|
|
||||||
[1.0f, -1.0f], // top right
|
|
||||||
[1.0f, 1.0f], // bottom right
|
|
||||||
|
|
||||||
[-1.0f, -1.0f], // top left
|
|
||||||
[-1.0f, 1.0f], // bottom left
|
|
||||||
[1.0f, 1.0f], // bottom right
|
|
||||||
];
|
|
||||||
|
|
||||||
vao.create(rect[]);
|
|
||||||
tex.create(pixels, w, h, GL_RED, GL_RED);
|
|
||||||
shader.compile(&vs_shader, &fs_shader);
|
|
||||||
|
|
||||||
} // create
|
|
||||||
|
|
||||||
void draw(int screen_w, int screen_h) {
|
|
||||||
|
|
||||||
vao.bind();
|
|
||||||
shader.bind();
|
|
||||||
shader.setUniforms(screen_w, screen_h);
|
|
||||||
tex.bind(0);
|
|
||||||
vao.draw();
|
|
||||||
|
|
||||||
} // draw
|
|
||||||
|
|
||||||
nothrow @nogc
|
|
||||||
void update(void[] pixels, size_t offset = 0) {
|
|
||||||
tex.update(pixels, offset);
|
|
||||||
} // update
|
|
||||||
|
|
||||||
} // PixelBuffer
|
|
||||||
|
|
||||||
struct Assembler {
|
|
||||||
|
|
||||||
/*
|
|
||||||
Instruction Glossary:
|
|
||||||
0x0nnn - SYS addr
|
|
||||||
0x00E0 - CLS
|
|
||||||
0x00EE - RET
|
|
||||||
0x1nnn - JP addr
|
|
||||||
0x2nnn - CALL addr
|
|
||||||
0x3xkk - SE Vx, byte
|
|
||||||
0x4xkk - SNE Vx, byte
|
|
||||||
0x5xy0 - SE Vx, Vy
|
|
||||||
0x6xkk - LD Vx, byte
|
|
||||||
0x7xkk - ADD Vx, byte
|
|
||||||
0x8xy0 - LD Vx, Vy
|
|
||||||
0x8xy1 - OR Vx, Vy
|
|
||||||
0x8xy2 - AND Vx, Vy
|
|
||||||
0x8xy3 - XOR Vx, Vy
|
|
||||||
0x8xy4 - ADD Vx, Vy
|
|
||||||
0x8xy5 - SUB Vx, Vy
|
|
||||||
0x8xy6 - SHR Vx {, Vy}
|
|
||||||
0x8xy7 - SUBN Vx, Vy
|
|
||||||
0x8xyE - SHL Vx {, Vy}
|
|
||||||
0x9xy0 - SNE Vx, Vy
|
|
||||||
0xAnnn - LD I, addr
|
|
||||||
0xBnnn - JP V0, addr
|
|
||||||
0xCxkk - RND Vx, byte
|
|
||||||
0xDxyn - DRW Vx, Vy, nibble
|
|
||||||
0xEx9E - SKP Vx
|
|
||||||
0xExA1 - SKNP Vx
|
|
||||||
0xFx07 - LD Vx, DT
|
|
||||||
0xFx0A - LD Vx, K
|
|
||||||
0xFx15 - LD DT, Vx
|
|
||||||
0xFx18 - LD ST, Vx
|
|
||||||
0xFx1E - ADD I, Vx
|
|
||||||
0xFx29 - LD F, Vx
|
|
||||||
0xFx33 - LD B, Vx
|
|
||||||
0xFx55 - LD [I], Vx
|
|
||||||
0xFx65 - LD Vx, [I]
|
|
||||||
*/
|
|
||||||
|
|
||||||
enum OpCode {
|
|
||||||
|
|
||||||
CLS, // CLS
|
|
||||||
RET, // RET
|
|
||||||
CALL, // CALL addr
|
|
||||||
|
|
||||||
ADD, // ADD Vx, Vy
|
|
||||||
// ADD Vx, byte
|
|
||||||
// ADD I, Vx
|
|
||||||
|
|
||||||
SUB, // SUB Vx, Vy
|
|
||||||
// SUB Vx, byte
|
|
||||||
|
|
||||||
SUBN, // SUBN Vx, Vy
|
|
||||||
|
|
||||||
SE, // SE Vx, Vy
|
|
||||||
// SE Vx, byte
|
|
||||||
|
|
||||||
SNE, // SNE Vx, byte
|
|
||||||
SKP, // SKP Vx
|
|
||||||
SKNP, // SKNP Vx
|
|
||||||
|
|
||||||
SHL, // SHL Vx {, Vy}
|
|
||||||
SHR, // SHR Vx {, Vy}
|
|
||||||
XOR, // XOR Vx, Vy
|
|
||||||
AND, // AND Vx, Vy
|
|
||||||
OR, // OR Vx, Vy
|
|
||||||
|
|
||||||
LD, // LD Vx, DT
|
|
||||||
// LD DT, Vx
|
|
||||||
// LD ST, Vx
|
|
||||||
// LD Vx, K
|
|
||||||
// LD [I], Vx
|
|
||||||
// LD Vx, [I]
|
|
||||||
|
|
||||||
RND // RND Vx, byte
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Argument {
|
|
||||||
|
|
||||||
Vx,
|
|
||||||
Vy,
|
|
||||||
|
|
||||||
DT,
|
|
||||||
ST,
|
|
||||||
K,
|
|
||||||
|
|
||||||
addr,
|
|
||||||
val
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Instruction {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
import std.array;
|
|
||||||
|
|
||||||
Appender!string dissassembly_data;
|
|
||||||
|
|
||||||
void assemble(const char* input) {
|
|
||||||
|
|
||||||
} // assemble
|
|
||||||
|
|
||||||
void assemble(const char[] input) {
|
|
||||||
|
|
||||||
} // assemble
|
|
||||||
|
|
||||||
const (char)[] disassemble(ubyte[] instructions) {
|
|
||||||
|
|
||||||
import std.range : chunks;
|
|
||||||
|
|
||||||
foreach (ref ubyte[] i; instructions.chunks(2)) {
|
|
||||||
|
|
||||||
ushort opcode = (*(cast(ushort*)(i.ptr)));
|
|
||||||
|
|
||||||
switch (opcode & 0xF000) {
|
|
||||||
case 0x0000:
|
|
||||||
switch (opcode & 0x0FFF) {
|
|
||||||
case 0x00E0: // 0x00E0 Clears the screen.
|
|
||||||
|
|
||||||
case 0x00EE: // 0x00EE Returns from a subroutine.
|
|
||||||
default: // 0x0NNN Calls RCA 1802 program at address NNN. Not necessary for most ROMs.
|
|
||||||
//assert(0, "0x0NNN RCA 1802 program opcode not implemented!");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 0x1000: // 0x1NNN Jumps to address NNN.
|
|
||||||
case 0x2000: // 0x2NNN Calls subroutine at NNN.
|
|
||||||
case 0x3000: // 0x3XNN Skips the next instruction if VX equals NN.
|
|
||||||
case 0x4000: // 0x4XNN Skips the next instruction if VX doesn't equal NN.
|
|
||||||
case 0x5000: // 0x5XYO Skips the next instruction if VX equals VY.
|
|
||||||
case 0x6000: // 0x6XNN Sets VX to NN.
|
|
||||||
case 0x7000: // 0x7XNN Adds NN to VX.
|
|
||||||
case 0x8000:
|
|
||||||
switch (opcode) {
|
|
||||||
case 0x0000: // 0x8XY0 Sets VX to the value of VY.
|
|
||||||
case 0x0001: // 0x8XY1 Sets VX to VX or VY.
|
|
||||||
case 0x0002: // 0x8XY2 Sets VX to VX and VY.
|
|
||||||
case 0x0003: // 0x8XY3 Sets VX to VX xor VY.
|
|
||||||
case 0x0004: // 0x8XY4 Adds VY to VX. VF is set to 1 when there's a carry, and to 0 when there isn't.
|
|
||||||
case 0x0005: // 0x8XY5 VY is subtracted from VX. VF is set to 0 when there's a borrow, and 1 when there isn't.
|
|
||||||
case 0x0006: // 0x8XY6 Shifts VX right by one. VF is set to the value of the least significant bit of VX before the shift.
|
|
||||||
case 0x0007: // 0x8XY7 Sets VX to VY minus VX. VF is set to 0 when there's a borrow, and 1 when there isn't.
|
|
||||||
case 0x000E: // 0x8XYE Shifts VX left by one. VF is set to the value of the most significant bit of VX before the shift.
|
|
||||||
default: // unhandled for some reason
|
|
||||||
writefln("unknown opcode: 0x%x", opcode);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 0x9000: // 0x9XYO Skips the next instruction if VX doesn't equal VY.
|
|
||||||
case 0xA000: // 0xANNN Sets I to the address NNN.
|
|
||||||
case 0xB000: // 0xBNNN Jumps to the address NNN plus V0.
|
|
||||||
case 0xC000: // 0xCXNN Sets VX to the result of a bitwise and operation on a random number and NN.
|
|
||||||
|
|
||||||
// 0xDXYN
|
|
||||||
// Sprites stored in memory at location in index register (I), 8bits wide.
|
|
||||||
// Wraps around the screen. If when drawn, clears a pixel, register VF is set to 1 otherwise it is zero.
|
|
||||||
// All drawing is XOR drawing (i.e. it toggles the screen pixels).
|
|
||||||
// Sprites are drawn starting at position VX, VY. N is the number of 8bit rows that need to be drawn.
|
|
||||||
// If N is greater than 1, second line continues at position VX, VY+1, and so on.
|
|
||||||
case 0xD000:
|
|
||||||
case 0xE000:
|
|
||||||
switch (opcode & 0x000F) {
|
|
||||||
case 0x000E: // 0xEX9E Skips the next instruction if the key stored in VX is pressed.
|
|
||||||
case 0x0001: // 0xEXA1 Skips the next instruction if the key stored in VX isn't pressed.
|
|
||||||
default: // unhandled for some reason
|
|
||||||
writefln("unknown opcode: 0x%x", opcode);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 0xF000:
|
|
||||||
switch (opcode & 0x00FF) {
|
|
||||||
case 0x0007: // 0xFX07 Sets VX to the value of the delay timer.
|
|
||||||
case 0x000A: // 0xFX0A A key press is awaited, and then stored in VX.
|
|
||||||
case 0x0015: // 0xFX15 Sets the delay timer to VX.
|
|
||||||
case 0x0018: // 0xFX18 Sets the sound timer to VX.
|
|
||||||
case 0x001E: // 0xFX1E Adds VX to I.
|
|
||||||
case 0x0029: // 0xFX29 Sets I to the location of the sprite for the character in VX.
|
|
||||||
// 0xFX33 Stores the Binary-coded decimal representation of VX,
|
|
||||||
// with the most significant of three digits at the address in I,
|
|
||||||
// the middle digit at I plus 1, and the least significant digit at I plus 2.
|
|
||||||
case 0x0033:
|
|
||||||
case 0x0055: // 0xFX55 Stores V0 to VX in memory starting at address I.
|
|
||||||
case 0x0065: // 0xFX65 Fills V0 to VX with values from memory starting at address I.
|
|
||||||
default: // unhandled for some reason
|
|
||||||
writefln("unknown opcode: 0x%x", opcode);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
writefln("unknown opcode: 0x%x", opcode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "";
|
|
||||||
|
|
||||||
} // dissassemble
|
|
||||||
|
|
||||||
} // Assembler
|
|
||||||
|
|
||||||
struct Chip8Status {
|
|
||||||
|
|
||||||
// emu ptr
|
// emu ptr
|
||||||
Emulator* run_;
|
Emulator* run_;
|
||||||
Chip8* emu_;
|
Chip8* emu_;
|
||||||
|
|
||||||
|
// assembly/disassembly
|
||||||
|
Assembly ass_;
|
||||||
|
|
||||||
// loaded program
|
// loaded program
|
||||||
const (char)* loaded_program;
|
const (char)* loaded_program;
|
||||||
|
|
||||||
|
@ -568,6 +226,7 @@ struct Chip8Status {
|
||||||
void initialize(Emulator* run, Chip8* emu) {
|
void initialize(Emulator* run, Chip8* emu) {
|
||||||
this.run_ = run;
|
this.run_ = run;
|
||||||
this.emu_ = emu;
|
this.emu_ = emu;
|
||||||
|
ass_.initialize(emu);
|
||||||
} // initialize
|
} // initialize
|
||||||
|
|
||||||
alias Callback = float delegate(int idx, const char** out_text);
|
alias Callback = float delegate(int idx, const char** out_text);
|
||||||
|
@ -598,6 +257,7 @@ struct Chip8Status {
|
||||||
loaded_program = null;
|
loaded_program = null;
|
||||||
|
|
||||||
emu_.reset();
|
emu_.reset();
|
||||||
|
ass_.reset();
|
||||||
|
|
||||||
} // resetShortcut
|
} // resetShortcut
|
||||||
|
|
||||||
|
@ -608,6 +268,8 @@ struct Chip8Status {
|
||||||
loaded_program = "chip8_picture.ch8";
|
loaded_program = "chip8_picture.ch8";
|
||||||
auto buf = read("programs/chip8_picture.ch8");
|
auto buf = read("programs/chip8_picture.ch8");
|
||||||
emu_.load(0x200, buf); // do ze load yes, will copy all the data in
|
emu_.load(0x200, buf); // do ze load yes, will copy all the data in
|
||||||
|
ass_.setRange(cast(ushort)0x200, cast(ushort)(0x200 + cast(ushort)buf.length));
|
||||||
|
ass_.reset();
|
||||||
|
|
||||||
} // loadShortcut
|
} // loadShortcut
|
||||||
|
|
||||||
|
@ -637,6 +299,8 @@ struct Chip8Status {
|
||||||
|
|
||||||
void draw() {
|
void draw() {
|
||||||
|
|
||||||
|
ass_.draw();
|
||||||
|
|
||||||
if (!status_menu_) return;
|
if (!status_menu_) return;
|
||||||
|
|
||||||
if (igBeginMainMenuBar()) {
|
if (igBeginMainMenuBar()) {
|
||||||
|
@ -768,29 +432,48 @@ struct Chip8Status {
|
||||||
|
|
||||||
pure
|
pure
|
||||||
@property {
|
@property {
|
||||||
ubyte x(ref ushort oc) {
|
ubyte x(in ushort oc) {
|
||||||
return (oc & 0x0F00) >> 8;
|
return (oc & 0x0F00) >> 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
ubyte y(ref ushort oc) {
|
ubyte y(in ushort oc) {
|
||||||
return (oc & 0x00F0) >> 4;
|
return (oc & 0x00F0) >> 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
ubyte n(ref ushort oc) {
|
ubyte n(in ushort oc) {
|
||||||
return oc & 0x000F;
|
return oc & 0x000F;
|
||||||
}
|
}
|
||||||
|
|
||||||
ubyte nn(ref ushort oc) {
|
ubyte nn(in ushort oc) {
|
||||||
return (oc & 0x00FF);
|
return (oc & 0x00FF);
|
||||||
}
|
}
|
||||||
|
|
||||||
ushort nnn(ref ushort oc) {
|
ushort nnn(in ushort oc) {
|
||||||
return (oc & 0x0FFF);
|
return (oc & 0x0FFF);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Chip8 {
|
struct Chip8 {
|
||||||
|
|
||||||
|
ubyte[80] chip8_fontset = [
|
||||||
|
0xF0, 0x90, 0x90, 0x90, 0xF0, // 0
|
||||||
|
0x20, 0x60, 0x20, 0x20, 0x70, // 1
|
||||||
|
0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2
|
||||||
|
0xF0, 0x10, 0xF0, 0x10, 0xF0, // 3
|
||||||
|
0x90, 0x90, 0xF0, 0x10, 0x10, // 4
|
||||||
|
0xF0, 0x80, 0xF0, 0x10, 0xF0, // 5
|
||||||
|
0xF0, 0x80, 0xF0, 0x90, 0xF0, // 6
|
||||||
|
0xF0, 0x10, 0x20, 0x40, 0x40, // 7
|
||||||
|
0xF0, 0x90, 0xF0, 0x90, 0xF0, // 8
|
||||||
|
0xF0, 0x90, 0xF0, 0x10, 0xF0, // 9
|
||||||
|
0xF0, 0x90, 0xF0, 0x90, 0x90, // A
|
||||||
|
0xE0, 0x90, 0xE0, 0x90, 0xE0, // B
|
||||||
|
0xF0, 0x80, 0x80, 0x80, 0xF0, // C
|
||||||
|
0xE0, 0x90, 0x90, 0x90, 0xE0, // D
|
||||||
|
0xF0, 0x80, 0xF0, 0x80, 0xF0, // E
|
||||||
|
0xF0, 0x80, 0xF0, 0x80, 0x80 // F
|
||||||
|
];
|
||||||
|
|
||||||
alias OpCode = ushort;
|
alias OpCode = ushort;
|
||||||
alias ProgramCounter = ushort;
|
alias ProgramCounter = ushort;
|
||||||
alias Memory = ubyte[4096];
|
alias Memory = ubyte[4096];
|
||||||
|
@ -824,7 +507,6 @@ struct Chip8 {
|
||||||
KeyPad kp;
|
KeyPad kp;
|
||||||
|
|
||||||
ubyte[64*32] screen_buf;
|
ubyte[64*32] screen_buf;
|
||||||
ubyte[3][64*32] screen_data;
|
|
||||||
|
|
||||||
bool run_flag;
|
bool run_flag;
|
||||||
bool draw_flag;
|
bool draw_flag;
|
||||||
|
@ -836,8 +518,18 @@ struct Chip8 {
|
||||||
|
|
||||||
ram[offset .. offset + data.length] = cast(ubyte[])data[];
|
ram[offset .. offset + data.length] = cast(ubyte[])data[];
|
||||||
|
|
||||||
|
loadFont(); // again
|
||||||
|
|
||||||
} // load
|
} // load
|
||||||
|
|
||||||
|
void loadFont() {
|
||||||
|
|
||||||
|
foreach (i, b; chip8_fontset) {
|
||||||
|
ram[i] = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // loadFont
|
||||||
|
|
||||||
void reset() {
|
void reset() {
|
||||||
|
|
||||||
reset_flag = true;
|
reset_flag = true;
|
||||||
|
@ -850,9 +542,7 @@ struct Chip8 {
|
||||||
|
|
||||||
run_flag = run_flag.init;
|
run_flag = run_flag.init;
|
||||||
draw_flag = draw_flag.init;
|
draw_flag = draw_flag.init;
|
||||||
|
|
||||||
screen_buf = screen_buf.init;
|
screen_buf = screen_buf.init;
|
||||||
screen_data = screen_data.init;
|
|
||||||
|
|
||||||
} // reset
|
} // reset
|
||||||
|
|
||||||
|
@ -936,8 +626,8 @@ struct Chip8 {
|
||||||
|
|
||||||
case 0x8000:
|
case 0x8000:
|
||||||
|
|
||||||
ubyte x = cpu.opcode.x;
|
immutable ubyte x = cpu.opcode.x;
|
||||||
ubyte y = cpu.opcode.y;
|
immutable ubyte y = cpu.opcode.y;
|
||||||
|
|
||||||
switch (cpu.opcode.n) {
|
switch (cpu.opcode.n) {
|
||||||
|
|
||||||
|
@ -958,8 +648,8 @@ struct Chip8 {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x0004: // 0x8XY4 Adds VY to VX. VF is set to 1 when there's a carry, and to 0 when there isn't.
|
case 0x0004: // 0x8XY4 Adds VY to VX. VF is set to 1 when there's a carry, and to 0 when there isn't.
|
||||||
ubyte vx = cpu.v[x];
|
immutable ubyte vx = cpu.v[x];
|
||||||
ubyte vy = cpu.v[y];
|
immutable ubyte vy = cpu.v[y];
|
||||||
if (cast(ushort)vx + cast(ushort)vy > 255) {
|
if (cast(ushort)vx + cast(ushort)vy > 255) {
|
||||||
cpu.v[0xF] = 1;
|
cpu.v[0xF] = 1;
|
||||||
} else {
|
} else {
|
||||||
|
@ -969,8 +659,8 @@ struct Chip8 {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x0005: // 0x8XY5 VY is subtracted from VX. VF is set to 0 when there's a borrow, and 1 when there isn't.
|
case 0x0005: // 0x8XY5 VY is subtracted from VX. VF is set to 0 when there's a borrow, and 1 when there isn't.
|
||||||
ubyte vx = cpu.v[x];
|
immutable ubyte vx = cpu.v[x];
|
||||||
ubyte vy = cpu.v[y];
|
immutable ubyte vy = cpu.v[y];
|
||||||
if (vx > vy) {
|
if (vx > vy) {
|
||||||
cpu.v[0xF] = 1;
|
cpu.v[0xF] = 1;
|
||||||
} else {
|
} else {
|
||||||
|
@ -981,14 +671,14 @@ struct Chip8 {
|
||||||
|
|
||||||
case 0x0006: // 0x8XY6 Shifts VX right by one. VF is set to the value of the least significant bit of VX before the shift.
|
case 0x0006: // 0x8XY6 Shifts VX right by one. VF is set to the value of the least significant bit of VX before the shift.
|
||||||
|
|
||||||
ubyte vx = cpu.v[x];
|
immutable ubyte vx = cpu.v[x];
|
||||||
cpu.v[0xF] = vx & 0x1;
|
cpu.v[0xF] = vx & 0x1;
|
||||||
cpu.v[x] >>= 1;
|
cpu.v[x] >>= 1;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x0007: // 0x8XY7 Sets VX to VY minus VX. VF is set to 0 when there's a borrow, and 1 when there isn't.
|
case 0x0007: // 0x8XY7 Sets VX to VY minus VX. VF is set to 0 when there's a borrow, and 1 when there isn't.
|
||||||
ubyte vx = cpu.v[x];
|
immutable ubyte vx = cpu.v[x];
|
||||||
ubyte vy = cpu.v[y];
|
immutable ubyte vy = cpu.v[y];
|
||||||
if (vy > vx) {
|
if (vy > vx) {
|
||||||
cpu.v[0xF] = 1;
|
cpu.v[0xF] = 1;
|
||||||
} else {
|
} else {
|
||||||
|
@ -999,7 +689,7 @@ struct Chip8 {
|
||||||
|
|
||||||
case 0x000E: // 0x8XYE Shifts VX left by one. VF is set to the value of the most significant bit of VX before the shift.
|
case 0x000E: // 0x8XYE Shifts VX left by one. VF is set to the value of the most significant bit of VX before the shift.
|
||||||
|
|
||||||
ubyte vx = cpu.v[x];
|
immutable ubyte vx = cpu.v[x];
|
||||||
cpu.v[0xF] = vx >> 7;
|
cpu.v[0xF] = vx >> 7;
|
||||||
cpu.v[x] <<= 1;
|
cpu.v[x] <<= 1;
|
||||||
break;
|
break;
|
||||||
|
@ -1032,7 +722,7 @@ struct Chip8 {
|
||||||
|
|
||||||
import std.random : uniform;
|
import std.random : uniform;
|
||||||
ubyte x = cpu.opcode.x;
|
ubyte x = cpu.opcode.x;
|
||||||
cpu.v[x] = uniform(Register.min, Register.max) & (cpu.opcode & 0x00FF);
|
cpu.v[x] = uniform(Register.min, Register.max) & x;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// 0xDXYN
|
// 0xDXYN
|
||||||
|
@ -1043,14 +733,14 @@ struct Chip8 {
|
||||||
// If N is greater than 1, second line continues at position VX, VY+1, and so on.
|
// If N is greater than 1, second line continues at position VX, VY+1, and so on.
|
||||||
case 0xD000:
|
case 0xD000:
|
||||||
|
|
||||||
ProgramCounter spr_addr = cpu.i;
|
immutable ProgramCounter spr_addr = cpu.i;
|
||||||
ubyte x = cpu.opcode.x;
|
immutable ubyte x = cpu.opcode.x;
|
||||||
ubyte y = cpu.opcode.y;
|
immutable ubyte y = cpu.opcode.y;
|
||||||
ubyte n = cpu.opcode.n;
|
immutable ubyte n = cpu.opcode.n;
|
||||||
|
|
||||||
foreach(int row; 0 .. n) {
|
foreach(int row; 0 .. n) {
|
||||||
|
|
||||||
ushort pixel = ram[spr_addr + row];
|
immutable ushort pixel = ram[spr_addr + row];
|
||||||
|
|
||||||
foreach (int col; 0 .. 8) {
|
foreach (int col; 0 .. 8) {
|
||||||
if ((pixel & (0x80 >> col)) != 0) {
|
if ((pixel & (0x80 >> col)) != 0) {
|
||||||
|
@ -1083,6 +773,7 @@ struct Chip8 {
|
||||||
|
|
||||||
case 0x0001: // 0xEXA1 Skips the next instruction if the key stored in VX isn't pressed.
|
case 0x0001: // 0xEXA1 Skips the next instruction if the key stored in VX isn't pressed.
|
||||||
writefln("0xEXA1: skip instruction if VX not pressed: %x", key);
|
writefln("0xEXA1: skip instruction if VX not pressed: %x", key);
|
||||||
|
pc_target += 2;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default: //unhandled for some reason
|
default: //unhandled for some reason
|
||||||
|
@ -1124,9 +815,9 @@ struct Chip8 {
|
||||||
|
|
||||||
case 0x0029: // 0xFX29 Sets I to the location of the sprite for the character in VX.
|
case 0x0029: // 0xFX29 Sets I to the location of the sprite for the character in VX.
|
||||||
|
|
||||||
ubyte vx = cpu.v[cpu.opcode.x];
|
immutable ubyte vx = cpu.v[cpu.opcode.x];
|
||||||
// ushort char_addr = 0x200 + (vx * 40); // base of char sprites + value of vx * bits per character
|
// immutable ushort char_addr = 0x200 + (vx * 40); // base of char sprites + value of vx * bits per character
|
||||||
ushort char_addr = vx * 0x5;
|
immutable ushort char_addr = vx * 0x05;
|
||||||
cpu.i = char_addr;
|
cpu.i = char_addr;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -1135,7 +826,7 @@ struct Chip8 {
|
||||||
// the middle digit at I plus 1, and the least significant digit at I plus 2.
|
// the middle digit at I plus 1, and the least significant digit at I plus 2.
|
||||||
case 0x0033:
|
case 0x0033:
|
||||||
|
|
||||||
ubyte vx = cpu.v[cpu.opcode.x];
|
immutable ubyte vx = cpu.v[cpu.opcode.x];
|
||||||
ram[cpu.i] = vx / 100;
|
ram[cpu.i] = vx / 100;
|
||||||
ram[cpu.i + 1] = (vx / 10) % 10;
|
ram[cpu.i + 1] = (vx / 10) % 10;
|
||||||
ram[cpu.i + 2] = (vx % 100) % 10;
|
ram[cpu.i + 2] = (vx % 100) % 10;
|
||||||
|
@ -1320,8 +1011,8 @@ struct Emulator {
|
||||||
int w, h;
|
int w, h;
|
||||||
window.windowSize(w, h);
|
window.windowSize(w, h);
|
||||||
if (emu.draw_flag || emu.reset_flag) {
|
if (emu.draw_flag || emu.reset_flag) {
|
||||||
if (emu.reset_flag) emu.reset_flag = false;
|
|
||||||
buf.update(emu.screen_buf);
|
buf.update(emu.screen_buf);
|
||||||
|
emu.reset_flag = false;
|
||||||
emu.draw_flag = false;
|
emu.draw_flag = false;
|
||||||
}
|
}
|
||||||
buf.draw(w, h);
|
buf.draw(w, h);
|
||||||
|
|
|
@ -0,0 +1,424 @@
|
||||||
|
module assembler;
|
||||||
|
|
||||||
|
import std.stdio : writefln;
|
||||||
|
|
||||||
|
struct Assembler {
|
||||||
|
|
||||||
|
/*
|
||||||
|
Instruction Glossary:
|
||||||
|
0x0nnn - SYS addr
|
||||||
|
0x00E0 - CLS
|
||||||
|
0x00EE - RET
|
||||||
|
0x1nnn - JP addr
|
||||||
|
0x2nnn - CALL addr
|
||||||
|
0x3xkk - SE Vx, byte
|
||||||
|
0x4xkk - SNE Vx, byte
|
||||||
|
0x5xy0 - SE Vx, Vy
|
||||||
|
0x6xkk - LD Vx, byte
|
||||||
|
0x7xkk - ADD Vx, byte
|
||||||
|
0x8xy0 - LD Vx, Vy
|
||||||
|
0x8xy1 - OR Vx, Vy
|
||||||
|
0x8xy2 - AND Vx, Vy
|
||||||
|
0x8xy3 - XOR Vx, Vy
|
||||||
|
0x8xy4 - ADD Vx, Vy
|
||||||
|
0x8xy5 - SUB Vx, Vy
|
||||||
|
0x8xy6 - SHR Vx {, Vy}
|
||||||
|
0x8xy7 - SUBN Vx, Vy
|
||||||
|
0x8xyE - SHL Vx {, Vy}
|
||||||
|
0x9xy0 - SNE Vx, Vy
|
||||||
|
0xAnnn - LD I, addr
|
||||||
|
0xBnnn - JP V0, addr
|
||||||
|
0xCxkk - RND Vx, byte
|
||||||
|
0xDxyn - DRW Vx, Vy, nibble
|
||||||
|
0xEx9E - SKP Vx
|
||||||
|
0xExA1 - SKNP Vx
|
||||||
|
0xFx07 - LD Vx, DT
|
||||||
|
0xFx0A - LD Vx, K
|
||||||
|
0xFx15 - LD DT, Vx
|
||||||
|
0xFx18 - LD ST, Vx
|
||||||
|
0xFx1E - ADD I, Vx
|
||||||
|
0xFx29 - LD F, Vx
|
||||||
|
0xFx33 - LD B, Vx
|
||||||
|
0xFx55 - LD [I], Vx
|
||||||
|
0xFx65 - LD Vx, [I]
|
||||||
|
*/
|
||||||
|
|
||||||
|
enum OpCode : const (char)* {
|
||||||
|
|
||||||
|
UNK = "UNK", // UNKNOWN
|
||||||
|
|
||||||
|
CLS = "CLS", // CLS
|
||||||
|
RET = "RET", // RET
|
||||||
|
CALL = "CALL", // CALL addr
|
||||||
|
|
||||||
|
ADD = "ADD", // ADD Vx, Vy
|
||||||
|
// ADD Vx, byte
|
||||||
|
// ADD I, Vx
|
||||||
|
|
||||||
|
SUB = "SUB", // SUB Vx, Vy
|
||||||
|
// SUB Vx, byte
|
||||||
|
|
||||||
|
SUBN = "SUBN", // SUBN Vx, Vy
|
||||||
|
|
||||||
|
SE = "SE", // SE Vx, Vy
|
||||||
|
// SE Vx, byte
|
||||||
|
|
||||||
|
SNE = "SNE", // SNE Vx, byte
|
||||||
|
SKP = "SKP", // SKP Vx
|
||||||
|
SKNP = "SKNP", // SKNP Vx
|
||||||
|
|
||||||
|
SHL = "SHL", // SHL Vx {, Vy}
|
||||||
|
SHR = "SHR", // SHR Vx {, Vy}
|
||||||
|
XOR = "XOR", // XOR Vx, Vy
|
||||||
|
AND = "AND", // AND Vx, Vy
|
||||||
|
OR = "OR", // OR Vx, Vy
|
||||||
|
|
||||||
|
LD = "LD", // LD Vx, DT
|
||||||
|
// LD DT, Vx
|
||||||
|
// LD ST, Vx
|
||||||
|
// LD Vx, K
|
||||||
|
// LD [I], Vx
|
||||||
|
// LD Vx, [I]
|
||||||
|
|
||||||
|
RND = "RND", // RND Vx, byte,
|
||||||
|
|
||||||
|
JP = "JP", // JP addr
|
||||||
|
// JP V0, addr
|
||||||
|
|
||||||
|
DRW = "DRW", // DRW Vx, Vy, nibble
|
||||||
|
|
||||||
|
SYS = "SYS" // SYS addr | probably unused? but still
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Argument {
|
||||||
|
|
||||||
|
Nil,
|
||||||
|
|
||||||
|
Vx,
|
||||||
|
Vy,
|
||||||
|
V0,
|
||||||
|
|
||||||
|
I,
|
||||||
|
I_addr,
|
||||||
|
DT,
|
||||||
|
ST,
|
||||||
|
K,
|
||||||
|
B,
|
||||||
|
F,
|
||||||
|
|
||||||
|
beit,
|
||||||
|
nibble,
|
||||||
|
addr,
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Instruction {
|
||||||
|
|
||||||
|
@property {
|
||||||
|
ubyte vx() {
|
||||||
|
return (v_ & 0x0F00) >> 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
ubyte vy() {
|
||||||
|
return (v_ & 0x00F0) >> 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
ubyte n() {
|
||||||
|
return (v_ & 0x000F);
|
||||||
|
}
|
||||||
|
|
||||||
|
ubyte nn() {
|
||||||
|
return (v_ & 0x00FF);
|
||||||
|
}
|
||||||
|
|
||||||
|
ushort nnn() {
|
||||||
|
return (v_ & 0x0FFF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private {
|
||||||
|
|
||||||
|
ushort v_; // raw value
|
||||||
|
|
||||||
|
OpCode op_;
|
||||||
|
Argument a1_;
|
||||||
|
Argument a2_;
|
||||||
|
Argument a3_;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void* extract(Argument a) {
|
||||||
|
final switch (a) with (Argument) {
|
||||||
|
|
||||||
|
case Vx: return cast(void*)(vx());
|
||||||
|
case Vy: return cast(void*)(vy());
|
||||||
|
case V0: return cast(void*)(0);
|
||||||
|
|
||||||
|
case I: return cast(void*)("I".ptr);
|
||||||
|
case I_addr: return cast(void*)("[I]".ptr);
|
||||||
|
|
||||||
|
case DT: return cast(void*)("DT".ptr);
|
||||||
|
case ST: return cast(void*)("ST".ptr);
|
||||||
|
|
||||||
|
case B: return cast(void*)("B".ptr);
|
||||||
|
case F: return cast(void*)("F".ptr);
|
||||||
|
case K: return cast(void*)("K".ptr);
|
||||||
|
|
||||||
|
case nibble: return cast(void*)(n());
|
||||||
|
case beit: return cast(void*)(nn());
|
||||||
|
case addr: return cast(void*)(nnn());
|
||||||
|
|
||||||
|
case Nil: return cast(void*)(0);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@property
|
||||||
|
OpCode op() {
|
||||||
|
return op_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* a1(out Argument a) {
|
||||||
|
a = a1_;
|
||||||
|
return extract(a1_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void* a2(out Argument a) {
|
||||||
|
a = a2_;
|
||||||
|
return extract(a2_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void* a3(out Argument a) {
|
||||||
|
a = a3_;
|
||||||
|
return extract(a3_);
|
||||||
|
}
|
||||||
|
|
||||||
|
@property int args() {
|
||||||
|
int args = 0;
|
||||||
|
args += (a1_ != Argument.Nil);
|
||||||
|
args += (a2_ != Argument.Nil);
|
||||||
|
args += (a3_ != Argument.Nil);
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
this(ushort v, OpCode op, Argument a) {
|
||||||
|
v_ = v;
|
||||||
|
op_ = op;
|
||||||
|
a1_ = a;
|
||||||
|
}
|
||||||
|
|
||||||
|
this(ushort v, OpCode op, Argument a1, Argument a2) {
|
||||||
|
v_ = v;
|
||||||
|
op_ = op;
|
||||||
|
a1_ = a1;
|
||||||
|
a2_ = a2;
|
||||||
|
}
|
||||||
|
|
||||||
|
this(ushort v, OpCode op, Argument a1, Argument a2, Argument a3) {
|
||||||
|
v_ = v;
|
||||||
|
op_ = op;
|
||||||
|
a1_ = a1;
|
||||||
|
a2_ = a2;
|
||||||
|
a3_ = a3;
|
||||||
|
}
|
||||||
|
|
||||||
|
this(ushort v, OpCode op) {
|
||||||
|
v_ = v;
|
||||||
|
op_ = op;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
import std.array;
|
||||||
|
|
||||||
|
private {
|
||||||
|
|
||||||
|
Appender!(ubyte[]) assembled_data;
|
||||||
|
Appender!string dissassembled_data;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@disable this(this);
|
||||||
|
|
||||||
|
ubyte[] assemble(const char* input) {
|
||||||
|
|
||||||
|
return [];
|
||||||
|
|
||||||
|
} // assemble
|
||||||
|
|
||||||
|
ubyte[] assemble(const char[] input) {
|
||||||
|
|
||||||
|
return [];
|
||||||
|
|
||||||
|
} // assemble
|
||||||
|
|
||||||
|
static Instruction[] disassemble(ubyte[] instructions) {
|
||||||
|
|
||||||
|
import std.range : chunks;
|
||||||
|
|
||||||
|
auto instr_output = Appender!(Instruction[])();
|
||||||
|
|
||||||
|
foreach (ref ubyte[] i; instructions.chunks(2)) {
|
||||||
|
|
||||||
|
if (i.length != 2) break;
|
||||||
|
|
||||||
|
ushort opcode = i[0] << 8 | i[1];
|
||||||
|
|
||||||
|
switch (opcode & 0xF000) {
|
||||||
|
case 0x0000:
|
||||||
|
switch (opcode & 0x0FFF) {
|
||||||
|
case 0x00E0: // 0x00E0 Clears the screen. | CLS
|
||||||
|
instr_output ~= Instruction(opcode, OpCode.CLS);
|
||||||
|
break;
|
||||||
|
case 0x00EE: // 0x00EE Returns from a subroutine. | RET
|
||||||
|
instr_output ~= Instruction(opcode, OpCode.RET);
|
||||||
|
break;
|
||||||
|
default: // 0x0NNN Calls RCA 1802 program at address NNN. Not necessary for most ROMs. | SYS addr
|
||||||
|
//assert(0, "0x0NNN RCA 1802 program opcode not implemented!");
|
||||||
|
// instr_output ~= Instruction(opcode, OpCode.SYS, Argument.addr);
|
||||||
|
instr_output ~= Instruction(opcode, OpCode.UNK);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 0x1000: // 0x1NNN Jumps to address NNN. | JP addr
|
||||||
|
instr_output ~= Instruction(opcode, OpCode.JP, Argument.addr);
|
||||||
|
break;
|
||||||
|
case 0x2000: // 0x2NNN Calls subroutine at NNN. | CALL addr
|
||||||
|
instr_output ~= Instruction(opcode, OpCode.CALL, Argument.addr);
|
||||||
|
break;
|
||||||
|
case 0x3000: // 0x3XNN Skips the next instruction if VX equals NN. | SE Vx, byte
|
||||||
|
instr_output ~= Instruction(opcode, OpCode.SE, Argument.Vx, Argument.beit);
|
||||||
|
break;
|
||||||
|
case 0x4000: // 0x4XNN Skips the next instruction if VX doesn't equal NN. | SNE Vx, byte
|
||||||
|
instr_output ~= Instruction(opcode, OpCode.SNE, Argument.Vx, Argument.beit);
|
||||||
|
break;
|
||||||
|
case 0x5000: // 0x5XYO Skips the next instruction if VX equals VY. | SE Vx, Vy
|
||||||
|
instr_output ~= Instruction(opcode, OpCode.SE, Argument.Vx, Argument.Vy);
|
||||||
|
break;
|
||||||
|
case 0x6000: // 0x6XNN Sets VX to NN. | LD Vx, byte
|
||||||
|
instr_output ~= Instruction(opcode, OpCode.LD, Argument.Vx, Argument.beit);
|
||||||
|
break;
|
||||||
|
case 0x7000: // 0x7XNN Adds NN to VX. | ADD Vx, byte
|
||||||
|
instr_output ~= Instruction(opcode, OpCode.ADD, Argument.Vx, Argument.beit);
|
||||||
|
break;
|
||||||
|
case 0x8000:
|
||||||
|
switch (opcode) {
|
||||||
|
case 0x0000: // 0x8XY0 Sets VX to the value of VY. | LD Vx, Vy
|
||||||
|
instr_output ~= Instruction(opcode, OpCode.LD, Argument.Vx, Argument.Vy);
|
||||||
|
break;
|
||||||
|
case 0x0001: // 0x8XY1 Sets VX to VX or VY. | OR Vx, Vy
|
||||||
|
instr_output ~= Instruction(opcode, OpCode.OR, Argument.Vx, Argument.Vy);
|
||||||
|
break;
|
||||||
|
case 0x0002: // 0x8XY2 Sets VX to VX and VY. | AND Vx, Vy
|
||||||
|
instr_output ~= Instruction(opcode, OpCode.AND, Argument.Vx, Argument.Vy);
|
||||||
|
break;
|
||||||
|
case 0x0003: // 0x8XY3 Sets VX to VX xor VY. | XOR Vx, Vy
|
||||||
|
instr_output ~= Instruction(opcode, OpCode.XOR, Argument.Vx, Argument.Vy);
|
||||||
|
break;
|
||||||
|
case 0x0004: // 0x8XY4 Adds VY to VX. VF is set to 1 when there's a carry, and to 0 when there isn't. | ADD Vx, Vy
|
||||||
|
instr_output ~= Instruction(opcode, OpCode.ADD, Argument.Vx, Argument.Vy);
|
||||||
|
break;
|
||||||
|
case 0x0005: // 0x8XY5 VY is subtracted from VX. VF is set to 0 when there's a borrow, and 1 when there isn't. | SUB Vx, Vy
|
||||||
|
instr_output ~= Instruction(opcode, OpCode.SUB, Argument.Vx, Argument.Vy);
|
||||||
|
break;
|
||||||
|
case 0x0006: // 0x8XY6 Shifts VX right by one. VF is set to the value of the least significant bit of VX before the shift. | SHR Vx {, Vy}
|
||||||
|
instr_output ~= Instruction(opcode, OpCode.SHR, Argument.Vx); // FIXME? what about Vy here actually?
|
||||||
|
break;
|
||||||
|
case 0x0007: // 0x8XY7 Sets VX to VY minus VX. VF is set to 0 when there's a borrow, and 1 when there isn't. | SUBN Vx, Vy
|
||||||
|
instr_output ~= Instruction(opcode, OpCode.SUBN, Argument.Vx, Argument.Vy);
|
||||||
|
break;
|
||||||
|
case 0x000E: // 0x8XYE Shifts VX left by one. VF is set to the value of the most significant bit of VX before the shift. | SHL Vx {, Vy}
|
||||||
|
instr_output ~= Instruction(opcode, OpCode.SHL, Argument.Vx); // FIXME? what about Vy here actually?
|
||||||
|
break;
|
||||||
|
default: // unhandled for some reason
|
||||||
|
instr_output ~= Instruction(opcode, OpCode.UNK);
|
||||||
|
writefln("unknown opcode: 0x%x", opcode);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 0x9000: // 0x9XYO Skips the next instruction if VX doesn't equal VY. | SNE Vx, Vy
|
||||||
|
instr_output ~= Instruction(opcode, OpCode.SNE, Argument.Vx, Argument.Vy);
|
||||||
|
break;
|
||||||
|
case 0xA000: // 0xANNN Sets I to the address NNN. | LD I, addr
|
||||||
|
instr_output ~= Instruction(opcode, OpCode.LD, Argument.I, Argument.addr);
|
||||||
|
break;
|
||||||
|
case 0xB000: // 0xBNNN Jumps to the address NNN plus V0. | JP V0, addr
|
||||||
|
instr_output ~= Instruction(opcode, OpCode.JP, Argument.V0, Argument.addr);
|
||||||
|
break;
|
||||||
|
case 0xC000: // 0xCXNN Sets VX to the result of a bitwise and operation on a random number and NN. | RND Vx, byte
|
||||||
|
instr_output ~= Instruction(opcode, OpCode.RND, Argument.Vx, Argument.beit);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// 0xDXYN | DRW Vx, Vy, nibble
|
||||||
|
// Sprites stored in memory at location in index register (I), 8bits wide.
|
||||||
|
// Wraps around the screen. If when drawn, clears a pixel, register VF is set to 1 otherwise it is zero.
|
||||||
|
// All drawing is XOR drawing (i.e. it toggles the screen pixels).
|
||||||
|
// Sprites are drawn starting at position VX, VY. N is the number of 8bit rows that need to be drawn.
|
||||||
|
// If N is greater than 1, second line continues at position VX, VY+1, and so on.
|
||||||
|
case 0xD000:
|
||||||
|
instr_output ~= Instruction(opcode, OpCode.DRW, Argument.Vx, Argument.Vy, Argument.beit);
|
||||||
|
break;
|
||||||
|
case 0xE000:
|
||||||
|
switch (opcode & 0x000F) {
|
||||||
|
case 0x000E: // 0xEX9E Skips the next instruction if the key stored in VX is pressed. | SKP Vx
|
||||||
|
instr_output ~= Instruction(opcode, OpCode.SKP, Argument.Vx);
|
||||||
|
break;
|
||||||
|
case 0x0001: // 0xEXA1 Skips the next instruction if the key stored in VX isn't pressed. | SKNP Vx
|
||||||
|
instr_output ~= Instruction(opcode, OpCode.SKNP, Argument.Vx);
|
||||||
|
break;
|
||||||
|
default: // unhandled for some reason
|
||||||
|
instr_output ~= Instruction(opcode, OpCode.UNK);
|
||||||
|
writefln("unknown opcode: 0x%x", opcode);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 0xF000:
|
||||||
|
switch (opcode & 0x00FF) {
|
||||||
|
case 0x0007: // 0xFX07 Sets VX to the value of the delay timer. | LD Vx, DT
|
||||||
|
instr_output ~= Instruction(opcode, OpCode.LD, Argument.Vx, Argument.DT);
|
||||||
|
break;
|
||||||
|
case 0x000A: // 0xFX0A A key press is awaited, and then stored in VX. | LD Vx, K
|
||||||
|
instr_output ~= Instruction(opcode, OpCode.LD, Argument.Vx, Argument.K);
|
||||||
|
break;
|
||||||
|
case 0x0015: // 0xFX15 Sets the delay timer to VX. | LD DT, Vx
|
||||||
|
instr_output ~= Instruction(opcode, OpCode.LD, Argument.DT, Argument.Vx);
|
||||||
|
break;
|
||||||
|
case 0x0018: // 0xFX18 Sets the sound timer to VX. | LD ST, Vx
|
||||||
|
instr_output ~= Instruction(opcode, OpCode.LD, Argument.ST, Argument.Vx);
|
||||||
|
break;
|
||||||
|
case 0x001E: // 0xFX1E Adds VX to I. | ADD I, Vx
|
||||||
|
instr_output ~= Instruction(opcode, OpCode.ADD, Argument.I, Argument.DT);
|
||||||
|
break;
|
||||||
|
case 0x0029: // 0xFX29 Sets I to the location of the sprite for the character in VX. | LD F, Vx
|
||||||
|
instr_output ~= Instruction(opcode, OpCode.LD, Argument.F, Argument.DT);
|
||||||
|
break;
|
||||||
|
// 0xFX33 Stores the Binary-coded decimal representation of VX,
|
||||||
|
// with the most significant of three digits at the address in I,
|
||||||
|
// the middle digit at I plus 1, and the least significant digit at I plus 2.
|
||||||
|
case 0x0033: // 0xFX33 ??? FIXME? | LD B, Vx
|
||||||
|
instr_output ~= Instruction(opcode, OpCode.LD, Argument.B, Argument.Vx);
|
||||||
|
break;
|
||||||
|
case 0x0055: // 0xFX55 Stores V0 to VX in memory starting at address I. | LD [I], Vx
|
||||||
|
instr_output ~= Instruction(opcode, OpCode.LD, Argument.I_addr, Argument.DT);
|
||||||
|
break;
|
||||||
|
case 0x0065: // 0xFX65 Fills V0 to VX with values from memory starting at address I. | LD Vx, [I]
|
||||||
|
instr_output ~= Instruction(opcode, OpCode.LD, Argument.Vx, Argument.I_addr);
|
||||||
|
break;
|
||||||
|
default: // unhandled for some reason
|
||||||
|
instr_output ~= Instruction(opcode, OpCode.UNK);
|
||||||
|
writefln("unknown opcode: 0x%x", opcode);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
instr_output ~= Instruction(opcode, OpCode.UNK);
|
||||||
|
writefln("unknown opcode: 0x%x", opcode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return instr_output.data;
|
||||||
|
|
||||||
|
} // dissassemble
|
||||||
|
|
||||||
|
} // Assembler
|
|
@ -0,0 +1,344 @@
|
||||||
|
module gl;
|
||||||
|
|
||||||
|
import glad.gl.all;
|
||||||
|
import core.stdc.stdio;
|
||||||
|
|
||||||
|
bool checkShaderError(GLuint shader, GLuint flag, bool is_program, in char[] shader_path) nothrow {
|
||||||
|
|
||||||
|
GLint result;
|
||||||
|
|
||||||
|
(is_program) ? glGetProgramiv(shader, flag, &result)
|
||||||
|
: glGetShaderiv(shader, flag, &result);
|
||||||
|
|
||||||
|
if (result == GL_FALSE) {
|
||||||
|
|
||||||
|
GLchar[256] log; //FIXME this is potentially fatal
|
||||||
|
(is_program) ? glGetProgramInfoLog(shader, log.sizeof, null, log.ptr)
|
||||||
|
: glGetShaderInfoLog(shader, log.sizeof, null, log.ptr);
|
||||||
|
|
||||||
|
printf("[OpenGL] Error in %s: %s\n", shader_path.ptr, log.ptr);
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} // checkShaderError
|
||||||
|
|
||||||
|
struct PixelBuffer {
|
||||||
|
|
||||||
|
struct Shader {
|
||||||
|
|
||||||
|
private {
|
||||||
|
GLuint shader_prog;
|
||||||
|
}
|
||||||
|
|
||||||
|
@disable this(this);
|
||||||
|
|
||||||
|
void compile(const (GLchar*)* vs_source, const (GLchar*)* fs_source) {
|
||||||
|
|
||||||
|
import core.stdc.stdlib : exit;
|
||||||
|
|
||||||
|
GLuint new_vs_shader = glCreateShader(GL_VERTEX_SHADER);
|
||||||
|
glShaderSource(new_vs_shader, 1, vs_source, null);
|
||||||
|
glCompileShader(new_vs_shader);
|
||||||
|
if (!checkShaderError(new_vs_shader, GL_COMPILE_STATUS, false, "vertex_shader")) {
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
GLuint new_fs_shader = glCreateShader(GL_FRAGMENT_SHADER);
|
||||||
|
glShaderSource(new_fs_shader, 1, fs_source, null);
|
||||||
|
glCompileShader(new_fs_shader);
|
||||||
|
if (!checkShaderError(new_fs_shader, GL_COMPILE_STATUS, false, "fragment_shader")) {
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
GLuint new_shader = glCreateProgram();
|
||||||
|
glAttachShader(new_shader, new_vs_shader);
|
||||||
|
glAttachShader(new_shader, new_fs_shader);
|
||||||
|
|
||||||
|
// glBindAttribLocation(new_shader, 0, "screen_size");
|
||||||
|
|
||||||
|
glLinkProgram(new_shader);
|
||||||
|
if (!checkShaderError(new_shader, GL_LINK_STATUS, true, "shader_program")) {
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
glValidateProgram(new_shader);
|
||||||
|
if (!checkShaderError(new_shader, GL_VALIDATE_STATUS, true, "shader_program")) {
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
shader_prog = new_shader;
|
||||||
|
|
||||||
|
} // compile
|
||||||
|
|
||||||
|
void setUniforms(int w, int h) {
|
||||||
|
auto attr_loc = glGetUniformLocation(shader_prog, "screen_size");
|
||||||
|
glUniform2f(attr_loc, cast(float)w, cast(float)h);
|
||||||
|
} // setUniforms
|
||||||
|
|
||||||
|
void bind() {
|
||||||
|
glUseProgram(shader_prog);
|
||||||
|
} // bind
|
||||||
|
|
||||||
|
void unbind() {
|
||||||
|
glUseProgram(0);
|
||||||
|
} // unbind
|
||||||
|
|
||||||
|
} // Shader
|
||||||
|
|
||||||
|
struct Texture {
|
||||||
|
|
||||||
|
import derelict.sdl2.sdl;
|
||||||
|
import derelict.sdl2.image;
|
||||||
|
|
||||||
|
private {
|
||||||
|
|
||||||
|
GLuint texture_; //OpenGL handle for texture
|
||||||
|
GLenum input_format_, output_format_, data_type_;
|
||||||
|
int width_, height_;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@property @nogc nothrow {
|
||||||
|
|
||||||
|
int width() const { return width_; }
|
||||||
|
int height() const { return height_; }
|
||||||
|
GLuint handle() { return texture_; }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@disable this(this);
|
||||||
|
|
||||||
|
nothrow @nogc
|
||||||
|
this(int width, int height, GLenum input_format, GLenum output_format, GLenum unpack_alignment) {
|
||||||
|
|
||||||
|
width_ = width;
|
||||||
|
height_ = height;
|
||||||
|
input_format_ = input_format;
|
||||||
|
output_format_ = output_format;
|
||||||
|
data_type_ = GL_UNSIGNED_BYTE;
|
||||||
|
|
||||||
|
glGenTextures(1, &texture_);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, texture_);
|
||||||
|
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||||
|
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||||
|
|
||||||
|
glPixelStorei(GL_UNPACK_ALIGNMENT, unpack_alignment);
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, input_format_, width_, height_, 0, output_format_, GL_UNSIGNED_BYTE, cast(void*)0);
|
||||||
|
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
|
||||||
|
} //this
|
||||||
|
|
||||||
|
nothrow @nogc
|
||||||
|
void create(void* pixels, int width, int height, GLenum input_format = GL_RGB, GLenum output_format = GL_RGB, GLenum data_type = GL_UNSIGNED_BYTE) {
|
||||||
|
|
||||||
|
width_ = width;
|
||||||
|
height_ = height;
|
||||||
|
input_format_ = input_format;
|
||||||
|
output_format_ = output_format;
|
||||||
|
data_type_ = data_type;
|
||||||
|
|
||||||
|
//generate single texture, put handle in texture
|
||||||
|
glGenTextures(1, &texture_);
|
||||||
|
|
||||||
|
//normal 2d texture, bind to our texture handle
|
||||||
|
glBindTexture(GL_TEXTURE_2D, texture_);
|
||||||
|
|
||||||
|
//set texture parameters in currently bound texture, controls texture wrapping (or GL_CLAMP?)
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||||
|
|
||||||
|
//linearly interpolate between pixels, MIN if texture is too small for drawing area, MAG if drawing area is smaller than texture
|
||||||
|
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||||
|
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||||
|
|
||||||
|
//texture type, level, format to store as, width, height, border, format loaded in
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, input_format_, width_, height_, 0, output_format_, data_type_, pixels);
|
||||||
|
|
||||||
|
//UNBIND
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
|
||||||
|
} // this
|
||||||
|
|
||||||
|
~this() nothrow @nogc {
|
||||||
|
|
||||||
|
if (texture_ != 0) {
|
||||||
|
glDeleteTextures(1, &texture_);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // ~this
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds the texture handle, takes an argument for which texture unit to use.
|
||||||
|
*/
|
||||||
|
nothrow @nogc
|
||||||
|
void bind(int unit) {
|
||||||
|
|
||||||
|
assert(unit >= 0 && unit <= 31);
|
||||||
|
glActiveTexture(GL_TEXTURE0 + unit); //since this is sequential, this works
|
||||||
|
glBindTexture(GL_TEXTURE_2D, texture_);
|
||||||
|
|
||||||
|
} // bind
|
||||||
|
|
||||||
|
nothrow @nogc
|
||||||
|
void unbind() {
|
||||||
|
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
|
||||||
|
} // unbind
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the texture in place given the new texture buffer.
|
||||||
|
* Takes an optional offset to update only a part of the texture.
|
||||||
|
**/
|
||||||
|
nothrow @nogc
|
||||||
|
void update(void[] pixels, size_t offset = 0) {
|
||||||
|
|
||||||
|
bind(0);
|
||||||
|
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width_, height_, input_format_, data_type_, pixels.ptr);
|
||||||
|
unbind();
|
||||||
|
|
||||||
|
} // update
|
||||||
|
|
||||||
|
} // Texture
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic VertexArray structure, used to upload data of any given vertex type to the GPU.
|
||||||
|
*/
|
||||||
|
struct VertexArray {
|
||||||
|
|
||||||
|
private {
|
||||||
|
|
||||||
|
GLuint vao_;
|
||||||
|
GLuint vbo_;
|
||||||
|
GLenum type_; // type of vertex data, GL_TRIANGLES etc
|
||||||
|
uint num_vertices_;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@disable this(this);
|
||||||
|
|
||||||
|
nothrow @nogc
|
||||||
|
void create(in float[2][] vertices, GLenum draw_type = GL_STATIC_DRAW, GLenum primitive = GL_TRIANGLES) {
|
||||||
|
|
||||||
|
this.num_vertices_ = cast(uint)vertices.length;
|
||||||
|
this.type_ = primitive;
|
||||||
|
|
||||||
|
glGenVertexArrays(1, &vao_);
|
||||||
|
glBindVertexArray(vao_);
|
||||||
|
|
||||||
|
glGenBuffers(1, &vbo_);
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, vbo_);
|
||||||
|
glBufferData(GL_ARRAY_BUFFER, vertices.length * vertices[0].sizeof, vertices.ptr, draw_type);
|
||||||
|
|
||||||
|
// pos
|
||||||
|
glEnableVertexAttribArray(0);
|
||||||
|
glVertexAttribPointer(0,
|
||||||
|
2,
|
||||||
|
GL_FLOAT,
|
||||||
|
GL_FALSE,
|
||||||
|
float.sizeof * 2,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
glBindVertexArray(0);
|
||||||
|
|
||||||
|
} // this
|
||||||
|
|
||||||
|
~this() nothrow @nogc {
|
||||||
|
if (vao_ != 0) {
|
||||||
|
glDeleteVertexArrays(1, &vao_);
|
||||||
|
}
|
||||||
|
} // ~this
|
||||||
|
|
||||||
|
void bind() nothrow @nogc {
|
||||||
|
glBindVertexArray(vao_);
|
||||||
|
} // bind
|
||||||
|
|
||||||
|
void draw() nothrow @nogc {
|
||||||
|
glDrawArrays(type_, 0, num_vertices_);
|
||||||
|
} // draw
|
||||||
|
|
||||||
|
void unbind() nothrow @nogc {
|
||||||
|
glBindVertexArray(0);
|
||||||
|
} // unbind
|
||||||
|
|
||||||
|
} // VertexArray
|
||||||
|
|
||||||
|
const char* vs_shader = q{
|
||||||
|
#version 330 core
|
||||||
|
|
||||||
|
uniform vec2 screen_size;
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 pos;
|
||||||
|
|
||||||
|
out vec2 frag_uv;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
gl_Position = vec4(pos, 0.0, 1.0);
|
||||||
|
frag_uv = clamp(pos, vec2(0.0, 0.0), vec2(1.0, 1.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
const char* fs_shader = q{
|
||||||
|
#version 330 core
|
||||||
|
|
||||||
|
uniform sampler2D tex;
|
||||||
|
|
||||||
|
in vec2 frag_uv;
|
||||||
|
|
||||||
|
out vec4 out_col;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
float v = texture(tex, frag_uv.st).r * 255;
|
||||||
|
out_col = vec4(v, v, v, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
Shader shader;
|
||||||
|
VertexArray vao;
|
||||||
|
Texture tex;
|
||||||
|
|
||||||
|
void create(void* pixels, int w, int h) {
|
||||||
|
|
||||||
|
float[2][6] rect = [
|
||||||
|
[-1.0f, -1.0f], // top left
|
||||||
|
[1.0f, -1.0f], // top right
|
||||||
|
[1.0f, 1.0f], // bottom right
|
||||||
|
|
||||||
|
[-1.0f, -1.0f], // top left
|
||||||
|
[-1.0f, 1.0f], // bottom left
|
||||||
|
[1.0f, 1.0f], // bottom right
|
||||||
|
];
|
||||||
|
|
||||||
|
vao.create(rect[]);
|
||||||
|
tex.create(pixels, w, h, GL_RED, GL_RED);
|
||||||
|
shader.compile(&vs_shader, &fs_shader);
|
||||||
|
|
||||||
|
} // create
|
||||||
|
|
||||||
|
void draw(int screen_w, int screen_h) {
|
||||||
|
|
||||||
|
vao.bind();
|
||||||
|
shader.bind();
|
||||||
|
shader.setUniforms(screen_w, screen_h);
|
||||||
|
tex.bind(0);
|
||||||
|
vao.draw();
|
||||||
|
|
||||||
|
} // draw
|
||||||
|
|
||||||
|
nothrow @nogc
|
||||||
|
void update(void[] pixels, size_t offset = 0) {
|
||||||
|
tex.update(pixels, offset);
|
||||||
|
} // update
|
||||||
|
|
||||||
|
} // PixelBuffer
|
193
source/imgui.d
193
source/imgui.d
|
@ -23,11 +23,11 @@ auto bindDelegate(T, string file = __FILE__, size_t line = __LINE__)(T t) if(isD
|
||||||
struct Imgui {
|
struct Imgui {
|
||||||
|
|
||||||
// OpenGL data, cleanup soon
|
// OpenGL data, cleanup soon
|
||||||
GLuint g_FontTexture = 0;
|
GLuint font_texture_ = 0;
|
||||||
int g_ShaderHandle = 0, g_VertHandle = 0, g_FragHandle = 0;
|
int shander_handle_ = 0, vs_handle_ = 0, fs_handle_ = 0;
|
||||||
int g_AttribLocationTex = 0, g_AttribLocationProjMtx = 0;
|
int shader_attrib_loc_tex_ = 0, shader_attrib_loc_projmtx_ = 0;
|
||||||
int g_AttribLocationPosition = 0, g_AttribLocationUV = 0, g_AttribLocationColor = 0;
|
int shader_attrib_loc_pos_ = 0, shader_attrib_loc_uv_ = 0, shader_attrib_loc_color_ = 0;
|
||||||
uint g_VboHandle = 0, g_VaoHandle = 0, g_ElementsHandle = 0;
|
uint vao_ = 0, vbo_ = 0, ebo_ = 0;
|
||||||
|
|
||||||
// imgui input related state
|
// imgui input related state
|
||||||
bool[3] mouse_buttons_pressed;
|
bool[3] mouse_buttons_pressed;
|
||||||
|
@ -74,14 +74,14 @@ struct Imgui {
|
||||||
// upload texture to graphics system
|
// upload texture to graphics system
|
||||||
GLint last_texture;
|
GLint last_texture;
|
||||||
glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture);
|
glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture);
|
||||||
glGenTextures(1, &g_FontTexture);
|
glGenTextures(1, &font_texture_);
|
||||||
glBindTexture(GL_TEXTURE_2D, g_FontTexture);
|
glBindTexture(GL_TEXTURE_2D, font_texture_);
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
|
||||||
|
|
||||||
// store our identifier
|
// store our identifier
|
||||||
ImFontAtlas_SetTexID(io.Fonts, cast(void*)g_FontTexture);
|
ImFontAtlas_SetTexID(io.Fonts, cast(void*)font_texture_);
|
||||||
|
|
||||||
// restore state
|
// restore state
|
||||||
glBindTexture(GL_TEXTURE_2D, last_texture);
|
glBindTexture(GL_TEXTURE_2D, last_texture);
|
||||||
|
@ -122,36 +122,36 @@ struct Imgui {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
g_ShaderHandle = glCreateProgram();
|
shander_handle_ = glCreateProgram();
|
||||||
g_VertHandle = glCreateShader(GL_VERTEX_SHADER);
|
vs_handle_ = glCreateShader(GL_VERTEX_SHADER);
|
||||||
g_FragHandle = glCreateShader(GL_FRAGMENT_SHADER);
|
fs_handle_ = glCreateShader(GL_FRAGMENT_SHADER);
|
||||||
glShaderSource(g_VertHandle, 1, &vertex_shader, null);
|
glShaderSource(vs_handle_, 1, &vertex_shader, null);
|
||||||
glShaderSource(g_FragHandle, 1, &fragment_shader, null);
|
glShaderSource(fs_handle_, 1, &fragment_shader, null);
|
||||||
glCompileShader(g_VertHandle);
|
glCompileShader(vs_handle_);
|
||||||
glCompileShader(g_FragHandle);
|
glCompileShader(fs_handle_);
|
||||||
glAttachShader(g_ShaderHandle, g_VertHandle);
|
glAttachShader(shander_handle_, vs_handle_);
|
||||||
glAttachShader(g_ShaderHandle, g_FragHandle);
|
glAttachShader(shander_handle_, fs_handle_);
|
||||||
glLinkProgram(g_ShaderHandle);
|
glLinkProgram(shander_handle_);
|
||||||
|
|
||||||
g_AttribLocationTex = glGetUniformLocation(g_ShaderHandle, "Texture");
|
shader_attrib_loc_tex_ = glGetUniformLocation(shander_handle_, "Texture");
|
||||||
g_AttribLocationProjMtx = glGetUniformLocation(g_ShaderHandle, "ProjMtx");
|
shader_attrib_loc_projmtx_ = glGetUniformLocation(shander_handle_, "ProjMtx");
|
||||||
g_AttribLocationPosition = glGetAttribLocation(g_ShaderHandle, "Position");
|
shader_attrib_loc_pos_ = glGetAttribLocation(shander_handle_, "Position");
|
||||||
g_AttribLocationUV = glGetAttribLocation(g_ShaderHandle, "UV");
|
shader_attrib_loc_uv_ = glGetAttribLocation(shander_handle_, "UV");
|
||||||
g_AttribLocationColor = glGetAttribLocation(g_ShaderHandle, "Color");
|
shader_attrib_loc_color_ = glGetAttribLocation(shander_handle_, "Color");
|
||||||
|
|
||||||
glGenBuffers(1, &g_VboHandle);
|
glGenBuffers(1, &vbo_);
|
||||||
glGenBuffers(1, &g_ElementsHandle);
|
glGenBuffers(1, &ebo_);
|
||||||
|
|
||||||
glGenVertexArrays(1, &g_VaoHandle);
|
glGenVertexArrays(1, &vao_);
|
||||||
glBindVertexArray(g_VaoHandle);
|
glBindVertexArray(vao_);
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, g_VboHandle);
|
glBindBuffer(GL_ARRAY_BUFFER, vbo_);
|
||||||
glEnableVertexAttribArray(g_AttribLocationPosition);
|
glEnableVertexAttribArray(shader_attrib_loc_pos_);
|
||||||
glEnableVertexAttribArray(g_AttribLocationUV);
|
glEnableVertexAttribArray(shader_attrib_loc_uv_);
|
||||||
glEnableVertexAttribArray(g_AttribLocationColor);
|
glEnableVertexAttribArray(shader_attrib_loc_color_);
|
||||||
|
|
||||||
glVertexAttribPointer(g_AttribLocationPosition, 2, GL_FLOAT, GL_FALSE, ImDrawVert.sizeof, cast(void*)ImDrawVert.pos.offsetof);
|
glVertexAttribPointer(shader_attrib_loc_pos_, 2, GL_FLOAT, GL_FALSE, ImDrawVert.sizeof, cast(void*)ImDrawVert.pos.offsetof);
|
||||||
glVertexAttribPointer(g_AttribLocationUV, 2, GL_FLOAT, GL_FALSE, ImDrawVert.sizeof, cast(void*)ImDrawVert.uv.offsetof);
|
glVertexAttribPointer(shader_attrib_loc_uv_, 2, GL_FLOAT, GL_FALSE, ImDrawVert.sizeof, cast(void*)ImDrawVert.uv.offsetof);
|
||||||
glVertexAttribPointer(g_AttribLocationColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, ImDrawVert.sizeof, cast(void*)ImDrawVert.col.offsetof);
|
glVertexAttribPointer(shader_attrib_loc_color_, 4, GL_UNSIGNED_BYTE, GL_TRUE, ImDrawVert.sizeof, cast(void*)ImDrawVert.col.offsetof);
|
||||||
|
|
||||||
createFontTexture();
|
createFontTexture();
|
||||||
|
|
||||||
|
@ -162,6 +162,82 @@ struct Imgui {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct GLState {
|
||||||
|
GLenum last_active_texture;
|
||||||
|
GLint last_program;
|
||||||
|
GLint last_texture;
|
||||||
|
GLint last_array_buffer;
|
||||||
|
GLint last_element_array_buffer;
|
||||||
|
GLint last_vertex_array;
|
||||||
|
GLint[4] last_viewport;
|
||||||
|
GLint[4] last_scissor_box;
|
||||||
|
GLenum last_blend_src_rgb;
|
||||||
|
GLenum last_blend_dst_rgb;
|
||||||
|
GLenum last_blend_src_alpha;
|
||||||
|
GLenum last_blend_dst_alpha;
|
||||||
|
GLenum last_blend_equation_rgb;
|
||||||
|
GLenum last_blend_equation_alpha;
|
||||||
|
GLboolean last_enable_blend;
|
||||||
|
GLboolean last_enable_cull_face;
|
||||||
|
GLboolean last_enable_depth_test;
|
||||||
|
GLboolean last_enable_scissor_test;
|
||||||
|
}
|
||||||
|
|
||||||
|
nothrow @nogc
|
||||||
|
GLState backupGLState() {
|
||||||
|
|
||||||
|
GLState gl_state;
|
||||||
|
|
||||||
|
// backup GL state
|
||||||
|
with (gl_state) {
|
||||||
|
glGetIntegerv(GL_ACTIVE_TEXTURE, cast(GLint*)&last_active_texture);
|
||||||
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
glGetIntegerv(GL_CURRENT_PROGRAM, &last_program);
|
||||||
|
glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture);
|
||||||
|
glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_array_buffer);
|
||||||
|
glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &last_element_array_buffer);
|
||||||
|
glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &last_vertex_array);
|
||||||
|
glGetIntegerv(GL_VIEWPORT, last_viewport.ptr);
|
||||||
|
glGetIntegerv(GL_SCISSOR_BOX, last_scissor_box.ptr);
|
||||||
|
glGetIntegerv(GL_BLEND_SRC_RGB, cast(GLint*)&last_blend_src_rgb);
|
||||||
|
glGetIntegerv(GL_BLEND_DST_RGB, cast(GLint*)&last_blend_dst_rgb);
|
||||||
|
glGetIntegerv(GL_BLEND_SRC_ALPHA, cast(GLint*)&last_blend_src_alpha);
|
||||||
|
glGetIntegerv(GL_BLEND_DST_ALPHA, cast(GLint*)&last_blend_dst_alpha);
|
||||||
|
glGetIntegerv(GL_BLEND_EQUATION_RGB, cast(GLint*)&last_blend_equation_rgb);
|
||||||
|
glGetIntegerv(GL_BLEND_EQUATION_ALPHA, cast(GLint*)&last_blend_equation_alpha);
|
||||||
|
last_enable_blend = glIsEnabled(GL_BLEND);
|
||||||
|
last_enable_cull_face = glIsEnabled(GL_CULL_FACE);
|
||||||
|
last_enable_depth_test = glIsEnabled(GL_DEPTH_TEST);
|
||||||
|
last_enable_scissor_test = glIsEnabled(GL_SCISSOR_TEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
return gl_state;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
nothrow @nogc
|
||||||
|
void restoreGLState(GLState state) {
|
||||||
|
|
||||||
|
// restore modified GL state
|
||||||
|
with (state) {
|
||||||
|
glUseProgram(last_program);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, last_texture);
|
||||||
|
glActiveTexture(last_active_texture);
|
||||||
|
glBindVertexArray(last_vertex_array);
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer);
|
||||||
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, last_element_array_buffer);
|
||||||
|
glBlendEquationSeparate(last_blend_equation_rgb, last_blend_equation_alpha);
|
||||||
|
glBlendFuncSeparate(last_blend_src_rgb, last_blend_dst_rgb, last_blend_src_alpha, last_blend_dst_alpha);
|
||||||
|
if (last_enable_blend) glEnable(GL_BLEND); else glDisable(GL_BLEND);
|
||||||
|
if (last_enable_cull_face) glEnable(GL_CULL_FACE); else glDisable(GL_CULL_FACE);
|
||||||
|
if (last_enable_depth_test) glEnable(GL_DEPTH_TEST); else glDisable(GL_DEPTH_TEST);
|
||||||
|
if (last_enable_scissor_test) glEnable(GL_SCISSOR_TEST); else glDisable(GL_SCISSOR_TEST);
|
||||||
|
glViewport(last_viewport[0], last_viewport[1], cast(GLsizei)last_viewport[2], cast(GLsizei)last_viewport[3]);
|
||||||
|
glScissor(last_scissor_box[0], last_scissor_box[1], cast(GLsizei)last_scissor_box[2], cast(GLsizei)last_scissor_box[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
extern(C) nothrow
|
extern(C) nothrow
|
||||||
void renderDrawLists(ImDrawData* draw_data) {
|
void renderDrawLists(ImDrawData* draw_data) {
|
||||||
|
|
||||||
|
@ -173,25 +249,7 @@ struct Imgui {
|
||||||
draw_data.ScaleClipRects(io.DisplayFramebufferScale);
|
draw_data.ScaleClipRects(io.DisplayFramebufferScale);
|
||||||
|
|
||||||
// backup GL state
|
// backup GL state
|
||||||
GLenum last_active_texture; glGetIntegerv(GL_ACTIVE_TEXTURE, cast(GLint*)&last_active_texture);
|
GLState last_state = backupGLState();
|
||||||
glActiveTexture(GL_TEXTURE0);
|
|
||||||
GLint last_program; glGetIntegerv(GL_CURRENT_PROGRAM, &last_program);
|
|
||||||
GLint last_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture);
|
|
||||||
GLint last_array_buffer; glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_array_buffer);
|
|
||||||
GLint last_element_array_buffer; glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &last_element_array_buffer);
|
|
||||||
GLint last_vertex_array; glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &last_vertex_array);
|
|
||||||
GLint[4] last_viewport; glGetIntegerv(GL_VIEWPORT, last_viewport.ptr);
|
|
||||||
GLint[4] last_scissor_box; glGetIntegerv(GL_SCISSOR_BOX, last_scissor_box.ptr);
|
|
||||||
GLenum last_blend_src_rgb; glGetIntegerv(GL_BLEND_SRC_RGB, cast(GLint*)&last_blend_src_rgb);
|
|
||||||
GLenum last_blend_dst_rgb; glGetIntegerv(GL_BLEND_DST_RGB, cast(GLint*)&last_blend_dst_rgb);
|
|
||||||
GLenum last_blend_src_alpha; glGetIntegerv(GL_BLEND_SRC_ALPHA, cast(GLint*)&last_blend_src_alpha);
|
|
||||||
GLenum last_blend_dst_alpha; glGetIntegerv(GL_BLEND_DST_ALPHA, cast(GLint*)&last_blend_dst_alpha);
|
|
||||||
GLenum last_blend_equation_rgb; glGetIntegerv(GL_BLEND_EQUATION_RGB, cast(GLint*)&last_blend_equation_rgb);
|
|
||||||
GLenum last_blend_equation_alpha; glGetIntegerv(GL_BLEND_EQUATION_ALPHA, cast(GLint*)&last_blend_equation_alpha);
|
|
||||||
GLboolean last_enable_blend = glIsEnabled(GL_BLEND);
|
|
||||||
GLboolean last_enable_cull_face = glIsEnabled(GL_CULL_FACE);
|
|
||||||
GLboolean last_enable_depth_test = glIsEnabled(GL_DEPTH_TEST);
|
|
||||||
GLboolean last_enable_scissor_test = glIsEnabled(GL_SCISSOR_TEST);
|
|
||||||
|
|
||||||
// Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled
|
// Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled
|
||||||
glEnable(GL_BLEND);
|
glEnable(GL_BLEND);
|
||||||
|
@ -210,10 +268,10 @@ struct Imgui {
|
||||||
[-1.0f, 1.0f, 0.0f, 1.0f ],
|
[-1.0f, 1.0f, 0.0f, 1.0f ],
|
||||||
];
|
];
|
||||||
|
|
||||||
glUseProgram(g_ShaderHandle);
|
glUseProgram(shander_handle_);
|
||||||
glUniform1i(g_AttribLocationTex, 0);
|
glUniform1i(shader_attrib_loc_tex_, 0);
|
||||||
glUniformMatrix4fv(g_AttribLocationProjMtx, 1, GL_FALSE, &ortho_projection[0][0]);
|
glUniformMatrix4fv(shader_attrib_loc_projmtx_, 1, GL_FALSE, &ortho_projection[0][0]);
|
||||||
glBindVertexArray(g_VaoHandle);
|
glBindVertexArray(vao_);
|
||||||
|
|
||||||
foreach (int n; 0 .. draw_data.CmdListsCount) {
|
foreach (int n; 0 .. draw_data.CmdListsCount) {
|
||||||
ImDrawList* cmd_list = draw_data.CmdLists[n];
|
ImDrawList* cmd_list = draw_data.CmdLists[n];
|
||||||
|
@ -222,10 +280,10 @@ struct Imgui {
|
||||||
auto countVertices = ImDrawList_GetVertexBufferSize(cmd_list);
|
auto countVertices = ImDrawList_GetVertexBufferSize(cmd_list);
|
||||||
auto countIndices = ImDrawList_GetIndexBufferSize(cmd_list);
|
auto countIndices = ImDrawList_GetIndexBufferSize(cmd_list);
|
||||||
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, g_VboHandle);
|
glBindBuffer(GL_ARRAY_BUFFER, vbo_);
|
||||||
glBufferData(GL_ARRAY_BUFFER, countVertices * ImDrawVert.sizeof, cast(GLvoid*)ImDrawList_GetVertexPtr(cmd_list,0), GL_STREAM_DRAW);
|
glBufferData(GL_ARRAY_BUFFER, countVertices * ImDrawVert.sizeof, cast(GLvoid*)ImDrawList_GetVertexPtr(cmd_list,0), GL_STREAM_DRAW);
|
||||||
|
|
||||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, g_ElementsHandle);
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo_);
|
||||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, countIndices * ImDrawIdx.sizeof, cast(GLvoid*)ImDrawList_GetIndexPtr(cmd_list,0), GL_STREAM_DRAW);
|
glBufferData(GL_ELEMENT_ARRAY_BUFFER, countIndices * ImDrawIdx.sizeof, cast(GLvoid*)ImDrawList_GetIndexPtr(cmd_list,0), GL_STREAM_DRAW);
|
||||||
|
|
||||||
auto cmdCnt = ImDrawList_GetCmdSize(cmd_list);
|
auto cmdCnt = ImDrawList_GetCmdSize(cmd_list);
|
||||||
|
@ -244,21 +302,8 @@ struct Imgui {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore modified GL state
|
// restore old state
|
||||||
glUseProgram(last_program);
|
restoreGLState(last_state);
|
||||||
glBindTexture(GL_TEXTURE_2D, last_texture);
|
|
||||||
glActiveTexture(last_active_texture);
|
|
||||||
glBindVertexArray(last_vertex_array);
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer);
|
|
||||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, last_element_array_buffer);
|
|
||||||
glBlendEquationSeparate(last_blend_equation_rgb, last_blend_equation_alpha);
|
|
||||||
glBlendFuncSeparate(last_blend_src_rgb, last_blend_dst_rgb, last_blend_src_alpha, last_blend_dst_alpha);
|
|
||||||
if (last_enable_blend) glEnable(GL_BLEND); else glDisable(GL_BLEND);
|
|
||||||
if (last_enable_cull_face) glEnable(GL_CULL_FACE); else glDisable(GL_CULL_FACE);
|
|
||||||
if (last_enable_depth_test) glEnable(GL_DEPTH_TEST); else glDisable(GL_DEPTH_TEST);
|
|
||||||
if (last_enable_scissor_test) glEnable(GL_SCISSOR_TEST); else glDisable(GL_SCISSOR_TEST);
|
|
||||||
glViewport(last_viewport[0], last_viewport[1], cast(GLsizei)last_viewport[2], cast(GLsizei)last_viewport[3]);
|
|
||||||
glScissor(last_scissor_box[0], last_scissor_box[1], cast(GLsizei)last_scissor_box[2], cast(GLsizei)last_scissor_box[3]);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,8 @@ import glad.gl.all;
|
||||||
* Converts an integer representing a colour, for example 0x428bca into a 4 element
|
* Converts an integer representing a colour, for example 0x428bca into a 4 element
|
||||||
* int array for passing to OpenGL.
|
* int array for passing to OpenGL.
|
||||||
*/
|
*/
|
||||||
GLfloat[4] to(T : GLfloat[4])(int color, ubyte alpha = 255) nothrow @nogc pure {
|
nothrow @nogc pure
|
||||||
|
GLfloat[4] to(T : GLfloat[4])(int color, ubyte alpha = 255) {
|
||||||
|
|
||||||
GLfloat[4] gl_color = [ //mask out r, g, b components from int
|
GLfloat[4] gl_color = [ //mask out r, g, b components from int
|
||||||
cast(float)cast(ubyte)(color>>16)/255,
|
cast(float)cast(ubyte)(color>>16)/255,
|
||||||
|
@ -73,8 +74,7 @@ struct Window {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
import glad.gl.loader;
|
import glad.gl.loader : gladLoadGL;
|
||||||
import std.functional;
|
|
||||||
|
|
||||||
// Check OpenGL properties
|
// Check OpenGL properties
|
||||||
printf("OpenGL loaded\n");
|
printf("OpenGL loaded\n");
|
||||||
|
|
Loading…
Reference in New Issue