Add project files.

This commit is contained in:
Robert 2021-07-12 04:54:24 +02:00
parent d40149497b
commit 54d6fc8c1e
33 changed files with 50685 additions and 0 deletions

22
CMakeLists.txt Normal file
View file

@ -0,0 +1,22 @@
# CMakeList.txt : CMake project for GameboyEmu, include source and define
# project specific logic here.
#
cmake_minimum_required (VERSION 3.8)
project ("GameboyEmu")
set(CMAKE_CXX_STANDARD 17)
add_compile_definitions(_CRT_SECURE_NO_WARNINGS)
find_package(SDL2 CONFIG REQUIRED)
find_package(glad CONFIG REQUIRED)
find_package(imgui CONFIG REQUIRED)
file(GLOB_RECURSE HEADER_FILES
"include/*.hpp"
)
# Add source to this project's executable.
add_subdirectory(src)
# TODO: Add tests and install targets if needed.

29
CMakeSettings.json Normal file
View file

@ -0,0 +1,29 @@
{
"configurations": [
{
"name": "x64-Debug",
"generator": "Ninja",
"configurationType": "Debug",
"inheritEnvironments": [ "clang_cl_x64" ],
"buildRoot": "${projectDir}\\out\\build\\${name}",
"installRoot": "${projectDir}\\out\\install\\${name}",
"cmakeCommandArgs": "",
"buildCommandArgs": "",
"ctestCommandArgs": "",
"cmakeToolchain": "C:/Users/Robert/source/repos/vcpkg/scripts/buildsystems/vcpkg.cmake"
},
{
"name": "x64-Clang-Release",
"generator": "Ninja",
"configurationType": "RelWithDebInfo",
"buildRoot": "${projectDir}\\out\\build\\${name}",
"installRoot": "${projectDir}\\out\\install\\${name}",
"cmakeCommandArgs": "",
"buildCommandArgs": "",
"ctestCommandArgs": "",
"cmakeToolchain": "C:/Users/Robert/source/repos/vcpkg/scripts/buildsystems/vcpkg.cmake",
"inheritEnvironments": [ "clang_cl_x64_x64" ],
"variables": []
}
]
}

7
ReadMe.md Normal file
View file

@ -0,0 +1,7 @@
# yabGBE
(pronounced yab G. B. E.)
yabGBE stands for "Yet another bad gameboy emulator" and it already
perfectly encapsulates what this is. This emulator is unfinished, missing a lot of features and the few features that are implemented seem to be of questionable quality.
This is my first ever emulator programming project I tackled all by myself, without looking at explicit tutorials. I only used hardware docs and the gbdev wiki to get all my informations and knowledge.

81
include/bus.hpp Normal file
View file

@ -0,0 +1,81 @@
#pragma once
#include <array>
#include "util.hpp"
#include "cpu.hpp"
#include "lcd.hpp"
#include "rom.hpp"
typedef union
{
BYTE b;
struct
{
BYTE select : 2;
BYTE enable : 1;
BYTE padding : 5;
} w;
} TimerControl;
typedef union
{
BYTE b;
struct
{
BYTE rightA : 1;
BYTE leftB : 1;
BYTE upSelect : 1;
BYTE downStart : 1;
BYTE selectDirKeys : 1;
BYTE selectButtonKeys : 1;
BYTE unused : 2;
} w;
} JoypadReg;
struct Joypad
{
bool a, b, up, down, left, right, start, select;
};
class Bus
{
public:
Bus();
~Bus();
void AttachCPU(CPU& cpu);
void AttachLCD(LCD& lcd);
void InsertROM(ROM& rom);
bool Tick();
bool Execute();
bool Frame();
BYTE Read(WORD addr);
void Write(WORD addr, BYTE val);
BYTE Fetch(WORD addr);
private:
BYTE& GetReference(WORD addr);
public:
ROM* rom;
CPU* cpu;
LCD* lcd;
BYTE invalid;
BYTE div;
BYTE tima;
BYTE tma;
BYTE dmg_rom;
JoypadReg joypadReg;
TimerControl tac;
size_t internalCounter = 0;
Joypad joypad;
// std::array<BYTE, 0x2000> vram;
std::array<BYTE, 0x2000> wram;
std::array<BYTE, 0x80> hram;
};

110
include/cpu.hpp Normal file
View file

@ -0,0 +1,110 @@
#pragma once
#include <array>
#include <string>
#include "util.hpp"
class Bus;
struct Register
{
union
{
WORD w;
struct
{
BYTE lo, hi;
} b;
};
char name[3];
};
typedef union {
BYTE b;
struct
{
BYTE vblank : 1;
BYTE lcd_stat : 1;
BYTE timer : 1;
BYTE serial : 1;
BYTE joypad : 1;
BYTE padding : 3;
} flags;
} Interrupt;
typedef union
{
BYTE b;
struct
{
BYTE unused : 4;
BYTE carry : 1;
BYTE halfCarry : 1;
BYTE negative : 1;
BYTE zero : 1;
} f;
} StatusFlag;
typedef union
{
BYTE b;
struct
{
BYTE z : 3;
BYTE y : 3;
BYTE x : 2;
} xyz;
struct
{
BYTE padding1 : 3;
BYTE q : 1;
BYTE p : 2;
BYTE padding2 : 2;
} pq;
} Opcode;
class CPU
{
public:
void Powerup();
void Tick();
friend class Bus;
public:
Interrupt interruptEnable;
Interrupt interruptFlag;
size_t totalCycles;
BYTE cycles;
Register AF; // Acc & Flags
Register BC;
Register DE;
Register HL;
Register SP; // Stack pointer
Register PC; // Program counter
StatusFlag* flag;
Opcode opcode;
std::array<Register*, 4> rp;
std::array<Register*, 4> rp2;
BYTE ime;
Bus* bus;
bool stopped;
bool halted;
bool justHaltedWithDI;
private:
void WriteToRegister(BYTE reg, BYTE val);
BYTE ReadFromRegister(BYTE reg);
void ALU(BYTE operation, BYTE operand);
void CBPrefixed();
};

129
include/lcd.hpp Normal file
View file

@ -0,0 +1,129 @@
#pragma once
#include <array>
#include "util.hpp"
class Bus;
typedef union
{
BYTE b;
struct
{
BYTE mode : 2;
BYTE coincidence : 1;
BYTE mode0 : 1;
BYTE mode1 : 1;
BYTE mode2 : 1;
BYTE lyc : 1;
} w;
} STAT;
typedef union
{
BYTE b;
struct
{
BYTE priority : 1;
BYTE obj_enable : 1;
BYTE obj_size : 1;
BYTE bg_tilemap : 1;
BYTE tiledata : 1;
BYTE window : 1;
BYTE window_tilemap : 1;
BYTE enable : 1;
} w;
} LCDC;
typedef union
{
WORD w;
struct
{
BYTE lo, hi;
} b;
} LCDRegister;
typedef struct
{
WORD sprite;
WORD highByte, lowByte;
WORD full;
} PixelFIFO;
typedef struct
{
WORD tile;
BYTE cycle;
BYTE x, y;
BYTE lo, hi;
} PixelFetcher;
typedef union
{
QWORD q;
struct
{
BYTE y;
BYTE x;
BYTE idx;
struct
{
BYTE padding : 4;
BYTE palette : 1;
BYTE xFlip : 1;
BYTE yFlip : 1;
BYTE bgPriority : 1;
} attr;
} b;
} OAMEntry;
class LCD
{
public:
void Setup();
void Tick();
BYTE& GetReferenceToAddress(WORD addr, bool& handled);
bool Read(WORD addr, BYTE& val);
bool Write(WORD addr, BYTE val);
DWORD cycles;
WORD scanlineCycles;
friend class Bus;
friend class CPU;
public:
std::array<BYTE, 160 * 144> display;
std::array<BYTE, 0x2000> vram;
std::array<BYTE, 0xA0> oam;
public:
Bus* bus;
// Registers
LCDC lcdc;
STAT stat;
BYTE scy;
BYTE scx;
BYTE ly;
BYTE lyc;
BYTE wy;
BYTE wx;
BYTE bgp;
BYTE obp0;
BYTE obp1;
BYTE dma;
PixelFetcher fetcher;
PixelFIFO bgFIFO;
PixelFIFO spriteFIFO;
BYTE x;
BYTE dmaCycles;
};

19
include/mbcs/Imbc.hpp Normal file
View file

@ -0,0 +1,19 @@
#pragma once
#include "util.hpp"
class IMBC
{
public:
IMBC(WORD romBanks, WORD ramBanks, WORD ramSize) :
romBanks(romBanks), ramBanks(ramBanks), ramSize(ramSize)
{ }
virtual ~IMBC() {}
virtual bool GetMappedRead(WORD address, DWORD& mappedAddr) = 0;
virtual bool GetMappedWrite(WORD address, BYTE val, DWORD& mappedAddr) = 0;
protected:
WORD romBanks, ramBanks, ramSize;
};

29
include/mbcs/mbc0.hpp Normal file
View file

@ -0,0 +1,29 @@
#pragma once
#include "Imbc.hpp"
class MBC0 : public IMBC
{
public:
MBC0(WORD ramBanks) : IMBC(2, ramBanks, 8) {}
virtual bool GetMappedRead(WORD address, DWORD& mappedAddress) override;
virtual bool GetMappedWrite(WORD address, BYTE val, DWORD& mappedAddress) override;
private:
};
inline bool MBC0::GetMappedRead(WORD address, DWORD& mappedAddress)
{
mappedAddress = address;
return (address < 0x8000);
}
inline bool MBC0::GetMappedWrite(WORD address, BYTE val, DWORD& mappedAddress)
{
if (ramBanks && 0xA000 <= address && address < 0xC000)
{
mappedAddress = address;
return true;
}
return false;
}

70
include/mbcs/mbc1.hpp Normal file
View file

@ -0,0 +1,70 @@
#pragma once
#include "Imbc.hpp"
class MBC1 : public IMBC
{
public:
MBC1(WORD romBanks, WORD ramBanks, WORD ramSize) : IMBC(romBanks, ramBanks, ramSize) {}
virtual bool GetMappedRead(WORD address, DWORD& mappedAddr) override;
virtual bool GetMappedWrite(WORD address, BYTE val, DWORD& mappedAddr) override;
private:
BYTE RamEnable = 0x00;
BYTE RomBankNumber = 0x01;
BYTE RamBankNumber = 0x00;
BYTE ModeSelect = 0x00;
};
inline bool MBC1::GetMappedRead(WORD address, DWORD& mappedAddr)
{
if (address < 0x4000)
{
mappedAddr = address;
return true;
}
else if(0x4000 <= address && address < 0x8000)
{
mappedAddr = ((DWORD)((RamBankNumber << (5 * !ModeSelect)) | RomBankNumber) * 0x4000) + (address & 0x3FFF);
return true;
}
else if (0xA000 <= address && address < 0xC000)
{
mappedAddr = (DWORD)((RamBankNumber * ModeSelect) * 0x2000) + (address & 0x1FFF);
return true;
}
return false;
}
inline bool MBC1::GetMappedWrite(WORD address, BYTE val, DWORD& mappedAddr)
{
if (0x0000 <= address && address < 0x2000)
{
RamEnable = val;
return false;
}
else if (0x2000 <= address && address < 0x4000)
{
RomBankNumber = val;
if (RomBankNumber == 0x00 || RomBankNumber == 0x20 || RomBankNumber == 0x40 || RomBankNumber == 0x60)
RomBankNumber += 1;
return false;
}
else if (0x4000 <= address && address < 0x6000)
{
RamBankNumber = val;
return false;
}
else if (0x6000 <= address && address < 0x8000)
{
ModeSelect = val;
return false;
}
else if (ramBanks == 0 && 0xA000 <= address && address < 0xC000)
return false;
mappedAddr = (DWORD)((RamBankNumber * ModeSelect) * 0x2000) + (address & 0x1FFF);
return true;
}

37
include/rom.hpp Normal file
View file

@ -0,0 +1,37 @@
#pragma once
#include <vector>
#include <memory>
#include "util.hpp"
#include "mbcs/Imbc.hpp"
class Bus;
struct MemoryBankController
{
BYTE w;
struct
{
BYTE ROMBankNumber : 5;
BYTE RAMBankNumber : 2;
BYTE Mode : 1;
} b;
};
class ROM
{
public:
ROM(FILE* f);
BYTE Read(WORD addr);
void Write(WORD addr, BYTE val);
friend class Bus;
private:
Bus* bus;
std::unique_ptr<IMBC> mbc;
std::vector<BYTE> data, ram;
};

19
include/util.hpp Normal file
View file

@ -0,0 +1,19 @@
#pragma once
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef unsigned int DWORD;
typedef unsigned long long QWORD;
#define EXIT_MSG(msg, ...) { \
fprintf(stderr, (msg), __VA_ARGS__); \
fprintf(stderr, "\n"); \
fprintf(stderr, "%s\n", strerror(errno)); \
fprintf(stderr, "Exiting.\n"); \
}
inline BYTE undefined;

BIN
res/cpu_instrs.gb Normal file

Binary file not shown.

BIN
res/mario.gb Normal file

Binary file not shown.

26
src/CMakeLists.txt Normal file
View file

@ -0,0 +1,26 @@
file(GLOB_RECURSE IMGUI_SRC
"${CMAKE_SOURCE_DIR}/vendor/imgui/src/*.cpp"
)
add_executable(yabgbe)
target_sources(yabgbe
PRIVATE
"main.cpp" "bus.cpp" "cpu.cpp" "rom.cpp" "lcd.cpp"
${IMGUI_SRC}
PUBLIC
${HEADER_FILES}
)
target_include_directories(yabgbe
PUBLIC
"${CMAKE_SOURCE_DIR}/include"
"${CMAKE_SOURCE_DIR}/vendor/imgui/include"
SDL2::SDL2 glad::glad
)
target_link_libraries(yabgbe
SDL2::SDL2 SDL2::SDL2main
glad::glad
)
target_compile_options(yabgbe PRIVATE -Wimplicit-fallthrough)

195
src/bus.cpp Normal file
View file

@ -0,0 +1,195 @@
#include "bus.hpp"
#include <stdlib.h>
#include <stdio.h>
static WORD timerModuloLookup[4] = { 1024, 16, 64, 256 };
Bus::Bus()
{
// 8KB of VRAM
invalid = 0;
dmg_rom = 0;
tac.b = 0;
joypadReg.b = 0xFF;
joypad.a = true;
joypad.b = true;
joypad.down = true;
joypad.up = true;
joypad.right = true;
joypad.left = true;
joypad.start = true;
joypad.select = true;
}
Bus::~Bus()
{
}
void Bus::AttachCPU(CPU& c)
{
cpu = &c;
c.bus = this;
}
void Bus::AttachLCD(LCD& l)
{
lcd = &l;
l.bus = this;
lcd->Setup();
}
void Bus::InsertROM(ROM& r)
{
rom = &r;
r.bus = this;
}
bool Bus::Tick()
{
internalCounter++;
if(!cpu->stopped)
cpu->Tick();
lcd->Tick();
if (!(internalCounter % 0xFF))
div++;
if (tac.w.enable && !(internalCounter % timerModuloLookup[tac.w.select]))
{
tima++;
if (tima == 0x00)
{
tima = tma;
cpu->interruptFlag.flags.timer = 1;
}
}
return true;
}
bool Bus::Execute()
{
while (cpu->cycles > 0)
{
Tick();
if (invalid)
return false;
}
Tick();
return true;
}
bool Bus::Frame()
{
while (lcd->cycles > 0)
{
Tick();
if (invalid)
return false;
}
Tick();
return true;
}
BYTE Bus::Read(WORD addr)
{
BYTE returnVal;
if (lcd->Read(addr, returnVal))
{
return returnVal;
}
if ((addr >= 0x0000 && addr < 0x8000) || (addr >= 0xA000 && addr < 0xC000))
{
return rom->Read(addr);
}
if (addr == 0xFF00)
{
if (!joypadReg.w.selectButtonKeys) {
joypadReg.w.rightA = joypad.a;
joypadReg.w.leftB = joypad.b;
joypadReg.w.upSelect = joypad.select;
joypadReg.w.downStart = joypad.start;
}
else if (!joypadReg.w.selectDirKeys)
{
joypadReg.w.rightA = joypad.right;
joypadReg.w.leftB = joypad.left;
joypadReg.w.upSelect = joypad.up;
joypadReg.w.downStart = joypad.down;
}
return joypadReg.b;
}
return GetReference(addr);
}
BYTE Bus::Fetch(WORD addr)
{
return Read(addr);
}
void Bus::Write(WORD addr, BYTE val)
{
if (lcd->Write(addr, val))
return;
if ((addr >= 0x0000 && addr < 0x8000) || (addr >= 0xA000 && addr < 0xC000))
{
rom->Write(addr, val);
return;
}
GetReference(addr) = val;
undefined = 0xFF;
}
BYTE& Bus::GetReference(WORD addr)
{
if (addr >= 0xA000 && addr < 0xC000) // Accessing external RAM
{
return undefined;
}
else if (addr >= 0xC000 && addr < 0xFE00) // Accessing WRAM / ECHO RAM
{
return wram[addr & 0x1FFF];
}
else if (addr >= 0xFEA0 && addr < 0xFF00) // Accessing unusable area???
{
return undefined;
}
else if (addr >= 0xFF00 && addr < 0xFF80) // Accessing I/O regs
{
switch (addr)
{
case 0xFF00: return joypadReg.b;
case 0xFF04: return div;
case 0xFF05: return tima;
case 0xFF06: return tma;
case 0xFF07: return tac.b;
case 0xFF0F: return cpu->interruptFlag.b;
case 0xFF50: return dmg_rom;
}
}
else if (addr >= 0xFF80 && addr < 0xFFFF) // Accessing HRAM
{
return hram[addr & 0x7F];
}
else if (addr == 0xFFFF)
{
return cpu->interruptEnable.b;
}
return undefined;
}

1170
src/cpu.cpp Normal file

File diff suppressed because it is too large Load diff

345
src/lcd.cpp Normal file
View file

@ -0,0 +1,345 @@
#include "lcd.hpp"
#include <assert.h>
#include "bus.hpp"
static BYTE colormap[4] = { 0b00000000, 0b00100101, 0b01001010, 0b10010011 };
BYTE Reverse(BYTE b) {
b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
return b;
}
void LCD::Setup()
{
lcdc.b = 0;
stat.b = 0;
scy = 0;
scx = 0;
ly = 0;
lyc = 0;
wy = 0;
wx = 0;
cycles = 0;
scanlineCycles = 0;
fetcher.cycle = 0;
fetcher.x = 0; fetcher.y = -1;
x = 0;
dmaCycles = 0;
bgFIFO.full = 0x00;
spriteFIFO.full = 0x00;
}
void LCD::Tick()
{
// Update cycles
scanlineCycles++;
cycles++;
if (scanlineCycles > 455)
{
fetcher.cycle = 0;
scanlineCycles = 0;
ly += 1;
if (ly > 153)
{
cycles = 0;
ly = 0;
}
}
// Set modes
stat.w.coincidence = (lyc == ly);
// Send interrupts
if (ly == 144 && scanlineCycles == 0)
{
bus->cpu->interruptFlag.flags.vblank = 1;
}
if (
(stat.w.lyc && stat.w.coincidence) ||
(stat.w.mode2 && stat.w.mode == 2) ||
(stat.w.mode1 && stat.w.mode == 1) ||
(stat.w.mode0 && stat.w.mode == 0)
)
{
bus->cpu->interruptFlag.flags.lcd_stat = 1;
}
// Screen
if (ly >= 0 && ly < 144)
{
if (scanlineCycles == 0)
stat.w.mode = 2;
else if (scanlineCycles == 81) {
stat.w.mode = 3;
bgFIFO.full = 0x00;
x = 0;
fetcher.x = 0;
fetcher.cycle = 0;
fetcher.y = (fetcher.y + 1) % 8;
}
// OAM Search
if (stat.w.mode == 2)
{
OAMEntry* entry;
int counter = 0;
for (int i = 0; i < 40; i++)
{
entry = (OAMEntry*)(oam.data() + i * 4);
if (entry->b.y == ly)
{
counter++;
if (counter > 10)
entry->b.y = 0;
}
}
}
// Pixel Fetcher
else if (stat.w.mode == 3)
{
if (lcdc.w.obj_enable)
{
OAMEntry* entry;
for (int i = 0; i < 40; i++)
{
entry = (OAMEntry*)(oam.data() + i * 4);
if (x == entry->b.x - 8 && entry->b.y <= ly + 16 && ly + 8 + (8 * lcdc.w.obj_size) < entry->b.y)
{
// Fetch Sprite!
WORD yOffset = (ly - entry->b.y + 16);
if (entry->b.attr.yFlip)
{
yOffset = 8 * (1 + lcdc.w.obj_size) - yOffset;
}
BYTE lo = vram[2 * yOffset + (entry->b.idx * 16 * (1 + lcdc.w.obj_size))];
BYTE hi = vram[2 * yOffset + (entry->b.idx * 16 * (1 + lcdc.w.obj_size)) + 1];
if (entry->b.attr.xFlip)
{
lo = Reverse(lo);
hi = Reverse(hi);
}
spriteFIFO.lowByte = lo;
spriteFIFO.highByte = hi;
spriteFIFO.full = 0xFF;
BYTE counter = 0;
while (spriteFIFO.full)
{
BYTE color = ((spriteFIFO.highByte & 0x80) >> 6) | ((spriteFIFO.lowByte & 0x80) >> 7);
if (color != 0x00)
{
BYTE bgPriority = (bgFIFO.sprite & (0x80 >> counter)) >> 6;
if (!bgPriority)
{
BYTE bgColor = ((bgFIFO.highByte & (0x80 >> counter)) >> 6) | ((bgFIFO.lowByte & (0x80 >> counter)) >> 7);
if (entry->b.attr.bgPriority)
{
if (bgColor == 0x00)
{
bgFIFO.highByte ^= ((-((spriteFIFO.highByte & 0x80) >> 7) ^ bgFIFO.highByte) & (0x80 >> counter));
bgFIFO.lowByte ^= ((-((spriteFIFO.lowByte & 0x80) >> 7) ^ bgFIFO.lowByte) & (0x80 >> counter));
bgFIFO.sprite |= (0x80 >> counter);
}
}
else
{
bgFIFO.highByte ^= ((((spriteFIFO.highByte & 0x80) >> 7) ^ bgFIFO.highByte) & (0x80 >> counter));
bgFIFO.lowByte ^= ((((spriteFIFO.lowByte & 0x80) >> 7) ^ bgFIFO.lowByte) & (0x80 >> counter));
bgFIFO.sprite |= (0x80 >> counter);
}
}
}
spriteFIFO.full <<= 1;
spriteFIFO.highByte <<= 1;
spriteFIFO.lowByte <<= 1;
counter++;
}
}
}
}
switch (fetcher.cycle)
{
case 0: // Get Tile
{
// TODO: Can be different (Window)
WORD baseAddr = (lcdc.w.bg_tilemap ? 0x1C00 : 0x1800);
BYTE fetcherX = ((scx + fetcher.x) & 0xFF) / 8;
BYTE fetcherY = ((ly + scy) & 0xFF) / 8;
fetcher.tile = vram[baseAddr + (0x20 * fetcherY) + fetcherX];
}
break;
case 2: // Get Tile Data Low
{
WORD baseAddr = (lcdc.w.tiledata ? 0x0000 : 0x0800);
fetcher.lo = vram[baseAddr + 2 * ((fetcher.y + scy) % 8) + (fetcher.tile * 16)];
}
break;
case 4: // Get Tile Data High
{
// TODO: Check LCDC.4
WORD baseAddr = (lcdc.w.tiledata ? 0x0000 : 0x0800);
fetcher.hi = vram[baseAddr + 2 * ((fetcher.y + scy) % 8) + (fetcher.tile * 16) + 1];
}
break;
case 8: // Push
if (!(bgFIFO.full & 0x00FF))
{
bgFIFO.lowByte |= fetcher.lo;
bgFIFO.highByte |= fetcher.hi;
bgFIFO.sprite |= 0x00;
bgFIFO.full |= 0xFF;
fetcher.cycle = -1;
}
else
{
fetcher.cycle--;
}
fetcher.x--;
break;
}
fetcher.cycle++;
fetcher.x++;
// Draw pixels
if (bgFIFO.full & 0x00FF) // If Data in FIFO
{
BYTE color = ((bgFIFO.highByte & 0x80) >> 6) | ((bgFIFO.lowByte & 0x80) >> 7);
display[ly * 160 + x] = colormap[color];
x++;
bgFIFO.highByte <<= 1;
bgFIFO.lowByte <<= 1;
bgFIFO.full <<= 1;
bgFIFO.sprite <<= 1;
}
if (x == 160)
{
stat.w.mode = 0;
}
}
}
else if (ly == 144)
{
stat.w.mode = 1;
fetcher.y = -1;
}
}
bool LCD::Read(WORD addr, BYTE& val)
{
if (0x8000 <= addr && addr < 0xA000) // VRAM
{
if (stat.w.mode != 3 || !lcdc.w.enable)
val = vram[addr & 0x1FFF];
else
val = undefined;
return true;
}
else if (0xFE00 <= addr && addr < 0xFEA0) // OAM
{
if (stat.w.mode == 0 || stat.w.mode == 1 || !lcdc.w.enable)
val = oam[addr & 0x9F];
else
val = undefined;
return true;
}
else if (0xFF00 <= addr && addr < 0xFF80) // I/O
{
switch (addr)
{
case 0xFF40: val = lcdc.b; return true;
case 0xFF41: val = stat.b; return true;
case 0xFF42: val = scy; return true;
case 0xFF43: val = scx; return true;
case 0xFF44: val = ly; return true;
case 0xFF45: val = lyc; return true;
case 0xFF47: val = bgp; return true;
case 0xFF48: val = obp0; return true;
case 0xFF49: val = obp1; return true;
case 0xFF4A: val = wy; return true;
case 0xFF4B: val = wx; return true;
case 0xFF46: val = dma; return true;
}
}
return false;
}
bool LCD::Write(WORD addr, BYTE val)
{
if (0x8000 <= addr && addr < 0xA000) // VRAM
{
if (stat.w.mode != 3 || !lcdc.w.enable)
vram[addr & 0x1FFF] = val;
return true;
}
else if (0xFE00 <= addr && addr < 0xFEA0) // OAM
{
if (stat.w.mode == 0 || stat.w.mode == 1 || !lcdc.w.enable)
oam[addr & 0x9F] = val;
return true;
}
else if (0xFF00 <= addr && addr < 0xFF80) // I/O
{
switch (addr)
{
case 0xFF40: lcdc.b = val; return true;
case 0xFF41: stat.b = val; return true;
case 0xFF42: scy = val; return true;
case 0xFF43: scx = val; return true;
case 0xFF44: ly = val; return true;
case 0xFF45: lyc = val; return true;
case 0xFF47: bgp = val; return true;
case 0xFF48: obp0 = val; return true;
case 0xFF49: obp1 = val; return true;
case 0xFF4A: wy = val; return true;
case 0xFF4B: wx = val; return true;
case 0xFF46: dma = val; dmaCycles = 160;
}
while (dmaCycles != 0)
{
dmaCycles--;
oam[dmaCycles] = bus->Read(((WORD)dma) << 8 | dmaCycles);
}
}
return false;
}

388
src/main.cpp Normal file
View file

@ -0,0 +1,388 @@
#include "bus.hpp"
#include <iostream>
#include <SDL.h>
#include <imgui.h>
#include <imgui_sdl.h>
static BYTE colormap[4] = { 0b00000000, 0b00100101, 0b01001010, 0b10010011 };
int main(int argc, char** argv)
{
// Calculate the size of the window? This is literally random lol
int width = (512 + 384) * 2 + 10;
int height = 256 * 4;
SDL_Init(SDL_INIT_VIDEO);
SDL_Window* window = SDL_CreateWindow("Gameboy Emulator", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, SDL_WINDOW_SHOWN);
SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
SDL_Event e;
// Initialize ImGui
// I use ImGui to display lots of useful info, mainly memory and registers
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiSDL::Initialize(renderer, width, height);
ImGui::StyleColorsDark();
// To avoid some ImGui weirdness, we need to clear our screen using a texture
SDL_Texture* clearScreen = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, 1, 1);
{
SDL_SetRenderTarget(renderer, clearScreen);
SDL_SetRenderDrawColor(renderer, 100, 0, 100, 255);
SDL_RenderClear(renderer);
SDL_SetRenderTarget(renderer, NULL);
}
// All the textures to old the info we'll be displaying
SDL_Texture* ramScreen = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGB332, SDL_TEXTUREACCESS_STREAMING, 128, 64);
SDL_Texture* vramScreen = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGB332, SDL_TEXTUREACCESS_STREAMING, 128, 64);
SDL_Texture* tilemapRaw1 = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGB332, SDL_TEXTUREACCESS_STREAMING, 32, 32);
SDL_Texture* tilemapRaw2 = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGB332, SDL_TEXTUREACCESS_STREAMING, 32, 32);
SDL_Texture* tilemap1 = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGB332, SDL_TEXTUREACCESS_STREAMING, 256, 256);
SDL_Texture* tilemap2 = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGB332, SDL_TEXTUREACCESS_STREAMING, 256, 256);
SDL_Texture* hramScreen = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGB332, SDL_TEXTUREACCESS_STREAMING, 16, 8);
SDL_Texture* gameboyScreen = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGB332, SDL_TEXTUREACCESS_STREAMING, 160, 144);
// aspect ratios for the non-square windows
float ramAR = 128.f / 64.f;
float vramAR = 128.f / 64.f;
float hramAR = 16.f / 8.f;
float gbAR = 160.f / 144.f;
// Initialize the gameboy
Bus bus;
CPU cpu;
LCD lcd;
bus.AttachCPU(cpu);
bus.AttachLCD(lcd);
// Load the rom
if (argc != 2)
{
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Failed to load ROM", "Usage: gbemu <ROM>\nOr drag and drop a ROM onto the executable.", window);
exit(-1);
}
FILE* f = fopen(argv[1], "rb");
ROM rom(f);
fclose(f);
bus.InsertROM(rom);
cpu.Powerup();
// Placeholder vars for pixel arrays used to calcualte the rendered tilemaps
BYTE* tilemappixels1;
int tilemappitch1;
BYTE* tilemappixels2;
int tilemappitch2;
// This is literally irrelevant since I'm using a texture to clear anyways? lol
SDL_SetRenderDrawColor(renderer, 100, 0, 100, 255);
// The main program loop
bool done = false;
while (!done)
{
// If the emulator hasn't shit itself yet we can run the Gameboy for one frame
if (!bus.invalid)
bus.Frame();
// Also give ImGui all the inputs that happened
ImGuiIO& io = ImGui::GetIO();
int wheel = 0;
// Poll for events
while (SDL_PollEvent(&e))
{
// Boring ImGui stuff
if (e.type == SDL_QUIT) done = true;
else if (e.type == SDL_WINDOWEVENT)
{
if (e.window.event == SDL_WINDOWEVENT_SIZE_CHANGED)
{
io.DisplaySize.x = static_cast<float>(e.window.data1);
io.DisplaySize.y = static_cast<float>(e.window.data2);
}
else if(e.window.event == SDL_WINDOWEVENT_CLOSE)
{
done = true;
}
}
else if (e.type == SDL_MOUSEWHEEL)
{
wheel = e.wheel.y;
}
// Input for the Joypad. Look at how ugly it is
else if (e.type == SDL_KEYUP)
{
switch (e.key.keysym.sym)
{
case SDLK_s: bus.joypad.a = true; break;
case SDLK_a: bus.joypad.b = true; break;
case SDLK_UP: bus.joypad.up = true; break;
case SDLK_DOWN: bus.joypad.down = true; break;
case SDLK_LEFT: bus.joypad.left = true; break;
case SDLK_RIGHT: bus.joypad.right = true; break;
case SDLK_LSHIFT: bus.joypad.select = true; break;
case SDLK_RETURN: bus.joypad.start = true; break;
}
}
else if (e.type == SDL_KEYDOWN)
{
switch (e.key.keysym.sym)
{
case SDLK_s: bus.joypad.a = false; cpu.interruptFlag.flags.joypad = 1; break;
case SDLK_a: bus.joypad.b = false; cpu.interruptFlag.flags.joypad = 1; break;
case SDLK_UP: bus.joypad.up = false; cpu.interruptFlag.flags.joypad = 1; break;
case SDLK_DOWN: bus.joypad.down = false; cpu.interruptFlag.flags.joypad = 1; break;
case SDLK_LEFT: bus.joypad.left = false; cpu.interruptFlag.flags.joypad = 1; break;
case SDLK_RIGHT: bus.joypad.right = false; cpu.interruptFlag.flags.joypad = 1; break;
case SDLK_LSHIFT: bus.joypad.select = false; cpu.interruptFlag.flags.joypad = 1; break;
case SDLK_RETURN: bus.joypad.start = false; cpu.interruptFlag.flags.joypad = 1; break;
}
bus.cpu->stopped = false;
}
}
// Send all the things that changed to ImGui
int mouseX, mouseY;
const int buttons = SDL_GetMouseState(&mouseX, &mouseY);
io.DeltaTime = 1.0f / 60.0f;
io.MousePos = ImVec2(static_cast<float>(mouseX), static_cast<float>(mouseY));
io.MouseDown[0] = buttons & SDL_BUTTON(SDL_BUTTON_LEFT);
io.MouseDown[1] = buttons & SDL_BUTTON(SDL_BUTTON_RIGHT);
io.MouseWheel = static_cast<float>(wheel);
ImGui::NewFrame();
// Update the memory maps by literally just copying the raw data of the arrays to the texture
SDL_UpdateTexture(ramScreen, NULL, bus.wram.data(), 128);
SDL_UpdateTexture(vramScreen, NULL, lcd.vram.data(), 128);
SDL_UpdateTexture(hramScreen, NULL, bus.hram.data(), 16);
SDL_UpdateTexture(tilemapRaw1, NULL, lcd.vram.data() + 0x1800, 32);
SDL_UpdateTexture(tilemapRaw2, NULL, lcd.vram.data() + 0x1C00, 32);
SDL_UpdateTexture(gameboyScreen, NULL, lcd.display.data(), 160);
// Just for the rendered tilemap we need to be a bit more elaborate
SDL_LockTexture(tilemap1, NULL, (void**)&tilemappixels1, &tilemappitch1);
SDL_LockTexture(tilemap2, NULL, (void**)&tilemappixels2, &tilemappitch2);
// basically this is a quick and dirty PPU background renderer
// read about it in the wiki if you wanna know more, i cba to
// explain it in a comment here
for (int tileY = 0; tileY < 32; tileY++)
{
for (int tileX = 0; tileX < 32; tileX++)
{
BYTE tile1ID = lcd.vram[0x1800 + (tileY * 32) + tileX];
BYTE tile2ID = lcd.vram[0x1C00 + (tileY * 32) + tileX];
WORD baseAddr = (lcd.lcdc.w.tiledata ? 0x0000 : 0x0800);
WORD addr1 = baseAddr + (tile1ID * 16);
WORD addr2 = baseAddr + (tile2ID * 16);
for (int y = 0; y < 8; y++)
{
BYTE lo1 = lcd.vram[addr1 + 2 * y];
BYTE hi1 = lcd.vram[addr1 + 2 * y + 1];
BYTE lo2 = lcd.vram[addr2 + 2 * y];
BYTE hi2 = lcd.vram[addr2 + 2 * y + 1];
for (int x = 0; x < 8; x++)
{
BYTE loVal1 = (lo1 & (0x80 >> x)) >> (7 - x);
BYTE hiVal1 = (hi1 & (0x80 >> x)) >> (7 - x);
BYTE loVal2 = (lo2 & (0x80 >> x)) >> (7 - x);
BYTE hiVal2 = (hi2 & (0x80 >> x)) >> (7 - x);
tilemappixels1[(tileX * 8 + x) + (32 * 8) * (tileY * 8 + y)] = colormap[loVal1 + hiVal1];
tilemappixels2[(tileX * 8 + x) + (32 * 8) * (tileY * 8 + y)] = colormap[loVal2 + hiVal2];
}
}
}
}
SDL_UnlockTexture(tilemap2);
SDL_UnlockTexture(tilemap1);
// Put all the textures in their appropriate ImGui menu
ImGui::Begin("WRAM");
ImGui::Image(ramScreen, ImVec2(ImGui::GetWindowContentRegionWidth(), ImGui::GetWindowContentRegionWidth() / ramAR));
ImGui::End();
ImGui::Begin("VRAM");
ImGui::Text("Raw");
ImGui::Image(vramScreen, ImVec2(ImGui::GetWindowContentRegionWidth(), ImGui::GetWindowContentRegionWidth() / vramAR));
ImGui::Separator();
ImGui::Text("Raw Tilemaps");
if (ImGui::BeginTable("tilemaps", 2))
{
ImGui::TableNextColumn(); ImGui::Image(tilemapRaw1, ImVec2(ImGui::GetWindowContentRegionWidth() / 2, ImGui::GetWindowContentRegionWidth() / 2));
ImGui::TableNextColumn(); ImGui::Image(tilemapRaw2, ImVec2(ImGui::GetWindowContentRegionWidth() / 2, ImGui::GetWindowContentRegionWidth() / 2));
ImGui::EndTable();
}
ImGui::Separator();
ImGui::Text("Rendered Tilemaps");
if (ImGui::BeginTable("rawtilemaps", 2))
{
ImGui::TableNextColumn(); ImGui::Image(tilemap1, ImVec2(ImGui::GetWindowContentRegionWidth() / 2, ImGui::GetWindowContentRegionWidth() / 2));
ImGui::TableNextColumn(); ImGui::Image(tilemap2, ImVec2(ImGui::GetWindowContentRegionWidth() / 2, ImGui::GetWindowContentRegionWidth() / 2));
ImGui::EndTable();
}
ImGui::End();
ImGui::Begin("HRAM");
ImGui::Image(hramScreen, ImVec2(ImGui::GetWindowContentRegionWidth(), ImGui::GetWindowContentRegionWidth() / hramAR));
ImGui::End();
ImGui::Begin("CPU");
ImGui::Text("-- Registers --");
if (ImGui::BeginTable("Registers", 6))
{
ImGui::TableNextColumn(); ImGui::Text("AF");
ImGui::TableNextColumn(); ImGui::Text("BC");
ImGui::TableNextColumn(); ImGui::Text("DE");
ImGui::TableNextColumn(); ImGui::Text("HL");
ImGui::TableNextColumn(); ImGui::Text("SP");
ImGui::TableNextColumn(); ImGui::Text("PC");
ImGui::TableNextColumn(); ImGui::Text("%04x", cpu.AF.w);
ImGui::TableNextColumn(); ImGui::Text("%04x", cpu.BC.w);
ImGui::TableNextColumn(); ImGui::Text("%04x", cpu.DE.w);
ImGui::TableNextColumn(); ImGui::Text("%04x", cpu.HL.w);
ImGui::TableNextColumn(); ImGui::Text("%04x", cpu.SP.w);
ImGui::TableNextColumn(); ImGui::Text("%04x", cpu.PC.w);
ImGui::EndTable();
}
ImGui::Separator();
ImGui::Text("-- Status Flags --");
if (ImGui::BeginTable("Flags", 4))
{
ImGui::TableNextColumn(); ImGui::Text("Z");
ImGui::TableNextColumn(); ImGui::Text("N");
ImGui::TableNextColumn(); ImGui::Text("H");
ImGui::TableNextColumn(); ImGui::Text("C");
ImGui::TableNextColumn(); ImGui::Text("%u", cpu.flag->f.zero);
ImGui::TableNextColumn(); ImGui::Text("%u", cpu.flag->f.negative);
ImGui::TableNextColumn(); ImGui::Text("%u", cpu.flag->f.halfCarry);
ImGui::TableNextColumn(); ImGui::Text("%u", cpu.flag->f.carry);
ImGui::EndTable();
}
ImGui::Separator();
ImGui::Text("-- Interrupts --");
ImGui::Text("Interrupts: %s", (cpu.ime ? "ON" : "OFF"));
if (ImGui::BeginTable("Interrupts", 6))
{
ImGui::TableNextColumn(); ImGui::Text("");
ImGui::TableNextColumn(); ImGui::Text("V-Blank");
ImGui::TableNextColumn(); ImGui::Text("LCD STAT");
ImGui::TableNextColumn(); ImGui::Text("Timer");
ImGui::TableNextColumn(); ImGui::Text("Serial");
ImGui::TableNextColumn(); ImGui::Text("Joypad");
ImGui::TableNextColumn(); ImGui::Text("IE");
ImGui::TableNextColumn(); ImGui::Text("%u", cpu.interruptEnable.flags.vblank);
ImGui::TableNextColumn(); ImGui::Text("%u", cpu.interruptEnable.flags.lcd_stat);
ImGui::TableNextColumn(); ImGui::Text("%u", cpu.interruptEnable.flags.timer);
ImGui::TableNextColumn(); ImGui::Text("%u", cpu.interruptEnable.flags.serial);
ImGui::TableNextColumn(); ImGui::Text("%u", cpu.interruptEnable.flags.joypad);
ImGui::TableNextColumn(); ImGui::Text("IF");
ImGui::TableNextColumn(); ImGui::Text("%u", cpu.interruptFlag.flags.vblank);
ImGui::TableNextColumn(); ImGui::Text("%u", cpu.interruptFlag.flags.lcd_stat);
ImGui::TableNextColumn(); ImGui::Text("%u", cpu.interruptFlag.flags.timer);
ImGui::TableNextColumn(); ImGui::Text("%u", cpu.interruptFlag.flags.serial);
ImGui::TableNextColumn(); ImGui::Text("%u", cpu.interruptFlag.flags.joypad);
ImGui::EndTable();
}
if (bus.cpu->stopped)
{
ImGui::Separator();
ImGui::TextColored(ImVec4(255, 0, 0, 255), "STOPPED");
}
if (bus.cpu->halted)
{
ImGui::Separator();
ImGui::TextColored(ImVec4(255, 0, 0, 255), "HALTED");
}
ImGui::End();
ImGui::Begin("OAM");
if (ImGui::BeginTable("OAM", 5))
{
ImGui::TableNextColumn(); ImGui::Text("$");
ImGui::TableNextColumn(); ImGui::Text("Y");
ImGui::TableNextColumn(); ImGui::Text("X");
ImGui::TableNextColumn(); ImGui::Text("#");
ImGui::TableNextColumn(); ImGui::Text("F");
for (int i = 0; i < 40; i++)
{
WORD addr = i * 4;
ImGui::TableNextColumn(); ImGui::Text("%02x", 0xFE00 + addr);
ImGui::TableNextColumn(); ImGui::Text("%03u", lcd.oam[addr + 0x0]);
ImGui::TableNextColumn(); ImGui::Text("%03u", lcd.oam[addr + 0x1]);
ImGui::TableNextColumn(); ImGui::Text("%02x", lcd.oam[addr + 0x2]);
ImGui::TableNextColumn(); ImGui::Text("%02x", lcd.oam[addr + 0x3]);
}
ImGui::EndTable();
}
ImGui::End();
ImGui::Begin("Gameboy");
ImGui::Image(gameboyScreen, ImVec2(ImGui::GetWindowContentRegionWidth(), ImGui::GetWindowContentRegionWidth() / gbAR));
ImGui::End();
// Clear screen and render ImGui
SDL_RenderClear(renderer);
SDL_RenderCopy(renderer, clearScreen, NULL, NULL);
ImGui::Render();
ImGuiSDL::Render(ImGui::GetDrawData());
SDL_RenderPresent(renderer);
}
// Free memory & cleanup
ImGuiSDL::Deinitialize();
ImGui::DestroyContext();
SDL_DestroyTexture(gameboyScreen);
SDL_DestroyTexture(hramScreen);
SDL_DestroyTexture(tilemap2);
SDL_DestroyTexture(tilemap1);
SDL_DestroyTexture(tilemapRaw2);
SDL_DestroyTexture(tilemapRaw1);
SDL_DestroyTexture(vramScreen);
SDL_DestroyTexture(ramScreen);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}

104
src/rom.cpp Normal file
View file

@ -0,0 +1,104 @@
#include "rom.hpp"
#include <stdlib.h>
#include "bus.hpp"
#include "mbcs/mbc0.hpp"
#include "mbcs/mbc1.hpp"
static BYTE bios[0x100] = {
0x31, 0xFE, 0xFF, 0xAF, 0x21, 0xFF, 0x9F, 0x32, 0xCB, 0x7C, 0x20, 0xFB, 0x21, 0x26, 0xFF, 0x0E, 0x11, 0x3E, 0x80, 0x32, 0xE2, 0x0C, 0x3E, 0xF3, 0xE2, 0x32, 0x3E, 0x77, 0x77, 0x3E, 0xFC, 0xE0, 0x47, 0x11, 0x04, 0x01, 0x21, 0x10, 0x80, 0x1A, 0xCD, 0x95, 0x00, 0xCD, 0x96, 0x00, 0x13, 0x7B, 0xFE, 0x34, 0x20, 0xF3, 0x11, 0xD8, 0x00, 0x06, 0x08, 0x1A, 0x13, 0x22, 0x23, 0x05, 0x20, 0xF9, 0x3E, 0x19, 0xEA, 0x10, 0x99, 0x21, 0x2F, 0x99, 0x0E, 0x0C, 0x3D, 0x28, 0x08, 0x32, 0x0D, 0x20, 0xF9, 0x2E, 0x0F, 0x18, 0xF3, 0x67, 0x3E, 0x64, 0x57, 0xE0, 0x42, 0x3E, 0x91, 0xE0, 0x40, 0x04, 0x1E, 0x02, 0x0E, 0x0C, 0xF0, 0x44, 0xFE, 0x90, 0x20, 0xFA, 0x0D, 0x20, 0xF7, 0x1D, 0x20, 0xF2, 0x0E, 0x13, 0x24, 0x7C, 0x1E, 0x83, 0xFE, 0x62, 0x28, 0x06, 0x1E, 0xC1, 0xFE, 0x64, 0x20, 0x06, 0x7B, 0xE2, 0x0C, 0x3E, 0x87, 0xE2, 0xF0, 0x42, 0x90, 0xE0, 0x42, 0x15, 0x20, 0xD2, 0x05, 0x20, 0x4F, 0x16, 0x20, 0x18, 0xCB, 0x4F, 0x06, 0x04, 0xC5, 0xCB, 0x11, 0x17, 0xC1, 0xCB, 0x11, 0x17, 0x05, 0x20, 0xF5, 0x22, 0x23, 0x22, 0x23, 0xC9, 0xCE, 0xED, 0x66, 0x66, 0xCC, 0x0D, 0x00, 0x0B, 0x03, 0x73, 0x00, 0x83, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x08, 0x11, 0x1F, 0x88, 0x89, 0x00, 0x0E, 0xDC, 0xCC, 0x6E, 0xE6, 0xDD, 0xDD, 0xD9, 0x99, 0xBB, 0xBB, 0x67, 0x63, 0x6E, 0x0E, 0xEC, 0xCC, 0xDD, 0xDC, 0x99, 0x9F, 0xBB, 0xB9, 0x33, 0x3E, 0x3C, 0x42, 0xB9, 0xA5, 0xB9, 0xA5, 0x42, 0x3C, 0x21, 0x04, 0x01, 0x11, 0xA8, 0x00, 0x1A, 0x13, 0xBE, 0x20, 0xFE, 0x23, 0x7D, 0xFE, 0x34, 0x20, 0xF5, 0x06, 0x19, 0x78, 0x86, 0x23, 0x05, 0x20, 0xFB, 0x86, 0x20, 0xFE, 0x3E, 0x01, 0xE0, 0x50
};
ROM::ROM(FILE* f)
{
fseek(f, 0, SEEK_END);
long fsize = ftell(f);
rewind(f);
data = std::vector<BYTE>(fsize);
fread(data.data(), 1, fsize, f);
switch (data[0x149])
{
case 0x01: ram = std::vector<BYTE>(0x800); break;
case 0x02: ram = std::vector<BYTE>(0x2000); break;
case 0x03: ram = std::vector<BYTE>(0x8000); break;
case 0x04: ram = std::vector<BYTE>(0x20000); break;
case 0x05: ram = std::vector<BYTE>(0x10000); break;
}
WORD RomBanks = (WORD)0x2 << data[0x0148];
if (data[0x0148] == 0x52)
RomBanks = 72;
else if (data[0x0148] == 0x53)
RomBanks = 80;
else if (data[0x0148] == 0x54)
RomBanks = 96;
WORD RamBanks = 0x00;
switch (data[0x0149])
{
case 0x03: RamBanks = 4; break;
case 0x04: RamBanks = 16; break;
case 0x05: RamBanks = 8; break;
}
// Select MBC
switch (data[0x0147])
{
case 0x00:
mbc = std::make_unique<MBC0>(0);
break;
case 0x01:
case 0x02:
case 0x03:
mbc = std::make_unique<MBC1>(RomBanks, RamBanks, 8);
break;
case 0x08:
case 0x09:
mbc = std::make_unique<MBC0>(1);
break;
default:
EXIT_MSG("This ROM uses an unsupported memory bank controller: %02x\n", data[0x0147]);
exit(1);
break;
}
}
BYTE ROM::Read(WORD addr)
{
DWORD mappedAddr = 0x00;
if (!mbc->GetMappedRead(addr, mappedAddr))
return 0xFF;
switch (bus->Read(0xFF50) + (addr >= 0x100))
{
case 0:
// Read BIOS
return bios[addr];
default:
// Read ROM
if (addr < 0x8000)
return data[mappedAddr];
else
return ram[mappedAddr];
break;
}
return undefined;
}
void ROM::Write(WORD addr, BYTE val)
{
DWORD mappedAddr = 0x00;
if (!mbc->GetMappedWrite(addr, val, mappedAddr))
return;
ram[mappedAddr] = val;
}

2852
vendor/imgui/include/imgui.h vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,87 @@
// dear imgui: Renderer Backend for modern OpenGL with shaders / programmatic pipeline
// - Desktop GL: 2.x 3.x 4.x
// - Embedded GL: ES 2.0 (WebGL 1.0), ES 3.0 (WebGL 2.0)
// This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..)
// Implemented features:
// [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID!
// [x] Renderer: Desktop GL only: Support for large meshes (64k+ vertices) with 16-bit indices.
// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp.
// Read online: https://github.com/ocornut/imgui/tree/master/docs
// About Desktop OpenGL function loaders:
// Modern Desktop OpenGL doesn't have a standard portable header file to load OpenGL function pointers.
// Helper libraries are often used for this purpose! Here we are supporting a few common ones (gl3w, glew, glad).
// You may use another loader/header of your choice (glext, glLoadGen, etc.), or chose to manually implement your own.
// About GLSL version:
// The 'glsl_version' initialization parameter should be NULL (default) or a "#version XXX" string.
// On computer platform the GLSL version default to "#version 130". On OpenGL ES 3 platform it defaults to "#version 300 es"
// Only override if your GL version doesn't handle this GLSL version. See GLSL version table at the top of imgui_impl_opengl3.cpp.
#pragma once
#include "imgui.h" // IMGUI_IMPL_API
// Backend API
IMGUI_IMPL_API bool ImGui_ImplOpenGL3_Init(const char* glsl_version = NULL);
IMGUI_IMPL_API void ImGui_ImplOpenGL3_Shutdown();
IMGUI_IMPL_API void ImGui_ImplOpenGL3_NewFrame();
IMGUI_IMPL_API void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data);
// (Optional) Called by Init/NewFrame/Shutdown
IMGUI_IMPL_API bool ImGui_ImplOpenGL3_CreateFontsTexture();
IMGUI_IMPL_API void ImGui_ImplOpenGL3_DestroyFontsTexture();
IMGUI_IMPL_API bool ImGui_ImplOpenGL3_CreateDeviceObjects();
IMGUI_IMPL_API void ImGui_ImplOpenGL3_DestroyDeviceObjects();
// Specific OpenGL ES versions
//#define IMGUI_IMPL_OPENGL_ES2 // Auto-detected on Emscripten
//#define IMGUI_IMPL_OPENGL_ES3 // Auto-detected on iOS/Android
// Attempt to auto-detect the default Desktop GL loader based on available header files.
// If auto-detection fails or doesn't select the same GL loader file as used by your application,
// you are likely to get a crash in ImGui_ImplOpenGL3_Init().
// You can explicitly select a loader by using one of the '#define IMGUI_IMPL_OPENGL_LOADER_XXX' in imconfig.h or compiler command-line.
#if !defined(IMGUI_IMPL_OPENGL_ES2) \
&& !defined(IMGUI_IMPL_OPENGL_ES3) \
&& !defined(IMGUI_IMPL_OPENGL_LOADER_GL3W) \
&& !defined(IMGUI_IMPL_OPENGL_LOADER_GLEW) \
&& !defined(IMGUI_IMPL_OPENGL_LOADER_GLAD) \
&& !defined(IMGUI_IMPL_OPENGL_LOADER_GLAD2) \
&& !defined(IMGUI_IMPL_OPENGL_LOADER_GLBINDING2) \
&& !defined(IMGUI_IMPL_OPENGL_LOADER_GLBINDING3) \
&& !defined(IMGUI_IMPL_OPENGL_LOADER_CUSTOM)
// Try to detect GLES on matching platforms
#if defined(__APPLE__)
#include "TargetConditionals.h"
#endif
#if (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV)) || (defined(__ANDROID__))
#define IMGUI_IMPL_OPENGL_ES3 // iOS, Android -> GL ES 3, "#version 300 es"
#elif defined(__EMSCRIPTEN__)
#define IMGUI_IMPL_OPENGL_ES2 // Emscripten -> GL ES 2, "#version 100"
// Otherwise try to detect supported Desktop OpenGL loaders..
#elif defined(__has_include)
#if __has_include(<GL/glew.h>)
#define IMGUI_IMPL_OPENGL_LOADER_GLEW
#elif __has_include(<glad/glad.h>)
#define IMGUI_IMPL_OPENGL_LOADER_GLAD
#elif __has_include(<glad/gl.h>)
#define IMGUI_IMPL_OPENGL_LOADER_GLAD2
#elif __has_include(<GL/gl3w.h>)
#define IMGUI_IMPL_OPENGL_LOADER_GL3W
#elif __has_include(<glbinding/glbinding.h>)
#define IMGUI_IMPL_OPENGL_LOADER_GLBINDING3
#elif __has_include(<glbinding/Binding.h>)
#define IMGUI_IMPL_OPENGL_LOADER_GLBINDING2
#else
#error "Cannot detect OpenGL loader!"
#endif
#else
#define IMGUI_IMPL_OPENGL_LOADER_GL3W // Default to GL3W embedded in our repository
#endif
#endif

29
vendor/imgui/include/imgui_impl_sdl.h vendored Normal file
View file

@ -0,0 +1,29 @@
// dear imgui: Platform Backend for SDL2
// This needs to be used along with a Renderer (e.g. DirectX11, OpenGL3, Vulkan..)
// (Info: SDL2 is a cross-platform general purpose library for handling windows, inputs, graphics context creation, etc.)
// Implemented features:
// [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.
// [X] Platform: Clipboard support.
// [X] Platform: Keyboard arrays indexed using SDL_SCANCODE_* codes, e.g. ImGui::IsKeyPressed(SDL_SCANCODE_SPACE).
// [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
// Missing features:
// [ ] Platform: SDL2 handling of IME under Windows appears to be broken and it explicitly disable the regular Windows IME. You can restore Windows IME by compiling SDL with SDL_DISABLE_WINDOWS_IME.
// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp.
// Read online: https://github.com/ocornut/imgui/tree/master/docs
#pragma once
#include "imgui.h" // IMGUI_IMPL_API
struct SDL_Window;
typedef union SDL_Event SDL_Event;
IMGUI_IMPL_API bool ImGui_ImplSDL2_InitForOpenGL(SDL_Window* window, void* sdl_gl_context);
IMGUI_IMPL_API bool ImGui_ImplSDL2_InitForVulkan(SDL_Window* window);
IMGUI_IMPL_API bool ImGui_ImplSDL2_InitForD3D(SDL_Window* window);
IMGUI_IMPL_API bool ImGui_ImplSDL2_InitForMetal(SDL_Window* window);
IMGUI_IMPL_API void ImGui_ImplSDL2_Shutdown();
IMGUI_IMPL_API void ImGui_ImplSDL2_NewFrame(SDL_Window* window);
IMGUI_IMPL_API bool ImGui_ImplSDL2_ProcessEvent(const SDL_Event* event);

2688
vendor/imgui/include/imgui_internal.h vendored Normal file

File diff suppressed because it is too large Load diff

17
vendor/imgui/include/imgui_sdl.h vendored Normal file
View file

@ -0,0 +1,17 @@
#pragma once
struct ImDrawData;
struct SDL_Renderer;
namespace ImGuiSDL
{
// Call this to initialize the SDL renderer device that is internally used by the renderer.
void Initialize(SDL_Renderer* renderer, int windowWidth, int windowHeight);
// Call this before destroying your SDL renderer or ImGui to ensure that proper cleanup is done. This doesn't do anything critically important though,
// so if you're fine with small memory leaks at the end of your application, you can even omit this.
void Deinitialize();
// Call this every frame after ImGui::Render with ImGui::GetDrawData(). This will use the SDL_Renderer provided to the interfrace with Initialize
// to draw the contents of the draw data to the screen.
void Render(ImDrawData* drawData);
}

4903
vendor/imgui/include/imstb_truetype.h vendored Normal file

File diff suppressed because it is too large Load diff

11589
vendor/imgui/src/imgui.cpp vendored Normal file

File diff suppressed because it is too large Load diff

7725
vendor/imgui/src/imgui_demo.cpp vendored Normal file

File diff suppressed because it is too large Load diff

4152
vendor/imgui/src/imgui_draw.cpp vendored Normal file

File diff suppressed because it is too large Load diff

725
vendor/imgui/src/imgui_impl_opengl3.cpp vendored Normal file
View file

@ -0,0 +1,725 @@
// dear imgui: Renderer Backend for modern OpenGL with shaders / programmatic pipeline
// - Desktop GL: 2.x 3.x 4.x
// - Embedded GL: ES 2.0 (WebGL 1.0), ES 3.0 (WebGL 2.0)
// This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..)
// Implemented features:
// [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID!
// [x] Renderer: Desktop GL only: Support for large meshes (64k+ vertices) with 16-bit indices.
// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp.
// Read online: https://github.com/ocornut/imgui/tree/master/docs
// CHANGELOG
// (minor and older changes stripped away, please see git history for details)
// 2021-05-19: OpenGL: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement)
// 2021-04-06: OpenGL: Don't try to read GL_CLIP_ORIGIN unless we're OpenGL 4.5 or greater.
// 2021-02-18: OpenGL: Change blending equation to preserve alpha in output buffer.
// 2021-01-03: OpenGL: Backup, setup and restore GL_STENCIL_TEST state.
// 2020-10-23: OpenGL: Backup, setup and restore GL_PRIMITIVE_RESTART state.
// 2020-10-15: OpenGL: Use glGetString(GL_VERSION) instead of glGetIntegerv(GL_MAJOR_VERSION, ...) when the later returns zero (e.g. Desktop GL 2.x)
// 2020-09-17: OpenGL: Fix to avoid compiling/calling glBindSampler() on ES or pre 3.3 context which have the defines set by a loader.
// 2020-07-10: OpenGL: Added support for glad2 OpenGL loader.
// 2020-05-08: OpenGL: Made default GLSL version 150 (instead of 130) on OSX.
// 2020-04-21: OpenGL: Fixed handling of glClipControl(GL_UPPER_LEFT) by inverting projection matrix.
// 2020-04-12: OpenGL: Fixed context version check mistakenly testing for 4.0+ instead of 3.2+ to enable ImGuiBackendFlags_RendererHasVtxOffset.
// 2020-03-24: OpenGL: Added support for glbinding 2.x OpenGL loader.
// 2020-01-07: OpenGL: Added support for glbinding 3.x OpenGL loader.
// 2019-10-25: OpenGL: Using a combination of GL define and runtime GL version to decide whether to use glDrawElementsBaseVertex(). Fix building with pre-3.2 GL loaders.
// 2019-09-22: OpenGL: Detect default GL loader using __has_include compiler facility.
// 2019-09-16: OpenGL: Tweak initialization code to allow application calling ImGui_ImplOpenGL3_CreateFontsTexture() before the first NewFrame() call.
// 2019-05-29: OpenGL: Desktop GL only: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag.
// 2019-04-30: OpenGL: Added support for special ImDrawCallback_ResetRenderState callback to reset render state.
// 2019-03-29: OpenGL: Not calling glBindBuffer more than necessary in the render loop.
// 2019-03-15: OpenGL: Added a GL call + comments in ImGui_ImplOpenGL3_Init() to detect uninitialized GL function loaders early.
// 2019-03-03: OpenGL: Fix support for ES 2.0 (WebGL 1.0).
// 2019-02-20: OpenGL: Fix for OSX not supporting OpenGL 4.5, we don't try to read GL_CLIP_ORIGIN even if defined by the headers/loader.
// 2019-02-11: OpenGL: Projecting clipping rectangles correctly using draw_data->FramebufferScale to allow multi-viewports for retina display.
// 2019-02-01: OpenGL: Using GLSL 410 shaders for any version over 410 (e.g. 430, 450).
// 2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window.
// 2018-11-13: OpenGL: Support for GL 4.5's glClipControl(GL_UPPER_LEFT) / GL_CLIP_ORIGIN.
// 2018-08-29: OpenGL: Added support for more OpenGL loaders: glew and glad, with comments indicative that any loader can be used.
// 2018-08-09: OpenGL: Default to OpenGL ES 3 on iOS and Android. GLSL version default to "#version 300 ES".
// 2018-07-30: OpenGL: Support for GLSL 300 ES and 410 core. Fixes for Emscripten compilation.
// 2018-07-10: OpenGL: Support for more GLSL versions (based on the GLSL version string). Added error output when shaders fail to compile/link.
// 2018-06-08: Misc: Extracted imgui_impl_opengl3.cpp/.h away from the old combined GLFW/SDL+OpenGL3 examples.
// 2018-06-08: OpenGL: Use draw_data->DisplayPos and draw_data->DisplaySize to setup projection matrix and clipping rectangle.
// 2018-05-25: OpenGL: Removed unnecessary backup/restore of GL_ELEMENT_ARRAY_BUFFER_BINDING since this is part of the VAO state.
// 2018-05-14: OpenGL: Making the call to glBindSampler() optional so 3.2 context won't fail if the function is a NULL pointer.
// 2018-03-06: OpenGL: Added const char* glsl_version parameter to ImGui_ImplOpenGL3_Init() so user can override the GLSL version e.g. "#version 150".
// 2018-02-23: OpenGL: Create the VAO in the render function so the setup can more easily be used with multiple shared GL context.
// 2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplSdlGL3_RenderDrawData() in the .h file so you can call it yourself.
// 2018-01-07: OpenGL: Changed GLSL shader version from 330 to 150.
// 2017-09-01: OpenGL: Save and restore current bound sampler. Save and restore current polygon mode.
// 2017-05-01: OpenGL: Fixed save and restore of current blend func state.
// 2017-05-01: OpenGL: Fixed save and restore of current GL_ACTIVE_TEXTURE.
// 2016-09-05: OpenGL: Fixed save and restore of current scissor rectangle.
// 2016-07-29: OpenGL: Explicitly setting GL_UNPACK_ROW_LENGTH to reduce issues because SDL changes it. (#752)
//----------------------------------------
// OpenGL GLSL GLSL
// version version string
//----------------------------------------
// 2.0 110 "#version 110"
// 2.1 120 "#version 120"
// 3.0 130 "#version 130"
// 3.1 140 "#version 140"
// 3.2 150 "#version 150"
// 3.3 330 "#version 330 core"
// 4.0 400 "#version 400 core"
// 4.1 410 "#version 410 core"
// 4.2 420 "#version 410 core"
// 4.3 430 "#version 430 core"
// ES 2.0 100 "#version 100" = WebGL 1.0
// ES 3.0 300 "#version 300 es" = WebGL 2.0
//----------------------------------------
#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
#define _CRT_SECURE_NO_WARNINGS
#endif
#include "imgui.h"
#include "imgui_impl_opengl3.h"
#include <stdio.h>
#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier
#include <stddef.h> // intptr_t
#else
#include <stdint.h> // intptr_t
#endif
// GL includes
#if defined(IMGUI_IMPL_OPENGL_ES2)
#include <GLES2/gl2.h>
#elif defined(IMGUI_IMPL_OPENGL_ES3)
#if (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV))
#include <OpenGLES/ES3/gl.h> // Use GL ES 3
#else
#include <GLES3/gl3.h> // Use GL ES 3
#endif
#else
// About Desktop OpenGL function loaders:
// Modern desktop OpenGL doesn't have a standard portable header file to load OpenGL function pointers.
// Helper libraries are often used for this purpose! Here we are supporting a few common ones (gl3w, glew, glad).
// You may use another loader/header of your choice (glext, glLoadGen, etc.), or chose to manually implement your own.
#if defined(IMGUI_IMPL_OPENGL_LOADER_GL3W)
#include <GL/gl3w.h> // Needs to be initialized with gl3wInit() in user's code
#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLEW)
#include <GL/glew.h> // Needs to be initialized with glewInit() in user's code.
#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLAD)
#include <glad/glad.h> // Needs to be initialized with gladLoadGL() in user's code.
#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLAD2)
#include <glad/gl.h> // Needs to be initialized with gladLoadGL(...) or gladLoaderLoadGL() in user's code.
#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLBINDING2)
#ifndef GLFW_INCLUDE_NONE
#define GLFW_INCLUDE_NONE // GLFW including OpenGL headers causes ambiguity or multiple definition errors.
#endif
#include <glbinding/Binding.h> // Needs to be initialized with glbinding::Binding::initialize() in user's code.
#include <glbinding/gl/gl.h>
using namespace gl;
#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLBINDING3)
#ifndef GLFW_INCLUDE_NONE
#define GLFW_INCLUDE_NONE // GLFW including OpenGL headers causes ambiguity or multiple definition errors.
#endif
#include <glbinding/glbinding.h>// Needs to be initialized with glbinding::initialize() in user's code.
#include <glbinding/gl/gl.h>
using namespace gl;
#else
#include IMGUI_IMPL_OPENGL_LOADER_CUSTOM
#endif
#endif
// Desktop GL 3.2+ has glDrawElementsBaseVertex() which GL ES and WebGL don't have.
#if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3) && defined(GL_VERSION_3_2)
#define IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET
#endif
// Desktop GL 3.3+ has glBindSampler()
#if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3) && defined(GL_VERSION_3_3)
#define IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER
#endif
// Desktop GL 3.1+ has GL_PRIMITIVE_RESTART state
#if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3) && defined(GL_VERSION_3_1)
#define IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART
#endif
// OpenGL Data
static GLuint g_GlVersion = 0; // Extracted at runtime using GL_MAJOR_VERSION, GL_MINOR_VERSION queries (e.g. 320 for GL 3.2)
static char g_GlslVersionString[32] = ""; // Specified by user or detected based on compile time GL settings.
static GLuint g_FontTexture = 0;
static GLuint g_ShaderHandle = 0, g_VertHandle = 0, g_FragHandle = 0;
static GLint g_AttribLocationTex = 0, g_AttribLocationProjMtx = 0; // Uniforms location
static GLuint g_AttribLocationVtxPos = 0, g_AttribLocationVtxUV = 0, g_AttribLocationVtxColor = 0; // Vertex attributes location
static unsigned int g_VboHandle = 0, g_ElementsHandle = 0;
// Functions
bool ImGui_ImplOpenGL3_Init(const char* glsl_version)
{
// Query for GL version (e.g. 320 for GL 3.2)
#if !defined(IMGUI_IMPL_OPENGL_ES2)
GLint major = 0;
GLint minor = 0;
glGetIntegerv(GL_MAJOR_VERSION, &major);
glGetIntegerv(GL_MINOR_VERSION, &minor);
if (major == 0 && minor == 0)
{
// Query GL_VERSION in desktop GL 2.x, the string will start with "<major>.<minor>"
const char* gl_version = (const char*)glGetString(GL_VERSION);
sscanf(gl_version, "%d.%d", &major, &minor);
}
g_GlVersion = (GLuint)(major * 100 + minor * 10);
#else
g_GlVersion = 200; // GLES 2
#endif
// Setup backend capabilities flags
ImGuiIO& io = ImGui::GetIO();
io.BackendRendererName = "imgui_impl_opengl3";
#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET
if (g_GlVersion >= 320)
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
#endif
// Store GLSL version string so we can refer to it later in case we recreate shaders.
// Note: GLSL version is NOT the same as GL version. Leave this to NULL if unsure.
#if defined(IMGUI_IMPL_OPENGL_ES2)
if (glsl_version == NULL)
glsl_version = "#version 100";
#elif defined(IMGUI_IMPL_OPENGL_ES3)
if (glsl_version == NULL)
glsl_version = "#version 300 es";
#elif defined(__APPLE__)
if (glsl_version == NULL)
glsl_version = "#version 150";
#else
if (glsl_version == NULL)
glsl_version = "#version 130";
#endif
IM_ASSERT((int)strlen(glsl_version) + 2 < IM_ARRAYSIZE(g_GlslVersionString));
strcpy(g_GlslVersionString, glsl_version);
strcat(g_GlslVersionString, "\n");
// Debugging construct to make it easily visible in the IDE and debugger which GL loader has been selected.
// The code actually never uses the 'gl_loader' variable! It is only here so you can read it!
// If auto-detection fails or doesn't select the same GL loader file as used by your application,
// you are likely to get a crash below.
// You can explicitly select a loader by using '#define IMGUI_IMPL_OPENGL_LOADER_XXX' in imconfig.h or compiler command-line.
const char* gl_loader = "Unknown";
IM_UNUSED(gl_loader);
#if defined(IMGUI_IMPL_OPENGL_LOADER_GL3W)
gl_loader = "GL3W";
#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLEW)
gl_loader = "GLEW";
#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLAD)
gl_loader = "GLAD";
#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLAD2)
gl_loader = "GLAD2";
#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLBINDING2)
gl_loader = "glbinding2";
#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLBINDING3)
gl_loader = "glbinding3";
#elif defined(IMGUI_IMPL_OPENGL_LOADER_CUSTOM)
gl_loader = "custom";
#else
gl_loader = "none";
#endif
// Make an arbitrary GL call (we don't actually need the result)
// IF YOU GET A CRASH HERE: it probably means that you haven't initialized the OpenGL function loader used by this code.
// Desktop OpenGL 3/4 need a function loader. See the IMGUI_IMPL_OPENGL_LOADER_xxx explanation above.
GLint current_texture;
glGetIntegerv(GL_TEXTURE_BINDING_2D, &current_texture);
return true;
}
void ImGui_ImplOpenGL3_Shutdown()
{
ImGui_ImplOpenGL3_DestroyDeviceObjects();
}
void ImGui_ImplOpenGL3_NewFrame()
{
if (!g_ShaderHandle)
ImGui_ImplOpenGL3_CreateDeviceObjects();
}
static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_width, int fb_height, GLuint vertex_array_object)
{
// Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, polygon fill
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_CULL_FACE);
glDisable(GL_DEPTH_TEST);
glDisable(GL_STENCIL_TEST);
glEnable(GL_SCISSOR_TEST);
#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART
if (g_GlVersion >= 310)
glDisable(GL_PRIMITIVE_RESTART);
#endif
#ifdef GL_POLYGON_MODE
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
#endif
// Support for GL 4.5 rarely used glClipControl(GL_UPPER_LEFT)
#if defined(GL_CLIP_ORIGIN)
bool clip_origin_lower_left = true;
if (g_GlVersion >= 450)
{
GLenum current_clip_origin = 0; glGetIntegerv(GL_CLIP_ORIGIN, (GLint*)&current_clip_origin);
if (current_clip_origin == GL_UPPER_LEFT)
clip_origin_lower_left = false;
}
#endif
// Setup viewport, orthographic projection matrix
// Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps.
glViewport(0, 0, (GLsizei)fb_width, (GLsizei)fb_height);
float L = draw_data->DisplayPos.x;
float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x;
float T = draw_data->DisplayPos.y;
float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y;
#if defined(GL_CLIP_ORIGIN)
if (!clip_origin_lower_left) { float tmp = T; T = B; B = tmp; } // Swap top and bottom if origin is upper left
#endif
const float ortho_projection[4][4] =
{
{ 2.0f/(R-L), 0.0f, 0.0f, 0.0f },
{ 0.0f, 2.0f/(T-B), 0.0f, 0.0f },
{ 0.0f, 0.0f, -1.0f, 0.0f },
{ (R+L)/(L-R), (T+B)/(B-T), 0.0f, 1.0f },
};
glUseProgram(g_ShaderHandle);
glUniform1i(g_AttribLocationTex, 0);
glUniformMatrix4fv(g_AttribLocationProjMtx, 1, GL_FALSE, &ortho_projection[0][0]);
#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER
if (g_GlVersion >= 330)
glBindSampler(0, 0); // We use combined texture/sampler state. Applications using GL 3.3 may set that otherwise.
#endif
(void)vertex_array_object;
#ifndef IMGUI_IMPL_OPENGL_ES2
glBindVertexArray(vertex_array_object);
#endif
// Bind vertex/index buffers and setup attributes for ImDrawVert
glBindBuffer(GL_ARRAY_BUFFER, g_VboHandle);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, g_ElementsHandle);
glEnableVertexAttribArray(g_AttribLocationVtxPos);
glEnableVertexAttribArray(g_AttribLocationVtxUV);
glEnableVertexAttribArray(g_AttribLocationVtxColor);
glVertexAttribPointer(g_AttribLocationVtxPos, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, pos));
glVertexAttribPointer(g_AttribLocationVtxUV, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, uv));
glVertexAttribPointer(g_AttribLocationVtxColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, col));
}
// OpenGL3 Render function.
// Note that this implementation is little overcomplicated because we are saving/setting up/restoring every OpenGL state explicitly.
// This is in order to be able to run within an OpenGL engine that doesn't do so.
void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data)
{
// Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates)
int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x);
int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y);
if (fb_width <= 0 || fb_height <= 0)
return;
// Backup GL state
GLenum last_active_texture; glGetIntegerv(GL_ACTIVE_TEXTURE, (GLint*)&last_active_texture);
glActiveTexture(GL_TEXTURE0);
GLuint last_program; glGetIntegerv(GL_CURRENT_PROGRAM, (GLint*)&last_program);
GLuint last_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, (GLint*)&last_texture);
#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER
GLuint last_sampler; if (g_GlVersion >= 330) { glGetIntegerv(GL_SAMPLER_BINDING, (GLint*)&last_sampler); } else { last_sampler = 0; }
#endif
GLuint last_array_buffer; glGetIntegerv(GL_ARRAY_BUFFER_BINDING, (GLint*)&last_array_buffer);
#ifndef IMGUI_IMPL_OPENGL_ES2
GLuint last_vertex_array_object; glGetIntegerv(GL_VERTEX_ARRAY_BINDING, (GLint*)&last_vertex_array_object);
#endif
#ifdef GL_POLYGON_MODE
GLint last_polygon_mode[2]; glGetIntegerv(GL_POLYGON_MODE, last_polygon_mode);
#endif
GLint last_viewport[4]; glGetIntegerv(GL_VIEWPORT, last_viewport);
GLint last_scissor_box[4]; glGetIntegerv(GL_SCISSOR_BOX, last_scissor_box);
GLenum last_blend_src_rgb; glGetIntegerv(GL_BLEND_SRC_RGB, (GLint*)&last_blend_src_rgb);
GLenum last_blend_dst_rgb; glGetIntegerv(GL_BLEND_DST_RGB, (GLint*)&last_blend_dst_rgb);
GLenum last_blend_src_alpha; glGetIntegerv(GL_BLEND_SRC_ALPHA, (GLint*)&last_blend_src_alpha);
GLenum last_blend_dst_alpha; glGetIntegerv(GL_BLEND_DST_ALPHA, (GLint*)&last_blend_dst_alpha);
GLenum last_blend_equation_rgb; glGetIntegerv(GL_BLEND_EQUATION_RGB, (GLint*)&last_blend_equation_rgb);
GLenum last_blend_equation_alpha; glGetIntegerv(GL_BLEND_EQUATION_ALPHA, (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_stencil_test = glIsEnabled(GL_STENCIL_TEST);
GLboolean last_enable_scissor_test = glIsEnabled(GL_SCISSOR_TEST);
#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART
GLboolean last_enable_primitive_restart = (g_GlVersion >= 310) ? glIsEnabled(GL_PRIMITIVE_RESTART) : GL_FALSE;
#endif
// Setup desired GL state
// Recreate the VAO every time (this is to easily allow multiple GL contexts to be rendered to. VAO are not shared among GL contexts)
// The renderer would actually work without any VAO bound, but then our VertexAttrib calls would overwrite the default one currently bound.
GLuint vertex_array_object = 0;
#ifndef IMGUI_IMPL_OPENGL_ES2
glGenVertexArrays(1, &vertex_array_object);
#endif
ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object);
// Will project scissor/clipping rectangles into framebuffer space
ImVec2 clip_off = draw_data->DisplayPos; // (0,0) unless using multi-viewports
ImVec2 clip_scale = draw_data->FramebufferScale; // (1,1) unless using retina display which are often (2,2)
// Render command lists
for (int n = 0; n < draw_data->CmdListsCount; n++)
{
const ImDrawList* cmd_list = draw_data->CmdLists[n];
// Upload vertex/index buffers
glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)cmd_list->VtxBuffer.Size * (int)sizeof(ImDrawVert), (const GLvoid*)cmd_list->VtxBuffer.Data, GL_STREAM_DRAW);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, (GLsizeiptr)cmd_list->IdxBuffer.Size * (int)sizeof(ImDrawIdx), (const GLvoid*)cmd_list->IdxBuffer.Data, GL_STREAM_DRAW);
for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
{
const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
if (pcmd->UserCallback != NULL)
{
// User callback, registered via ImDrawList::AddCallback()
// (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)
if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)
ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object);
else
pcmd->UserCallback(cmd_list, pcmd);
}
else
{
// Project scissor/clipping rectangles into framebuffer space
ImVec4 clip_rect;
clip_rect.x = (pcmd->ClipRect.x - clip_off.x) * clip_scale.x;
clip_rect.y = (pcmd->ClipRect.y - clip_off.y) * clip_scale.y;
clip_rect.z = (pcmd->ClipRect.z - clip_off.x) * clip_scale.x;
clip_rect.w = (pcmd->ClipRect.w - clip_off.y) * clip_scale.y;
if (clip_rect.x < fb_width && clip_rect.y < fb_height && clip_rect.z >= 0.0f && clip_rect.w >= 0.0f)
{
// Apply scissor/clipping rectangle
glScissor((int)clip_rect.x, (int)(fb_height - clip_rect.w), (int)(clip_rect.z - clip_rect.x), (int)(clip_rect.w - clip_rect.y));
// Bind texture, Draw
glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->GetTexID());
#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET
if (g_GlVersion >= 320)
glDrawElementsBaseVertex(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx)), (GLint)pcmd->VtxOffset);
else
#endif
glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx)));
}
}
}
}
// Destroy the temporary VAO
#ifndef IMGUI_IMPL_OPENGL_ES2
glDeleteVertexArrays(1, &vertex_array_object);
#endif
// Restore modified GL state
glUseProgram(last_program);
glBindTexture(GL_TEXTURE_2D, last_texture);
#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER
if (g_GlVersion >= 330)
glBindSampler(0, last_sampler);
#endif
glActiveTexture(last_active_texture);
#ifndef IMGUI_IMPL_OPENGL_ES2
glBindVertexArray(last_vertex_array_object);
#endif
glBindBuffer(GL_ARRAY_BUFFER, last_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_stencil_test) glEnable(GL_STENCIL_TEST); else glDisable(GL_STENCIL_TEST);
if (last_enable_scissor_test) glEnable(GL_SCISSOR_TEST); else glDisable(GL_SCISSOR_TEST);
#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART
if (g_GlVersion >= 310) { if (last_enable_primitive_restart) glEnable(GL_PRIMITIVE_RESTART); else glDisable(GL_PRIMITIVE_RESTART); }
#endif
#ifdef GL_POLYGON_MODE
glPolygonMode(GL_FRONT_AND_BACK, (GLenum)last_polygon_mode[0]);
#endif
glViewport(last_viewport[0], last_viewport[1], (GLsizei)last_viewport[2], (GLsizei)last_viewport[3]);
glScissor(last_scissor_box[0], last_scissor_box[1], (GLsizei)last_scissor_box[2], (GLsizei)last_scissor_box[3]);
}
bool ImGui_ImplOpenGL3_CreateFontsTexture()
{
// Build texture atlas
ImGuiIO& io = ImGui::GetIO();
unsigned char* pixels;
int width, height;
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); // Load as RGBA 32-bit (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory.
// Upload texture to graphics system
GLint last_texture;
glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture);
glGenTextures(1, &g_FontTexture);
glBindTexture(GL_TEXTURE_2D, g_FontTexture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
#ifdef GL_UNPACK_ROW_LENGTH
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
#endif
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
// Store our identifier
io.Fonts->SetTexID((ImTextureID)(intptr_t)g_FontTexture);
// Restore state
glBindTexture(GL_TEXTURE_2D, last_texture);
return true;
}
void ImGui_ImplOpenGL3_DestroyFontsTexture()
{
if (g_FontTexture)
{
ImGuiIO& io = ImGui::GetIO();
glDeleteTextures(1, &g_FontTexture);
io.Fonts->SetTexID(0);
g_FontTexture = 0;
}
}
// If you get an error please report on github. You may try different GL context version or GLSL version. See GL<>GLSL version table at the top of this file.
static bool CheckShader(GLuint handle, const char* desc)
{
GLint status = 0, log_length = 0;
glGetShaderiv(handle, GL_COMPILE_STATUS, &status);
glGetShaderiv(handle, GL_INFO_LOG_LENGTH, &log_length);
if ((GLboolean)status == GL_FALSE)
fprintf(stderr, "ERROR: ImGui_ImplOpenGL3_CreateDeviceObjects: failed to compile %s!\n", desc);
if (log_length > 1)
{
ImVector<char> buf;
buf.resize((int)(log_length + 1));
glGetShaderInfoLog(handle, log_length, NULL, (GLchar*)buf.begin());
fprintf(stderr, "%s\n", buf.begin());
}
return (GLboolean)status == GL_TRUE;
}
// If you get an error please report on GitHub. You may try different GL context version or GLSL version.
static bool CheckProgram(GLuint handle, const char* desc)
{
GLint status = 0, log_length = 0;
glGetProgramiv(handle, GL_LINK_STATUS, &status);
glGetProgramiv(handle, GL_INFO_LOG_LENGTH, &log_length);
if ((GLboolean)status == GL_FALSE)
fprintf(stderr, "ERROR: ImGui_ImplOpenGL3_CreateDeviceObjects: failed to link %s! (with GLSL '%s')\n", desc, g_GlslVersionString);
if (log_length > 1)
{
ImVector<char> buf;
buf.resize((int)(log_length + 1));
glGetProgramInfoLog(handle, log_length, NULL, (GLchar*)buf.begin());
fprintf(stderr, "%s\n", buf.begin());
}
return (GLboolean)status == GL_TRUE;
}
bool ImGui_ImplOpenGL3_CreateDeviceObjects()
{
// Backup GL state
GLint last_texture, last_array_buffer;
glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture);
glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_array_buffer);
#ifndef IMGUI_IMPL_OPENGL_ES2
GLint last_vertex_array;
glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &last_vertex_array);
#endif
// Parse GLSL version string
int glsl_version = 130;
sscanf(g_GlslVersionString, "#version %d", &glsl_version);
const GLchar* vertex_shader_glsl_120 =
"uniform mat4 ProjMtx;\n"
"attribute vec2 Position;\n"
"attribute vec2 UV;\n"
"attribute vec4 Color;\n"
"varying vec2 Frag_UV;\n"
"varying vec4 Frag_Color;\n"
"void main()\n"
"{\n"
" Frag_UV = UV;\n"
" Frag_Color = Color;\n"
" gl_Position = ProjMtx * vec4(Position.xy,0,1);\n"
"}\n";
const GLchar* vertex_shader_glsl_130 =
"uniform mat4 ProjMtx;\n"
"in vec2 Position;\n"
"in vec2 UV;\n"
"in vec4 Color;\n"
"out vec2 Frag_UV;\n"
"out vec4 Frag_Color;\n"
"void main()\n"
"{\n"
" Frag_UV = UV;\n"
" Frag_Color = Color;\n"
" gl_Position = ProjMtx * vec4(Position.xy,0,1);\n"
"}\n";
const GLchar* vertex_shader_glsl_300_es =
"precision mediump float;\n"
"layout (location = 0) in vec2 Position;\n"
"layout (location = 1) in vec2 UV;\n"
"layout (location = 2) in vec4 Color;\n"
"uniform mat4 ProjMtx;\n"
"out vec2 Frag_UV;\n"
"out vec4 Frag_Color;\n"
"void main()\n"
"{\n"
" Frag_UV = UV;\n"
" Frag_Color = Color;\n"
" gl_Position = ProjMtx * vec4(Position.xy,0,1);\n"
"}\n";
const GLchar* vertex_shader_glsl_410_core =
"layout (location = 0) in vec2 Position;\n"
"layout (location = 1) in vec2 UV;\n"
"layout (location = 2) in vec4 Color;\n"
"uniform mat4 ProjMtx;\n"
"out vec2 Frag_UV;\n"
"out vec4 Frag_Color;\n"
"void main()\n"
"{\n"
" Frag_UV = UV;\n"
" Frag_Color = Color;\n"
" gl_Position = ProjMtx * vec4(Position.xy,0,1);\n"
"}\n";
const GLchar* fragment_shader_glsl_120 =
"#ifdef GL_ES\n"
" precision mediump float;\n"
"#endif\n"
"uniform sampler2D Texture;\n"
"varying vec2 Frag_UV;\n"
"varying vec4 Frag_Color;\n"
"void main()\n"
"{\n"
" gl_FragColor = Frag_Color * texture2D(Texture, Frag_UV.st);\n"
"}\n";
const GLchar* fragment_shader_glsl_130 =
"uniform sampler2D Texture;\n"
"in vec2 Frag_UV;\n"
"in vec4 Frag_Color;\n"
"out vec4 Out_Color;\n"
"void main()\n"
"{\n"
" Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n"
"}\n";
const GLchar* fragment_shader_glsl_300_es =
"precision mediump float;\n"
"uniform sampler2D Texture;\n"
"in vec2 Frag_UV;\n"
"in vec4 Frag_Color;\n"
"layout (location = 0) out vec4 Out_Color;\n"
"void main()\n"
"{\n"
" Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n"
"}\n";
const GLchar* fragment_shader_glsl_410_core =
"in vec2 Frag_UV;\n"
"in vec4 Frag_Color;\n"
"uniform sampler2D Texture;\n"
"layout (location = 0) out vec4 Out_Color;\n"
"void main()\n"
"{\n"
" Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n"
"}\n";
// Select shaders matching our GLSL versions
const GLchar* vertex_shader = NULL;
const GLchar* fragment_shader = NULL;
if (glsl_version < 130)
{
vertex_shader = vertex_shader_glsl_120;
fragment_shader = fragment_shader_glsl_120;
}
else if (glsl_version >= 410)
{
vertex_shader = vertex_shader_glsl_410_core;
fragment_shader = fragment_shader_glsl_410_core;
}
else if (glsl_version == 300)
{
vertex_shader = vertex_shader_glsl_300_es;
fragment_shader = fragment_shader_glsl_300_es;
}
else
{
vertex_shader = vertex_shader_glsl_130;
fragment_shader = fragment_shader_glsl_130;
}
// Create shaders
const GLchar* vertex_shader_with_version[2] = { g_GlslVersionString, vertex_shader };
g_VertHandle = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(g_VertHandle, 2, vertex_shader_with_version, NULL);
glCompileShader(g_VertHandle);
CheckShader(g_VertHandle, "vertex shader");
const GLchar* fragment_shader_with_version[2] = { g_GlslVersionString, fragment_shader };
g_FragHandle = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(g_FragHandle, 2, fragment_shader_with_version, NULL);
glCompileShader(g_FragHandle);
CheckShader(g_FragHandle, "fragment shader");
g_ShaderHandle = glCreateProgram();
glAttachShader(g_ShaderHandle, g_VertHandle);
glAttachShader(g_ShaderHandle, g_FragHandle);
glLinkProgram(g_ShaderHandle);
CheckProgram(g_ShaderHandle, "shader program");
g_AttribLocationTex = glGetUniformLocation(g_ShaderHandle, "Texture");
g_AttribLocationProjMtx = glGetUniformLocation(g_ShaderHandle, "ProjMtx");
g_AttribLocationVtxPos = (GLuint)glGetAttribLocation(g_ShaderHandle, "Position");
g_AttribLocationVtxUV = (GLuint)glGetAttribLocation(g_ShaderHandle, "UV");
g_AttribLocationVtxColor = (GLuint)glGetAttribLocation(g_ShaderHandle, "Color");
// Create buffers
glGenBuffers(1, &g_VboHandle);
glGenBuffers(1, &g_ElementsHandle);
ImGui_ImplOpenGL3_CreateFontsTexture();
// Restore modified GL state
glBindTexture(GL_TEXTURE_2D, last_texture);
glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer);
#ifndef IMGUI_IMPL_OPENGL_ES2
glBindVertexArray(last_vertex_array);
#endif
return true;
}
void ImGui_ImplOpenGL3_DestroyDeviceObjects()
{
if (g_VboHandle) { glDeleteBuffers(1, &g_VboHandle); g_VboHandle = 0; }
if (g_ElementsHandle) { glDeleteBuffers(1, &g_ElementsHandle); g_ElementsHandle = 0; }
if (g_ShaderHandle && g_VertHandle) { glDetachShader(g_ShaderHandle, g_VertHandle); }
if (g_ShaderHandle && g_FragHandle) { glDetachShader(g_ShaderHandle, g_FragHandle); }
if (g_VertHandle) { glDeleteShader(g_VertHandle); g_VertHandle = 0; }
if (g_FragHandle) { glDeleteShader(g_FragHandle); g_FragHandle = 0; }
if (g_ShaderHandle) { glDeleteProgram(g_ShaderHandle); g_ShaderHandle = 0; }
ImGui_ImplOpenGL3_DestroyFontsTexture();
}

377
vendor/imgui/src/imgui_impl_sdl.cpp vendored Normal file
View file

@ -0,0 +1,377 @@
// dear imgui: Platform Backend for SDL2
// This needs to be used along with a Renderer (e.g. DirectX11, OpenGL3, Vulkan..)
// (Info: SDL2 is a cross-platform general purpose library for handling windows, inputs, graphics context creation, etc.)
// (Requires: SDL 2.0. Prefer SDL 2.0.4+ for full feature support.)
// Implemented features:
// [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.
// [X] Platform: Clipboard support.
// [X] Platform: Keyboard arrays indexed using SDL_SCANCODE_* codes, e.g. ImGui::IsKeyPressed(SDL_SCANCODE_SPACE).
// [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
// Missing features:
// [ ] Platform: SDL2 handling of IME under Windows appears to be broken and it explicitly disable the regular Windows IME. You can restore Windows IME by compiling SDL with SDL_DISABLE_WINDOWS_IME.
// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp.
// Read online: https://github.com/ocornut/imgui/tree/master/docs
// CHANGELOG
// (minor and older changes stripped away, please see git history for details)
// 2021-03-22: Rework global mouse pos availability check listing supported platforms explicitly, effectively fixing mouse access on Raspberry Pi. (#2837, #3950)
// 2020-05-25: Misc: Report a zero display-size when window is minimized, to be consistent with other backends.
// 2020-02-20: Inputs: Fixed mapping for ImGuiKey_KeyPadEnter (using SDL_SCANCODE_KP_ENTER instead of SDL_SCANCODE_RETURN2).
// 2019-12-17: Inputs: On Wayland, use SDL_GetMouseState (because there is no global mouse state).
// 2019-12-05: Inputs: Added support for ImGuiMouseCursor_NotAllowed mouse cursor.
// 2019-07-21: Inputs: Added mapping for ImGuiKey_KeyPadEnter.
// 2019-04-23: Inputs: Added support for SDL_GameController (if ImGuiConfigFlags_NavEnableGamepad is set by user application).
// 2019-03-12: Misc: Preserve DisplayFramebufferScale when main window is minimized.
// 2018-12-21: Inputs: Workaround for Android/iOS which don't seem to handle focus related calls.
// 2018-11-30: Misc: Setting up io.BackendPlatformName so it can be displayed in the About Window.
// 2018-11-14: Changed the signature of ImGui_ImplSDL2_ProcessEvent() to take a 'const SDL_Event*'.
// 2018-08-01: Inputs: Workaround for Emscripten which doesn't seem to handle focus related calls.
// 2018-06-29: Inputs: Added support for the ImGuiMouseCursor_Hand cursor.
// 2018-06-08: Misc: Extracted imgui_impl_sdl.cpp/.h away from the old combined SDL2+OpenGL/Vulkan examples.
// 2018-06-08: Misc: ImGui_ImplSDL2_InitForOpenGL() now takes a SDL_GLContext parameter.
// 2018-05-09: Misc: Fixed clipboard paste memory leak (we didn't call SDL_FreeMemory on the data returned by SDL_GetClipboardText).
// 2018-03-20: Misc: Setup io.BackendFlags ImGuiBackendFlags_HasMouseCursors flag + honor ImGuiConfigFlags_NoMouseCursorChange flag.
// 2018-02-16: Inputs: Added support for mouse cursors, honoring ImGui::GetMouseCursor() value.
// 2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves.
// 2018-02-06: Inputs: Added mapping for ImGuiKey_Space.
// 2018-02-05: Misc: Using SDL_GetPerformanceCounter() instead of SDL_GetTicks() to be able to handle very high framerate (1000+ FPS).
// 2018-02-05: Inputs: Keyboard mapping is using scancodes everywhere instead of a confusing mixture of keycodes and scancodes.
// 2018-01-20: Inputs: Added Horizontal Mouse Wheel support.
// 2018-01-19: Inputs: When available (SDL 2.0.4+) using SDL_CaptureMouse() to retrieve coordinates outside of client area when dragging. Otherwise (SDL 2.0.3 and before) testing for SDL_WINDOW_INPUT_FOCUS instead of SDL_WINDOW_MOUSE_FOCUS.
// 2018-01-18: Inputs: Added mapping for ImGuiKey_Insert.
// 2017-08-25: Inputs: MousePos set to -FLT_MAX,-FLT_MAX when mouse is unavailable/missing (instead of -1,-1).
// 2016-10-15: Misc: Added a void* user_data parameter to Clipboard function handlers.
#include "imgui.h"
#include "imgui_impl_sdl.h"
// SDL
#include <SDL.h>
#include <SDL_syswm.h>
#if defined(__APPLE__)
#include "TargetConditionals.h"
#endif
#define SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE SDL_VERSION_ATLEAST(2,0,4)
#define SDL_HAS_VULKAN SDL_VERSION_ATLEAST(2,0,6)
// Data
static SDL_Window* g_Window = NULL;
static Uint64 g_Time = 0;
static bool g_MousePressed[3] = { false, false, false };
static SDL_Cursor* g_MouseCursors[ImGuiMouseCursor_COUNT] = {};
static char* g_ClipboardTextData = NULL;
static bool g_MouseCanUseGlobalState = true;
static const char* ImGui_ImplSDL2_GetClipboardText(void*)
{
if (g_ClipboardTextData)
SDL_free(g_ClipboardTextData);
g_ClipboardTextData = SDL_GetClipboardText();
return g_ClipboardTextData;
}
static void ImGui_ImplSDL2_SetClipboardText(void*, const char* text)
{
SDL_SetClipboardText(text);
}
// You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs.
// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application.
// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application.
// Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags.
// If you have multiple SDL events and some of them are not meant to be used by dear imgui, you may need to filter events based on their windowID field.
bool ImGui_ImplSDL2_ProcessEvent(const SDL_Event* event)
{
ImGuiIO& io = ImGui::GetIO();
switch (event->type)
{
case SDL_MOUSEWHEEL:
{
if (event->wheel.x > 0) io.MouseWheelH += 1;
if (event->wheel.x < 0) io.MouseWheelH -= 1;
if (event->wheel.y > 0) io.MouseWheel += 1;
if (event->wheel.y < 0) io.MouseWheel -= 1;
return true;
}
case SDL_MOUSEBUTTONDOWN:
{
if (event->button.button == SDL_BUTTON_LEFT) g_MousePressed[0] = true;
if (event->button.button == SDL_BUTTON_RIGHT) g_MousePressed[1] = true;
if (event->button.button == SDL_BUTTON_MIDDLE) g_MousePressed[2] = true;
return true;
}
case SDL_TEXTINPUT:
{
io.AddInputCharactersUTF8(event->text.text);
return true;
}
case SDL_KEYDOWN:
case SDL_KEYUP:
{
int key = event->key.keysym.scancode;
IM_ASSERT(key >= 0 && key < IM_ARRAYSIZE(io.KeysDown));
io.KeysDown[key] = (event->type == SDL_KEYDOWN);
io.KeyShift = ((SDL_GetModState() & KMOD_SHIFT) != 0);
io.KeyCtrl = ((SDL_GetModState() & KMOD_CTRL) != 0);
io.KeyAlt = ((SDL_GetModState() & KMOD_ALT) != 0);
#ifdef _WIN32
io.KeySuper = false;
#else
io.KeySuper = ((SDL_GetModState() & KMOD_GUI) != 0);
#endif
return true;
}
}
return false;
}
static bool ImGui_ImplSDL2_Init(SDL_Window* window)
{
g_Window = window;
// Setup backend capabilities flags
ImGuiIO& io = ImGui::GetIO();
io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional)
io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos; // We can honor io.WantSetMousePos requests (optional, rarely used)
io.BackendPlatformName = "imgui_impl_sdl";
// Keyboard mapping. Dear ImGui will use those indices to peek into the io.KeysDown[] array.
io.KeyMap[ImGuiKey_Tab] = SDL_SCANCODE_TAB;
io.KeyMap[ImGuiKey_LeftArrow] = SDL_SCANCODE_LEFT;
io.KeyMap[ImGuiKey_RightArrow] = SDL_SCANCODE_RIGHT;
io.KeyMap[ImGuiKey_UpArrow] = SDL_SCANCODE_UP;
io.KeyMap[ImGuiKey_DownArrow] = SDL_SCANCODE_DOWN;
io.KeyMap[ImGuiKey_PageUp] = SDL_SCANCODE_PAGEUP;
io.KeyMap[ImGuiKey_PageDown] = SDL_SCANCODE_PAGEDOWN;
io.KeyMap[ImGuiKey_Home] = SDL_SCANCODE_HOME;
io.KeyMap[ImGuiKey_End] = SDL_SCANCODE_END;
io.KeyMap[ImGuiKey_Insert] = SDL_SCANCODE_INSERT;
io.KeyMap[ImGuiKey_Delete] = SDL_SCANCODE_DELETE;
io.KeyMap[ImGuiKey_Backspace] = SDL_SCANCODE_BACKSPACE;
io.KeyMap[ImGuiKey_Space] = SDL_SCANCODE_SPACE;
io.KeyMap[ImGuiKey_Enter] = SDL_SCANCODE_RETURN;
io.KeyMap[ImGuiKey_Escape] = SDL_SCANCODE_ESCAPE;
io.KeyMap[ImGuiKey_KeyPadEnter] = SDL_SCANCODE_KP_ENTER;
io.KeyMap[ImGuiKey_A] = SDL_SCANCODE_A;
io.KeyMap[ImGuiKey_C] = SDL_SCANCODE_C;
io.KeyMap[ImGuiKey_V] = SDL_SCANCODE_V;
io.KeyMap[ImGuiKey_X] = SDL_SCANCODE_X;
io.KeyMap[ImGuiKey_Y] = SDL_SCANCODE_Y;
io.KeyMap[ImGuiKey_Z] = SDL_SCANCODE_Z;
io.SetClipboardTextFn = ImGui_ImplSDL2_SetClipboardText;
io.GetClipboardTextFn = ImGui_ImplSDL2_GetClipboardText;
io.ClipboardUserData = NULL;
// Load mouse cursors
g_MouseCursors[ImGuiMouseCursor_Arrow] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW);
g_MouseCursors[ImGuiMouseCursor_TextInput] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM);
g_MouseCursors[ImGuiMouseCursor_ResizeAll] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEALL);
g_MouseCursors[ImGuiMouseCursor_ResizeNS] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENS);
g_MouseCursors[ImGuiMouseCursor_ResizeEW] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE);
g_MouseCursors[ImGuiMouseCursor_ResizeNESW] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENESW);
g_MouseCursors[ImGuiMouseCursor_ResizeNWSE] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENWSE);
g_MouseCursors[ImGuiMouseCursor_Hand] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND);
g_MouseCursors[ImGuiMouseCursor_NotAllowed] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NO);
// Check and store if we are on a SDL backend that supports global mouse position
// ("wayland" and "rpi" don't support it, but we chose to use a white-list instead of a black-list)
const char* sdl_backend = SDL_GetCurrentVideoDriver();
const char* global_mouse_whitelist[] = { "windows", "cocoa", "x11", "DIVE", "VMAN" };
g_MouseCanUseGlobalState = false;
for (int n = 0; n < IM_ARRAYSIZE(global_mouse_whitelist); n++)
if (strncmp(sdl_backend, global_mouse_whitelist[n], strlen(global_mouse_whitelist[n])) == 0)
g_MouseCanUseGlobalState = true;
#ifdef _WIN32
SDL_SysWMinfo wmInfo;
SDL_VERSION(&wmInfo.version);
SDL_GetWindowWMInfo(window, &wmInfo);
io.ImeWindowHandle = wmInfo.info.win.window;
#else
(void)window;
#endif
return true;
}
bool ImGui_ImplSDL2_InitForOpenGL(SDL_Window* window, void* sdl_gl_context)
{
(void)sdl_gl_context; // Viewport branch will need this.
return ImGui_ImplSDL2_Init(window);
}
bool ImGui_ImplSDL2_InitForVulkan(SDL_Window* window)
{
#if !SDL_HAS_VULKAN
IM_ASSERT(0 && "Unsupported");
#endif
return ImGui_ImplSDL2_Init(window);
}
bool ImGui_ImplSDL2_InitForD3D(SDL_Window* window)
{
#if !defined(_WIN32)
IM_ASSERT(0 && "Unsupported");
#endif
return ImGui_ImplSDL2_Init(window);
}
bool ImGui_ImplSDL2_InitForMetal(SDL_Window* window)
{
return ImGui_ImplSDL2_Init(window);
}
void ImGui_ImplSDL2_Shutdown()
{
g_Window = NULL;
// Destroy last known clipboard data
if (g_ClipboardTextData)
SDL_free(g_ClipboardTextData);
g_ClipboardTextData = NULL;
// Destroy SDL mouse cursors
for (ImGuiMouseCursor cursor_n = 0; cursor_n < ImGuiMouseCursor_COUNT; cursor_n++)
SDL_FreeCursor(g_MouseCursors[cursor_n]);
memset(g_MouseCursors, 0, sizeof(g_MouseCursors));
}
static void ImGui_ImplSDL2_UpdateMousePosAndButtons()
{
ImGuiIO& io = ImGui::GetIO();
// Set OS mouse position if requested (rarely used, only when ImGuiConfigFlags_NavEnableSetMousePos is enabled by user)
if (io.WantSetMousePos)
SDL_WarpMouseInWindow(g_Window, (int)io.MousePos.x, (int)io.MousePos.y);
else
io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX);
int mx, my;
Uint32 mouse_buttons = SDL_GetMouseState(&mx, &my);
io.MouseDown[0] = g_MousePressed[0] || (mouse_buttons & SDL_BUTTON(SDL_BUTTON_LEFT)) != 0; // If a mouse press event came, always pass it as "mouse held this frame", so we don't miss click-release events that are shorter than 1 frame.
io.MouseDown[1] = g_MousePressed[1] || (mouse_buttons & SDL_BUTTON(SDL_BUTTON_RIGHT)) != 0;
io.MouseDown[2] = g_MousePressed[2] || (mouse_buttons & SDL_BUTTON(SDL_BUTTON_MIDDLE)) != 0;
g_MousePressed[0] = g_MousePressed[1] = g_MousePressed[2] = false;
#if SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE && !defined(__EMSCRIPTEN__) && !defined(__ANDROID__) && !(defined(__APPLE__) && TARGET_OS_IOS)
SDL_Window* focused_window = SDL_GetKeyboardFocus();
if (g_Window == focused_window)
{
if (g_MouseCanUseGlobalState)
{
// SDL_GetMouseState() gives mouse position seemingly based on the last window entered/focused(?)
// The creation of a new windows at runtime and SDL_CaptureMouse both seems to severely mess up with that, so we retrieve that position globally.
// Won't use this workaround on SDL backends that have no global mouse position, like Wayland or RPI
int wx, wy;
SDL_GetWindowPosition(focused_window, &wx, &wy);
SDL_GetGlobalMouseState(&mx, &my);
mx -= wx;
my -= wy;
}
io.MousePos = ImVec2((float)mx, (float)my);
}
// SDL_CaptureMouse() let the OS know e.g. that our imgui drag outside the SDL window boundaries shouldn't e.g. trigger the OS window resize cursor.
// The function is only supported from SDL 2.0.4 (released Jan 2016)
bool any_mouse_button_down = ImGui::IsAnyMouseDown();
SDL_CaptureMouse(any_mouse_button_down ? SDL_TRUE : SDL_FALSE);
#else
if (SDL_GetWindowFlags(g_Window) & SDL_WINDOW_INPUT_FOCUS)
io.MousePos = ImVec2((float)mx, (float)my);
#endif
}
static void ImGui_ImplSDL2_UpdateMouseCursor()
{
ImGuiIO& io = ImGui::GetIO();
if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange)
return;
ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor();
if (io.MouseDrawCursor || imgui_cursor == ImGuiMouseCursor_None)
{
// Hide OS mouse cursor if imgui is drawing it or if it wants no cursor
SDL_ShowCursor(SDL_FALSE);
}
else
{
// Show OS mouse cursor
SDL_SetCursor(g_MouseCursors[imgui_cursor] ? g_MouseCursors[imgui_cursor] : g_MouseCursors[ImGuiMouseCursor_Arrow]);
SDL_ShowCursor(SDL_TRUE);
}
}
static void ImGui_ImplSDL2_UpdateGamepads()
{
ImGuiIO& io = ImGui::GetIO();
memset(io.NavInputs, 0, sizeof(io.NavInputs));
if ((io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) == 0)
return;
// Get gamepad
SDL_GameController* game_controller = SDL_GameControllerOpen(0);
if (!game_controller)
{
io.BackendFlags &= ~ImGuiBackendFlags_HasGamepad;
return;
}
// Update gamepad inputs
#define MAP_BUTTON(NAV_NO, BUTTON_NO) { io.NavInputs[NAV_NO] = (SDL_GameControllerGetButton(game_controller, BUTTON_NO) != 0) ? 1.0f : 0.0f; }
#define MAP_ANALOG(NAV_NO, AXIS_NO, V0, V1) { float vn = (float)(SDL_GameControllerGetAxis(game_controller, AXIS_NO) - V0) / (float)(V1 - V0); if (vn > 1.0f) vn = 1.0f; if (vn > 0.0f && io.NavInputs[NAV_NO] < vn) io.NavInputs[NAV_NO] = vn; }
const int thumb_dead_zone = 8000; // SDL_gamecontroller.h suggests using this value.
MAP_BUTTON(ImGuiNavInput_Activate, SDL_CONTROLLER_BUTTON_A); // Cross / A
MAP_BUTTON(ImGuiNavInput_Cancel, SDL_CONTROLLER_BUTTON_B); // Circle / B
MAP_BUTTON(ImGuiNavInput_Menu, SDL_CONTROLLER_BUTTON_X); // Square / X
MAP_BUTTON(ImGuiNavInput_Input, SDL_CONTROLLER_BUTTON_Y); // Triangle / Y
MAP_BUTTON(ImGuiNavInput_DpadLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT); // D-Pad Left
MAP_BUTTON(ImGuiNavInput_DpadRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT); // D-Pad Right
MAP_BUTTON(ImGuiNavInput_DpadUp, SDL_CONTROLLER_BUTTON_DPAD_UP); // D-Pad Up
MAP_BUTTON(ImGuiNavInput_DpadDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN); // D-Pad Down
MAP_BUTTON(ImGuiNavInput_FocusPrev, SDL_CONTROLLER_BUTTON_LEFTSHOULDER); // L1 / LB
MAP_BUTTON(ImGuiNavInput_FocusNext, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER); // R1 / RB
MAP_BUTTON(ImGuiNavInput_TweakSlow, SDL_CONTROLLER_BUTTON_LEFTSHOULDER); // L1 / LB
MAP_BUTTON(ImGuiNavInput_TweakFast, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER); // R1 / RB
MAP_ANALOG(ImGuiNavInput_LStickLeft, SDL_CONTROLLER_AXIS_LEFTX, -thumb_dead_zone, -32768);
MAP_ANALOG(ImGuiNavInput_LStickRight, SDL_CONTROLLER_AXIS_LEFTX, +thumb_dead_zone, +32767);
MAP_ANALOG(ImGuiNavInput_LStickUp, SDL_CONTROLLER_AXIS_LEFTY, -thumb_dead_zone, -32767);
MAP_ANALOG(ImGuiNavInput_LStickDown, SDL_CONTROLLER_AXIS_LEFTY, +thumb_dead_zone, +32767);
io.BackendFlags |= ImGuiBackendFlags_HasGamepad;
#undef MAP_BUTTON
#undef MAP_ANALOG
}
void ImGui_ImplSDL2_NewFrame(SDL_Window* window)
{
ImGuiIO& io = ImGui::GetIO();
IM_ASSERT(io.Fonts->IsBuilt() && "Font atlas not built! It is generally built by the renderer backend. Missing call to renderer _NewFrame() function? e.g. ImGui_ImplOpenGL3_NewFrame().");
// Setup display size (every frame to accommodate for window resizing)
int w, h;
int display_w, display_h;
SDL_GetWindowSize(window, &w, &h);
if (SDL_GetWindowFlags(window) & SDL_WINDOW_MINIMIZED)
w = h = 0;
SDL_GL_GetDrawableSize(window, &display_w, &display_h);
io.DisplaySize = ImVec2((float)w, (float)h);
if (w > 0 && h > 0)
io.DisplayFramebufferScale = ImVec2((float)display_w / w, (float)display_h / h);
// Setup time step (we don't use SDL_GetTicks() because it is using millisecond resolution)
static Uint64 frequency = SDL_GetPerformanceFrequency();
Uint64 current_time = SDL_GetPerformanceCounter();
io.DeltaTime = g_Time > 0 ? (float)((double)(current_time - g_Time) / frequency) : (float)(1.0f / 60.0f);
g_Time = current_time;
ImGui_ImplSDL2_UpdateMousePosAndButtons();
ImGui_ImplSDL2_UpdateMouseCursor();
// Update game controllers (if enabled and available)
ImGui_ImplSDL2_UpdateGamepads();
}

677
vendor/imgui/src/imgui_sdl.cpp vendored Normal file
View file

@ -0,0 +1,677 @@
#include "imgui_sdl.h"
#include "SDL.h"
#include "imgui.h"
#include <map>
#include <list>
#include <cmath>
#include <array>
#include <vector>
#include <memory>
#include <iostream>
#include <algorithm>
#include <functional>
#include <unordered_map>
namespace
{
struct Device* CurrentDevice = nullptr;
namespace TupleHash
{
template <typename T> struct Hash
{
std::size_t operator()(const T& value) const
{
return std::hash<T>()(value);
}
};
template <typename T> void CombineHash(std::size_t& seed, const T& value)
{
seed ^= TupleHash::Hash<T>()(value) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}
template <typename Tuple, std::size_t Index = std::tuple_size<Tuple>::value - 1> struct Hasher
{
static void Hash(std::size_t& seed, const Tuple& tuple)
{
Hasher<Tuple, Index - 1>::Hash(seed, tuple);
CombineHash(seed, std::get<Index>(tuple));
}
};
template <typename Tuple> struct Hasher<Tuple, 0>
{
static void Hash(std::size_t& seed, const Tuple& tuple)
{
CombineHash(seed, std::get<0>(tuple));
}
};
template <typename... T> struct Hash<std::tuple<T...>>
{
std::size_t operator()(const std::tuple<T...>& value) const
{
std::size_t seed = 0;
Hasher<std::tuple<T...>>::Hash(seed, value);
return seed;
}
};
}
template <typename Key, typename Value, std::size_t Size> class LRUCache
{
public:
bool Contains(const Key& key) const
{
return Container.find(key) != Container.end();
}
const Value& At(const Key& key)
{
assert(Contains(key));
const auto location = Container.find(key);
Order.splice(Order.begin(), Order, location->second);
return location->second->second;
}
void Insert(const Key& key, Value value)
{
const auto existingLocation = Container.find(key);
if (existingLocation != Container.end())
{
Order.erase(existingLocation->second);
Container.erase(existingLocation);
}
Order.push_front(std::make_pair(key, std::move(value)));
Container.insert(std::make_pair(key, Order.begin()));
Clean();
}
private:
void Clean()
{
while (Container.size() > Size)
{
auto last = Order.end();
last--;
Container.erase(last->first);
Order.pop_back();
}
}
std::list<std::pair<Key, Value>> Order;
std::unordered_map<Key, decltype(Order.begin()), TupleHash::Hash<Key>> Container;
};
struct Color
{
const float R, G, B, A;
explicit Color(uint32_t color)
: R(((color >> 0) & 0xff) / 255.0f), G(((color >> 8) & 0xff) / 255.0f), B(((color >> 16) & 0xff) / 255.0f), A(((color >> 24) & 0xff) / 255.0f) { }
Color(float r, float g, float b, float a) : R(r), G(g), B(b), A(a) { }
Color operator*(const Color& c) const { return Color(R * c.R, G * c.G, B * c.B, A * c.A); }
Color operator*(float v) const { return Color(R * v, G * v, B * v, A * v); }
Color operator+(const Color& c) const { return Color(R + c.R, G + c.G, B + c.B, A + c.A); }
uint32_t ToInt() const
{
return ((static_cast<int>(R * 255) & 0xff) << 0)
| ((static_cast<int>(G * 255) & 0xff) << 8)
| ((static_cast<int>(B * 255) & 0xff) << 16)
| ((static_cast<int>(A * 255) & 0xff) << 24);
}
void UseAsDrawColor(SDL_Renderer* renderer) const
{
SDL_SetRenderDrawColor(renderer,
static_cast<uint8_t>(R * 255),
static_cast<uint8_t>(G * 255),
static_cast<uint8_t>(B * 255),
static_cast<uint8_t>(A * 255));
}
};
struct Device
{
SDL_Renderer* Renderer;
struct ClipRect
{
int X, Y, Width, Height;
} Clip;
struct TriangleCacheItem
{
SDL_Texture* Texture = nullptr;
int Width = 0, Height = 0;
~TriangleCacheItem() { if (Texture) SDL_DestroyTexture(Texture); }
};
// You can tweak these to values that you find that work the best.
static constexpr std::size_t UniformColorTriangleCacheSize = 512;
static constexpr std::size_t GenericTriangleCacheSize = 64;
// Uniform color is identified by its color and the coordinates of the edges.
using UniformColorTriangleKey = std::tuple<uint32_t, int, int, int, int, int, int>;
// The generic triangle cache unfortunately has to be basically a full representation of the triangle.
// This includes the (offset) vertex positions, texture coordinates and vertex colors.
using GenericTriangleVertexKey = std::tuple<int, int, double, double, uint32_t>;
using GenericTriangleKey = std::tuple<GenericTriangleVertexKey, GenericTriangleVertexKey, GenericTriangleVertexKey>;
LRUCache<UniformColorTriangleKey, std::unique_ptr<TriangleCacheItem>, UniformColorTriangleCacheSize> UniformColorTriangleCache;
LRUCache<GenericTriangleKey, std::unique_ptr<TriangleCacheItem>, GenericTriangleCacheSize> GenericTriangleCache;
Device(SDL_Renderer* renderer) : Renderer(renderer) { }
void SetClipRect(const ClipRect& rect)
{
Clip = rect;
const SDL_Rect clip = { rect.X, rect.Y, rect.Width, rect.Height };
SDL_RenderSetClipRect(Renderer, &clip);
}
void EnableClip() { SetClipRect(Clip); }
void DisableClip() { SDL_RenderSetClipRect(Renderer, nullptr); }
void SetAt(int x, int y, const Color& color)
{
color.UseAsDrawColor(Renderer);
SDL_RenderDrawPoint(Renderer, x, y);
}
SDL_Texture* MakeTexture(int width, int height)
{
SDL_Texture* texture = SDL_CreateTexture(Renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_TARGET, width, height);
SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND);
return texture;
}
void UseAsRenderTarget(SDL_Texture* texture)
{
SDL_SetRenderTarget(Renderer, texture);
if (texture)
{
SDL_SetRenderDrawColor(Renderer, 0, 0, 0, 0);
SDL_RenderClear(Renderer);
}
}
};
struct Texture
{
SDL_Surface* Surface;
SDL_Texture* Source;
~Texture()
{
SDL_FreeSurface(Surface);
SDL_DestroyTexture(Source);
}
Color Sample(float u, float v) const
{
const int x = static_cast<int>(std::round(u * (Surface->w - 1) + 0.5f));
const int y = static_cast<int>(std::round(v * (Surface->h - 1) + 0.5f));
const int location = y * Surface->w + x;
assert(location < Surface->w* Surface->h);
return Color(static_cast<uint32_t*>(Surface->pixels)[location]);
}
};
template <typename T> class InterpolatedFactorEquation
{
public:
InterpolatedFactorEquation(const T& value0, const T& value1, const T& value2, const ImVec2& v0, const ImVec2& v1, const ImVec2& v2)
: Value0(value0), Value1(value1), Value2(value2), V0(v0), V1(v1), V2(v2),
Divisor((V1.y - V2.y)* (V0.x - V2.x) + (V2.x - V1.x) * (V0.y - V2.y)) { }
T Evaluate(float x, float y) const
{
const float w1 = ((V1.y - V2.y) * (x - V2.x) + (V2.x - V1.x) * (y - V2.y)) / Divisor;
const float w2 = ((V2.y - V0.y) * (x - V2.x) + (V0.x - V2.x) * (y - V2.y)) / Divisor;
const float w3 = 1.0f - w1 - w2;
return static_cast<T>((Value0 * w1) + (Value1 * w2) + (Value2 * w3));
}
private:
const T Value0;
const T Value1;
const T Value2;
const ImVec2& V0;
const ImVec2& V1;
const ImVec2& V2;
const float Divisor;
};
struct Rect
{
float MinX, MinY, MaxX, MaxY;
float MinU, MinV, MaxU, MaxV;
bool IsOnExtreme(const ImVec2& point) const
{
return (point.x == MinX || point.x == MaxX) && (point.y == MinY || point.y == MaxY);
}
bool UsesOnlyColor() const
{
const ImVec2& whitePixel = ImGui::GetIO().Fonts->TexUvWhitePixel;
return MinU == MaxU && MinU == whitePixel.x && MinV == MaxV && MaxV == whitePixel.y;
}
static Rect CalculateBoundingBox(const ImDrawVert& v0, const ImDrawVert& v1, const ImDrawVert& v2)
{
return Rect{
std::min({ v0.pos.x, v1.pos.x, v2.pos.x }),
std::min({ v0.pos.y, v1.pos.y, v2.pos.y }),
std::max({ v0.pos.x, v1.pos.x, v2.pos.x }),
std::max({ v0.pos.y, v1.pos.y, v2.pos.y }),
std::min({ v0.uv.x, v1.uv.x, v2.uv.x }),
std::min({ v0.uv.y, v1.uv.y, v2.uv.y }),
std::max({ v0.uv.x, v1.uv.x, v2.uv.x }),
std::max({ v0.uv.y, v1.uv.y, v2.uv.y })
};
}
};
struct FixedPointTriangleRenderInfo
{
int X1, X2, X3, Y1, Y2, Y3;
int MinX, MaxX, MinY, MaxY;
static FixedPointTriangleRenderInfo CalculateFixedPointTriangleInfo(const ImVec2& v1, const ImVec2& v2, const ImVec2& v3)
{
static constexpr float scale = 16.0f;
const int x1 = static_cast<int>(std::round(v1.x * scale));
const int x2 = static_cast<int>(std::round(v2.x * scale));
const int x3 = static_cast<int>(std::round(v3.x * scale));
const int y1 = static_cast<int>(std::round(v1.y * scale));
const int y2 = static_cast<int>(std::round(v2.y * scale));
const int y3 = static_cast<int>(std::round(v3.y * scale));
int minX = (std::min({ x1, x2, x3 }) + 0xF) >> 4;
int maxX = (std::max({ x1, x2, x3 }) + 0xF) >> 4;
int minY = (std::min({ y1, y2, y3 }) + 0xF) >> 4;
int maxY = (std::max({ y1, y2, y3 }) + 0xF) >> 4;
return FixedPointTriangleRenderInfo{ x1, x2, x3, y1, y2, y3, minX, maxX, minY, maxY };
}
};
void DrawTriangleWithColorFunction(const FixedPointTriangleRenderInfo& renderInfo, const std::function<Color(float x, float y)>& colorFunction, Device::TriangleCacheItem* cacheItem)
{
// Implementation source: https://web.archive.org/web/20171128164608/http://forum.devmaster.net/t/advanced-rasterization/6145.
// This is a fixed point implementation that rounds to top-left.
const int deltaX12 = renderInfo.X1 - renderInfo.X2;
const int deltaX23 = renderInfo.X2 - renderInfo.X3;
const int deltaX31 = renderInfo.X3 - renderInfo.X1;
const int deltaY12 = renderInfo.Y1 - renderInfo.Y2;
const int deltaY23 = renderInfo.Y2 - renderInfo.Y3;
const int deltaY31 = renderInfo.Y3 - renderInfo.Y1;
const int fixedDeltaX12 = deltaX12 << 4;
const int fixedDeltaX23 = deltaX23 << 4;
const int fixedDeltaX31 = deltaX31 << 4;
const int fixedDeltaY12 = deltaY12 << 4;
const int fixedDeltaY23 = deltaY23 << 4;
const int fixedDeltaY31 = deltaY31 << 4;
const int width = renderInfo.MaxX - renderInfo.MinX;
const int height = renderInfo.MaxY - renderInfo.MinY;
if (width == 0 || height == 0) return;
int c1 = deltaY12 * renderInfo.X1 - deltaX12 * renderInfo.Y1;
int c2 = deltaY23 * renderInfo.X2 - deltaX23 * renderInfo.Y2;
int c3 = deltaY31 * renderInfo.X3 - deltaX31 * renderInfo.Y3;
if (deltaY12 < 0 || (deltaY12 == 0 && deltaX12 > 0)) c1++;
if (deltaY23 < 0 || (deltaY23 == 0 && deltaX23 > 0)) c2++;
if (deltaY31 < 0 || (deltaY31 == 0 && deltaX31 > 0)) c3++;
int edgeStart1 = c1 + deltaX12 * (renderInfo.MinY << 4) - deltaY12 * (renderInfo.MinX << 4);
int edgeStart2 = c2 + deltaX23 * (renderInfo.MinY << 4) - deltaY23 * (renderInfo.MinX << 4);
int edgeStart3 = c3 + deltaX31 * (renderInfo.MinY << 4) - deltaY31 * (renderInfo.MinX << 4);
SDL_Texture* cache = CurrentDevice->MakeTexture(width, height);
CurrentDevice->DisableClip();
CurrentDevice->UseAsRenderTarget(cache);
for (int y = renderInfo.MinY; y < renderInfo.MaxY; y++)
{
int edge1 = edgeStart1;
int edge2 = edgeStart2;
int edge3 = edgeStart3;
for (int x = renderInfo.MinX; x < renderInfo.MaxX; x++)
{
if (edge1 > 0 && edge2 > 0 && edge3 > 0)
{
CurrentDevice->SetAt(x - renderInfo.MinX, y - renderInfo.MinY, colorFunction(x + 0.5f, y + 0.5f));
}
edge1 -= fixedDeltaY12;
edge2 -= fixedDeltaY23;
edge3 -= fixedDeltaY31;
}
edgeStart1 += fixedDeltaX12;
edgeStart2 += fixedDeltaX23;
edgeStart3 += fixedDeltaX31;
}
CurrentDevice->UseAsRenderTarget(nullptr);
CurrentDevice->EnableClip();
cacheItem->Texture = cache;
cacheItem->Width = width;
cacheItem->Height = height;
}
void DrawCachedTriangle(const Device::TriangleCacheItem& triangle, const FixedPointTriangleRenderInfo& renderInfo)
{
const SDL_Rect destination = { renderInfo.MinX, renderInfo.MinY, triangle.Width, triangle.Height };
SDL_RenderCopy(CurrentDevice->Renderer, triangle.Texture, nullptr, &destination);
}
void DrawTriangle(const ImDrawVert& v1, const ImDrawVert& v2, const ImDrawVert& v3, const Texture* texture)
{
// The naming inconsistency in the parameters is intentional. The fixed point algorithm wants the vertices in a counter clockwise order.
const auto& renderInfo = FixedPointTriangleRenderInfo::CalculateFixedPointTriangleInfo(v3.pos, v2.pos, v1.pos);
// First we check if there is a cached version of this triangle already waiting for us. If so, we can just do a super fast texture copy.
const auto key = std::make_tuple(
std::make_tuple(static_cast<int>(std::round(v1.pos.x)) - renderInfo.MinX, static_cast<int>(std::round(v1.pos.y)) - renderInfo.MinY, v1.uv.x, v1.uv.y, v1.col),
std::make_tuple(static_cast<int>(std::round(v2.pos.x)) - renderInfo.MinX, static_cast<int>(std::round(v2.pos.y)) - renderInfo.MinY, v2.uv.x, v2.uv.y, v2.col),
std::make_tuple(static_cast<int>(std::round(v3.pos.x)) - renderInfo.MinX, static_cast<int>(std::round(v3.pos.y)) - renderInfo.MinY, v3.uv.x, v3.uv.y, v3.col));
if (CurrentDevice->GenericTriangleCache.Contains(key))
{
const auto& cached = CurrentDevice->GenericTriangleCache.At(key);
DrawCachedTriangle(*cached, renderInfo);
return;
}
const InterpolatedFactorEquation<float> textureU(v1.uv.x, v2.uv.x, v3.uv.x, v1.pos, v2.pos, v3.pos);
const InterpolatedFactorEquation<float> textureV(v1.uv.y, v2.uv.y, v3.uv.y, v1.pos, v2.pos, v3.pos);
const InterpolatedFactorEquation<Color> shadeColor(Color(v1.col), Color(v2.col), Color(v3.col), v1.pos, v2.pos, v3.pos);
auto cached = std::make_unique<Device::TriangleCacheItem>();
DrawTriangleWithColorFunction(renderInfo, [&](float x, float y) {
const float u = textureU.Evaluate(x, y);
const float v = textureV.Evaluate(x, y);
const Color sampled = texture->Sample(u, v);
const Color shade = shadeColor.Evaluate(x, y);
return sampled * shade;
}, cached.get());
if (!cached->Texture) return;
const SDL_Rect destination = { renderInfo.MinX, renderInfo.MinY, cached->Width, cached->Height };
SDL_RenderCopy(CurrentDevice->Renderer, cached->Texture, nullptr, &destination);
CurrentDevice->GenericTriangleCache.Insert(key, std::move(cached));
}
void DrawUniformColorTriangle(const ImDrawVert& v1, const ImDrawVert& v2, const ImDrawVert& v3)
{
const Color color(v1.col);
// The naming inconsistency in the parameters is intentional. The fixed point algorithm wants the vertices in a counter clockwise order.
const auto& renderInfo = FixedPointTriangleRenderInfo::CalculateFixedPointTriangleInfo(v3.pos, v2.pos, v1.pos);
const auto key = std::make_tuple(v1.col,
static_cast<int>(std::round(v1.pos.x)) - renderInfo.MinX, static_cast<int>(std::round(v1.pos.y)) - renderInfo.MinY,
static_cast<int>(std::round(v2.pos.x)) - renderInfo.MinX, static_cast<int>(std::round(v2.pos.y)) - renderInfo.MinY,
static_cast<int>(std::round(v3.pos.x)) - renderInfo.MinX, static_cast<int>(std::round(v3.pos.y)) - renderInfo.MinY);
if (CurrentDevice->UniformColorTriangleCache.Contains(key))
{
const auto& cached = CurrentDevice->UniformColorTriangleCache.At(key);
DrawCachedTriangle(*cached, renderInfo);
return;
}
auto cached = std::make_unique<Device::TriangleCacheItem>();
DrawTriangleWithColorFunction(renderInfo, [&color](float, float) { return color; }, cached.get());
if (!cached->Texture) return;
const SDL_Rect destination = { renderInfo.MinX, renderInfo.MinY, cached->Width, cached->Height };
SDL_RenderCopy(CurrentDevice->Renderer, cached->Texture, nullptr, &destination);
CurrentDevice->UniformColorTriangleCache.Insert(key, std::move(cached));
}
void DrawRectangle(const Rect& bounding, SDL_Texture* texture, int textureWidth, int textureHeight, const Color& color, bool doHorizontalFlip, bool doVerticalFlip)
{
// We are safe to assume uniform color here, because the caller checks it and and uses the triangle renderer to render those.
const SDL_Rect destination = {
static_cast<int>(bounding.MinX),
static_cast<int>(bounding.MinY),
static_cast<int>(bounding.MaxX - bounding.MinX),
static_cast<int>(bounding.MaxY - bounding.MinY)
};
// If the area isn't textured, we can just draw a rectangle with the correct color.
if (bounding.UsesOnlyColor())
{
color.UseAsDrawColor(CurrentDevice->Renderer);
SDL_RenderFillRect(CurrentDevice->Renderer, &destination);
}
else
{
// We can now just calculate the correct source rectangle and draw it.
const SDL_Rect source = {
static_cast<int>(bounding.MinU * textureWidth),
static_cast<int>(bounding.MinV * textureHeight),
static_cast<int>((bounding.MaxU - bounding.MinU) * textureWidth),
static_cast<int>((bounding.MaxV - bounding.MinV) * textureHeight)
};
const SDL_RendererFlip flip = static_cast<SDL_RendererFlip>((doHorizontalFlip ? SDL_FLIP_HORIZONTAL : 0) | (doVerticalFlip ? SDL_FLIP_VERTICAL : 0));
SDL_SetTextureColorMod(texture, static_cast<uint8_t>(color.R * 255), static_cast<uint8_t>(color.G * 255), static_cast<uint8_t>(color.B * 255));
SDL_RenderCopyEx(CurrentDevice->Renderer, texture, &source, &destination, 0.0, nullptr, flip);
}
}
void DrawRectangle(const Rect& bounding, const Texture* texture, const Color& color, bool doHorizontalFlip, bool doVerticalFlip)
{
DrawRectangle(bounding, texture->Source, texture->Surface->w, texture->Surface->h, color, doHorizontalFlip, doVerticalFlip);
}
void DrawRectangle(const Rect& bounding, SDL_Texture* texture, const Color& color, bool doHorizontalFlip, bool doVerticalFlip)
{
int width, height;
SDL_QueryTexture(texture, nullptr, nullptr, &width, &height);
DrawRectangle(bounding, texture, width, height, color, doHorizontalFlip, doVerticalFlip);
}
}
namespace ImGuiSDL
{
void Initialize(SDL_Renderer* renderer, int windowWidth, int windowHeight)
{
ImGuiIO& io = ImGui::GetIO();
io.DisplaySize.x = static_cast<float>(windowWidth);
io.DisplaySize.y = static_cast<float>(windowHeight);
ImGui::GetStyle().WindowRounding = 0.0f;
ImGui::GetStyle().AntiAliasedFill = false;
ImGui::GetStyle().AntiAliasedLines = false;
// Loads the font texture.
unsigned char* pixels;
int width, height;
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
static constexpr uint32_t rmask = 0x000000ff, gmask = 0x0000ff00, bmask = 0x00ff0000, amask = 0xff000000;
SDL_Surface* surface = SDL_CreateRGBSurfaceFrom(pixels, width, height, 32, 4 * width, rmask, gmask, bmask, amask);
Texture* texture = new Texture();
texture->Surface = surface;
texture->Source = SDL_CreateTextureFromSurface(renderer, surface);
io.Fonts->TexID = (void*)texture;
CurrentDevice = new Device(renderer);
}
void Deinitialize()
{
// Frees up the memory of the font texture.
ImGuiIO& io = ImGui::GetIO();
Texture* texture = static_cast<Texture*>(io.Fonts->TexID);
delete texture;
delete CurrentDevice;
}
void Render(ImDrawData* drawData)
{
SDL_BlendMode blendMode;
SDL_GetRenderDrawBlendMode(CurrentDevice->Renderer, &blendMode);
SDL_SetRenderDrawBlendMode(CurrentDevice->Renderer, SDL_BLENDMODE_BLEND);
Uint8 initialR, initialG, initialB, initialA;
SDL_GetRenderDrawColor(CurrentDevice->Renderer, &initialR, &initialG, &initialB, &initialA);
SDL_bool initialClipEnabled = SDL_RenderIsClipEnabled(CurrentDevice->Renderer);
SDL_Rect initialClipRect;
SDL_RenderGetClipRect(CurrentDevice->Renderer, &initialClipRect);
SDL_Texture* initialRenderTarget = SDL_GetRenderTarget(CurrentDevice->Renderer);
ImGuiIO& io = ImGui::GetIO();
for (int n = 0; n < drawData->CmdListsCount; n++)
{
auto commandList = drawData->CmdLists[n];
auto vertexBuffer = commandList->VtxBuffer;
auto indexBuffer = commandList->IdxBuffer.Data;
for (int cmd_i = 0; cmd_i < commandList->CmdBuffer.Size; cmd_i++)
{
const ImDrawCmd* drawCommand = &commandList->CmdBuffer[cmd_i];
const Device::ClipRect clipRect = {
static_cast<int>(drawCommand->ClipRect.x),
static_cast<int>(drawCommand->ClipRect.y),
static_cast<int>(drawCommand->ClipRect.z - drawCommand->ClipRect.x),
static_cast<int>(drawCommand->ClipRect.w - drawCommand->ClipRect.y)
};
CurrentDevice->SetClipRect(clipRect);
if (drawCommand->UserCallback)
{
drawCommand->UserCallback(commandList, drawCommand);
}
else
{
const bool isWrappedTexture = drawCommand->TextureId == io.Fonts->TexID;
// Loops over triangles.
for (unsigned int i = 0; i + 3 <= drawCommand->ElemCount; i += 3)
{
const ImDrawVert& v0 = vertexBuffer[indexBuffer[i + 0]];
const ImDrawVert& v1 = vertexBuffer[indexBuffer[i + 1]];
const ImDrawVert& v2 = vertexBuffer[indexBuffer[i + 2]];
const Rect& bounding = Rect::CalculateBoundingBox(v0, v1, v2);
const bool isTriangleUniformColor = v0.col == v1.col && v1.col == v2.col;
const bool doesTriangleUseOnlyColor = bounding.UsesOnlyColor();
// Actually, since we render a whole bunch of rectangles, we try to first detect those, and render them more efficiently.
// How are rectangles detected? It's actually pretty simple: If all 6 vertices lie on the extremes of the bounding box,
// it's a rectangle.
if (i + 6 <= drawCommand->ElemCount)
{
const ImDrawVert& v3 = vertexBuffer[indexBuffer[i + 3]];
const ImDrawVert& v4 = vertexBuffer[indexBuffer[i + 4]];
const ImDrawVert& v5 = vertexBuffer[indexBuffer[i + 5]];
const bool isUniformColor = isTriangleUniformColor && v2.col == v3.col && v3.col == v4.col && v4.col == v5.col;
if (isUniformColor
&& bounding.IsOnExtreme(v0.pos)
&& bounding.IsOnExtreme(v1.pos)
&& bounding.IsOnExtreme(v2.pos)
&& bounding.IsOnExtreme(v3.pos)
&& bounding.IsOnExtreme(v4.pos)
&& bounding.IsOnExtreme(v5.pos))
{
// ImGui gives the triangles in a nice order: the first vertex happens to be the topleft corner of our rectangle.
// We need to check for the orientation of the texture, as I believe in theory ImGui could feed us a flipped texture,
// so that the larger texture coordinates are at topleft instead of bottomright.
// We don't consider equal texture coordinates to require a flip, as then the rectangle is mostlikely simply a colored rectangle.
const bool doHorizontalFlip = v2.uv.x < v0.uv.x;
const bool doVerticalFlip = v2.uv.x < v0.uv.x;
if (isWrappedTexture)
{
DrawRectangle(bounding, static_cast<const Texture*>(drawCommand->TextureId), Color(v0.col), doHorizontalFlip, doVerticalFlip);
}
else
{
DrawRectangle(bounding, static_cast<SDL_Texture*>(drawCommand->TextureId), Color(v0.col), doHorizontalFlip, doVerticalFlip);
}
i += 3; // Additional increment to account for the extra 3 vertices we consumed.
continue;
}
}
if (isTriangleUniformColor && doesTriangleUseOnlyColor)
{
DrawUniformColorTriangle(v0, v1, v2);
}
else
{
// Currently we assume that any non rectangular texture samples the font texture. Dunno if that's what actually happens, but it seems to work.
assert(isWrappedTexture);
DrawTriangle(v0, v1, v2, static_cast<const Texture*>(drawCommand->TextureId));
}
}
}
indexBuffer += drawCommand->ElemCount;
}
}
CurrentDevice->DisableClip();
SDL_SetRenderTarget(CurrentDevice->Renderer, initialRenderTarget);
SDL_RenderSetClipRect(CurrentDevice->Renderer, initialClipEnabled ? &initialClipRect : nullptr);
SDL_SetRenderDrawColor(CurrentDevice->Renderer,
initialR, initialG, initialB, initialA);
SDL_SetRenderDrawBlendMode(CurrentDevice->Renderer, blendMode);
}
}

4028
vendor/imgui/src/imgui_tables.cpp vendored Normal file

File diff suppressed because it is too large Load diff

8056
vendor/imgui/src/imgui_widgets.cpp vendored Normal file

File diff suppressed because it is too large Load diff