mirror of https://github.com/bsnes-emu/bsnes.git
Update to v101r07 release.
byuu says: Added VDP sprite rendering. Can't get any games far enough in to see if it actually works. So in other words, it doesn't work at all and is 100% completely broken. Also added 68K exceptions and interrupts. So far only the VDP interrupt is present. It definitely seems to be firing in commercial games, so that's promising. But the implementation is almost certainly completely wrong. There is fuck all of nothing for documentation on how interrupts actually work. I had to find out the interrupt vector numbers from reading the comments from the Sonic the Hedgehog disassembly. I have literally no fucking clue what I0-I2 (3-bit integer priority value in the status register) is supposed to do. I know that Vblank=6, Hblank=4, Ext(gamepad)=2. I know that at reset, SR.I=7. I don't know if I'm supposed to block interrupts when I is >, >=, <, <= to the interrupt level. I don't know what level CPU exceptions are supposed to be. Also implemented VDP regular DMA. No idea if it works correctly since none of the commercial games run far enough to use it. So again, it's horribly broken for usre. Also improved VDP fill mode. But I don't understand how it takes byte-lengths when the bus is 16-bit. The transfer times indicate it's actually transferring at the same speed as the 68K->VDP copy, strongly suggesting it's actually doing 16-bit transfers at a time. In which case, what happens when you set an odd transfer length? Also, both DMA modes can now target VRAM, VSRAM, CRAM. Supposedly there's all kinds of weird shit going on when you target VSRAM, CRAM with VDP fill/copy modes, but whatever. Get to that later. Also implemented a very lazy preliminary wait mechanism to to stall out a processor while another processor exerts control over the bus. This one's going to be a major work in progress. For one, it totally breaks the model I use to do save states with libco. For another, I don't know if a 68K->VDP DMA instantly locks the CPU, or if it the CPU could actually keep running if it was executing out of RAM when it started the DMA transfer from ROM (eg it's a bus busy stall, not a hard chip stall.) That'll greatly change how I handle the waiting. Also, the OSS driver now supports Audio::Latency. Sound should be even lower latency now. On FreeBSD when set to 0ms, it's absolutely incredible. Cannot detect latency whatsoever. The Mario jump sound seems to happen at the very instant I hear my cherry blue keyswitch activate.
This commit is contained in:
parent
427bac3011
commit
ffd150735b
|
@ -12,7 +12,7 @@ using namespace nall;
|
|||
|
||||
namespace Emulator {
|
||||
static const string Name = "higan";
|
||||
static const string Version = "101.06";
|
||||
static const string Version = "101.07";
|
||||
static const string Author = "byuu";
|
||||
static const string License = "GPLv3";
|
||||
static const string Website = "http://byuu.org/";
|
||||
|
|
|
@ -21,17 +21,51 @@ auto CPU::main() -> void {
|
|||
fp.print(pad(disassemble(r.pc), -60, ' '), " ", disassembleRegisters().replace("\n", " "), "\n");
|
||||
#endif
|
||||
|
||||
if(state.interruptPending) {
|
||||
if(state.interruptPending.bit((uint)Interrupt::HorizontalBlank)) {
|
||||
state.interruptPending.bit((uint)Interrupt::HorizontalBlank) = 0;
|
||||
r.i = 4;
|
||||
return exception(Exception::Interrupt, Vector::HorizontalBlank);
|
||||
}
|
||||
|
||||
if(state.interruptPending.bit((uint)Interrupt::VerticalBlank)) {
|
||||
state.interruptPending.bit((uint)Interrupt::VerticalBlank) = 0;
|
||||
r.i = 6;
|
||||
return exception(Exception::Interrupt, Vector::VerticalBlank);
|
||||
}
|
||||
}
|
||||
|
||||
instruction();
|
||||
}
|
||||
|
||||
auto CPU::step(uint clocks) -> void {
|
||||
while(wait) {
|
||||
Thread::step(1);
|
||||
synchronize();
|
||||
}
|
||||
|
||||
Thread::step(clocks);
|
||||
synchronize();
|
||||
}
|
||||
|
||||
auto CPU::synchronize() -> void {
|
||||
synchronize(apu);
|
||||
synchronize(vdp);
|
||||
synchronize(psg);
|
||||
synchronize(ym2612);
|
||||
}
|
||||
|
||||
auto CPU::raise(Interrupt interrupt) -> void {
|
||||
if(!state.interruptLine.bit((uint)interrupt)) {
|
||||
state.interruptLine.bit((uint)interrupt) = 1;
|
||||
state.interruptPending.bit((uint)interrupt) = 1;
|
||||
}
|
||||
}
|
||||
|
||||
auto CPU::lower(Interrupt interrupt) -> void {
|
||||
state.interruptLine.bit((uint)interrupt) = 0;
|
||||
}
|
||||
|
||||
auto CPU::power() -> void {
|
||||
M68K::power();
|
||||
|
||||
|
@ -41,7 +75,8 @@ auto CPU::power() -> void {
|
|||
auto CPU::reset() -> void {
|
||||
M68K::reset();
|
||||
create(CPU::Enter, system.colorburst() * 15.0 / 7.0);
|
||||
cycles = 0;
|
||||
|
||||
memory::fill(&state, sizeof(State));
|
||||
}
|
||||
|
||||
auto CPU::readByte(uint24 addr) -> uint8 {
|
||||
|
|
|
@ -1,10 +1,21 @@
|
|||
//Motorola 68000
|
||||
|
||||
struct CPU : Processor::M68K, Thread {
|
||||
enum class Interrupt : uint {
|
||||
HorizontalBlank,
|
||||
VerticalBlank,
|
||||
};
|
||||
|
||||
using Thread::synchronize;
|
||||
|
||||
static auto Enter() -> void;
|
||||
auto boot() -> void;
|
||||
auto main() -> void;
|
||||
auto step(uint clocks) -> void override;
|
||||
auto synchronize() -> void;
|
||||
|
||||
auto raise(Interrupt) -> void;
|
||||
auto lower(Interrupt) -> void;
|
||||
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
|
@ -17,7 +28,10 @@ struct CPU : Processor::M68K, Thread {
|
|||
private:
|
||||
uint8 ram[64 * 1024];
|
||||
|
||||
uint cycles = 0;
|
||||
struct State {
|
||||
uint32 interruptLine;
|
||||
uint32 interruptPending;
|
||||
} state;
|
||||
};
|
||||
|
||||
extern CPU cpu;
|
||||
|
|
|
@ -15,15 +15,24 @@ namespace MegaDrive {
|
|||
using Scheduler = Emulator::Scheduler;
|
||||
extern Scheduler scheduler;
|
||||
|
||||
struct Wait {
|
||||
enum : uint {
|
||||
VDP_DMA = 1 << 0,
|
||||
};
|
||||
};
|
||||
|
||||
struct Thread : Emulator::Thread {
|
||||
auto create(auto (*entrypoint)() -> void, double frequency) -> void {
|
||||
Emulator::Thread::create(entrypoint, frequency);
|
||||
scheduler.append(*this);
|
||||
wait = 0;
|
||||
}
|
||||
|
||||
inline auto synchronize(Thread& thread) -> void {
|
||||
if(clock() >= thread.clock()) scheduler.resume(thread);
|
||||
}
|
||||
|
||||
uint wait = 0;
|
||||
};
|
||||
|
||||
#include <md/cpu/cpu.hpp>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
auto VDP::Background::scanline() -> void {
|
||||
auto VDP::Background::scanline(uint y) -> void {
|
||||
}
|
||||
|
||||
auto VDP::Background::run(uint x, uint y) -> void {
|
||||
|
@ -17,9 +17,9 @@ auto VDP::Background::run(uint x, uint y) -> void {
|
|||
tileAddress += pixelY << 1 | pixelX >> 2;
|
||||
|
||||
uint16 tileData = vdp.vram[tileAddress];
|
||||
uint4 palette = tileData >> (((pixelX & 3) ^ 3) << 2);
|
||||
if(palette) {
|
||||
output.color = tileAttributes.bits(13,14) << 4 | palette;
|
||||
uint4 color = tileData >> (((pixelX & 3) ^ 3) << 2);
|
||||
if(color) {
|
||||
output.color = tileAttributes.bits(13,14) << 4 | color;
|
||||
output.priority = tileAttributes.bit(15);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,36 @@
|
|||
auto VDP::dmaRun() -> void {
|
||||
if(!io.dmaEnable) return;
|
||||
if(!io.dmaActive) return;
|
||||
if(io.command.bits(4,5) != 2) return;
|
||||
|
||||
if(io.dmaMode == 2) return dmaFillVRAM();
|
||||
if(io.dmaMode <= 1) return dmaLoad();
|
||||
if(io.dmaMode == 2) return dmaFill();
|
||||
if(io.dmaMode == 3) return dmaCopy();
|
||||
}
|
||||
|
||||
auto VDP::dmaFillVRAM() -> void {
|
||||
auto address = io.address.bits(1,15);
|
||||
auto data = io.dmaFillWord;
|
||||
if(io.address.bit(0)) data = data >> 8 | data << 8;
|
||||
vram[address] = data;
|
||||
io.address += io.dataIncrement;
|
||||
auto VDP::dmaLoad() -> void {
|
||||
cpu.wait |= Wait::VDP_DMA;
|
||||
|
||||
auto data = cpu.readWord(io.dmaSource);
|
||||
writeDataPort(data);
|
||||
|
||||
io.dmaSource.bits(0,15) += 2;
|
||||
if(--io.dmaLength == 0) {
|
||||
io.dmaActive = false;
|
||||
io.command.bit(5) = 0;
|
||||
cpu.wait &=~ Wait::VDP_DMA;
|
||||
}
|
||||
}
|
||||
|
||||
auto VDP::dmaFill() -> void {
|
||||
if(io.dmaFillWait) return;
|
||||
|
||||
auto data = io.dmaFillWord;
|
||||
writeDataPort(data);
|
||||
|
||||
io.dmaSource.bits(0,15) += 2;
|
||||
if(--io.dmaLength == 0 || --io.dmaLength == 0) {
|
||||
io.command.bit(5) = 0;
|
||||
}
|
||||
}
|
||||
|
||||
auto VDP::dmaCopy() -> void {
|
||||
}
|
||||
|
|
|
@ -72,8 +72,7 @@ auto VDP::writeDataPort(uint16 data) -> void {
|
|||
io.commandPending = false;
|
||||
|
||||
//DMA VRAM fill
|
||||
if(io.command.bits(4,5) == 2) {
|
||||
io.dmaActive = true;
|
||||
if(io.dmaFillWait.lower()) {
|
||||
io.dmaFillWord = data;
|
||||
return;
|
||||
}
|
||||
|
@ -113,7 +112,10 @@ auto VDP::readControlPort() -> uint16 {
|
|||
io.commandPending = false;
|
||||
|
||||
uint16 result = 0b0011'0100'0000'0000;
|
||||
result |= io.dmaActive << 1;
|
||||
result |= 1 << 9; //FIFO empty
|
||||
result |= (state.y >= 240) << 3; //vertical blank
|
||||
result |= (state.y >= 240 || state.x >= 320) << 2; //horizontal blank
|
||||
result |= io.command.bit(5) << 1; //DMA active
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -126,7 +128,7 @@ auto VDP::writeControlPort(uint16 data) -> void {
|
|||
|
||||
io.command.bits(2,5) = data.bits(4,7);
|
||||
io.address.bits(14,15) = data.bits(0,1);
|
||||
|
||||
io.dmaFillWait = io.dmaMode == 2 && io.command.bits(4,5) == 2;
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -147,7 +149,7 @@ auto VDP::writeControlPort(uint16 data) -> void {
|
|||
case 0x00: {
|
||||
io.displayOverlayEnable = data.bit(0);
|
||||
io.counterLatch = data.bit(1);
|
||||
io.horizontalInterruptEnable = data.bit(4);
|
||||
io.horizontalBlankInterruptEnable = data.bit(4);
|
||||
io.leftColumnBlank = data.bit(5);
|
||||
return;
|
||||
}
|
||||
|
@ -186,13 +188,13 @@ auto VDP::writeControlPort(uint16 data) -> void {
|
|||
|
||||
//sprite attribute table location
|
||||
case 0x05: {
|
||||
io.attrtableSprite = data.bits(0,7);
|
||||
sprite.io.attributeAddress = data.bits(0,7) << 8;
|
||||
return;
|
||||
}
|
||||
|
||||
//sprite pattern base address
|
||||
case 0x06: {
|
||||
io.nametableBaseSprite = data.bit(5);
|
||||
sprite.io.nametableAddressBase = data.bit(5);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,17 @@ auto VDP::scanline() -> void {
|
|||
if(++state.y >= 262) state.y = 0;
|
||||
if(state.y == 0) scheduler.exit(Scheduler::Event::Frame);
|
||||
|
||||
if(state.y == 0) {
|
||||
sprite.frame();
|
||||
}
|
||||
|
||||
if(state.y < 240) {
|
||||
planeA.scanline(state.y);
|
||||
window.scanline(state.y);
|
||||
planeB.scanline(state.y);
|
||||
sprite.scanline(state.y);
|
||||
}
|
||||
|
||||
state.output = buffer + (state.y * 2 + 0) * 1280;
|
||||
}
|
||||
|
||||
|
@ -16,12 +27,15 @@ auto VDP::run() -> void {
|
|||
|
||||
planeA.run(state.x, state.y);
|
||||
planeB.run(state.x, state.y);
|
||||
sprite.run(state.x, state.y);
|
||||
|
||||
auto output = io.backgroundColor;
|
||||
if(auto color = planeB.output.color) output = color;
|
||||
if(auto color = planeA.output.color) output = color;
|
||||
if(auto color = sprite.output.color) output = color;
|
||||
if(planeB.output.priority) if(auto color = planeB.output.color) output = color;
|
||||
if(planeA.output.priority) if(auto color = planeA.output.color) output = color;
|
||||
if(sprite.output.priority) if(auto color = sprite.output.color) output = color;
|
||||
|
||||
outputPixel(cram[output]);
|
||||
state.x++;
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
auto VDP::Sprite::frame() -> void {
|
||||
uint15 address = io.attributeAddress;
|
||||
uint7 link = 0;
|
||||
|
||||
oam.reset();
|
||||
while(oam.size() < 80) {
|
||||
uint64 attributes;
|
||||
attributes |= (uint64)vdp.vram[address + (link << 2) + 0] << 48;
|
||||
attributes |= (uint64)vdp.vram[address + (link << 2) + 1] << 32;
|
||||
attributes |= (uint64)vdp.vram[address + (link << 2) + 2] << 16;
|
||||
attributes |= (uint64)vdp.vram[address + (link << 2) + 3] << 0;
|
||||
|
||||
auto& object = oam.append();
|
||||
object.x = attributes.bits( 0, 9) - 128;
|
||||
object.address = attributes.bits(16,26) << 4;
|
||||
object.horizontalFlip = attributes.bit (27);
|
||||
object.verticalFlip = attributes.bit (28);
|
||||
object.palette = attributes.bits(29,30);
|
||||
object.priority = attributes.bit (31);
|
||||
object.height = attributes.bits(40,41) << 3;
|
||||
object.width = attributes.bits(42,43) << 3;
|
||||
object.y = attributes.bits(48,57) - 128;
|
||||
|
||||
link = attributes.bits(32,38);
|
||||
if(!link) break;
|
||||
}
|
||||
}
|
||||
|
||||
auto VDP::Sprite::scanline(uint y) -> void {
|
||||
object.reset();
|
||||
for(auto& o : oam) {
|
||||
if((uint9)(o.y + o.height - 1) < y) continue;
|
||||
if((uint9)(y + o.height - 1) < o.y) continue;
|
||||
if(o.x == 0) break;
|
||||
|
||||
object.append(o);
|
||||
if(object.size() >= object.capacity()) break;
|
||||
}
|
||||
}
|
||||
|
||||
auto VDP::Sprite::run(uint x, uint y) -> void {
|
||||
output.priority = 0;
|
||||
output.color = 0;
|
||||
|
||||
for(auto& o : object) {
|
||||
if((uint9)(o.x + o.width - 1) < x) continue;
|
||||
if((uint9)(y + o.width - 1) < o.x) continue;
|
||||
|
||||
auto objectX = (uint9)(x - o.x);
|
||||
auto objectY = (uint9)(y - o.y);
|
||||
if(o.horizontalFlip) objectX = (o.width - 1) - objectX;
|
||||
if(o.verticalFlip) objectY = (o.height - 1) - objectY;
|
||||
|
||||
uint tileX = objectX >> 3;
|
||||
uint tileY = objectY >> 3;
|
||||
uint tileNumber = tileX * (o.width >> 3) + tileY;
|
||||
uint15 tileAddress = o.address + (tileNumber << 4);
|
||||
uint pixelX = objectX & 7;
|
||||
uint pixelY = objectY & 7;
|
||||
tileAddress += pixelY << 1 | pixelX >> 2;
|
||||
|
||||
uint16 tileData = vdp.vram[tileAddress];
|
||||
uint4 color = tileData >> (((pixelX & 3) ^ 3) << 2);
|
||||
if(color) {
|
||||
output.color = o.palette << 4 | color;
|
||||
output.priority = o.priority;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto VDP::Sprite::power() -> void {
|
||||
}
|
||||
|
||||
auto VDP::Sprite::reset() -> void {
|
||||
memory::fill(&io, sizeof(IO));
|
||||
}
|
|
@ -10,6 +10,7 @@ VDP vdp;
|
|||
#include "dma.cpp"
|
||||
#include "render.cpp"
|
||||
#include "background.cpp"
|
||||
#include "sprite.cpp"
|
||||
|
||||
auto VDP::Enter() -> void {
|
||||
while(true) scheduler.synchronize(), vdp.main();
|
||||
|
@ -18,14 +19,26 @@ auto VDP::Enter() -> void {
|
|||
auto VDP::main() -> void {
|
||||
scanline();
|
||||
if(state.y < 240) {
|
||||
if(state.y == 0) {
|
||||
cpu.lower(CPU::Interrupt::VerticalBlank);
|
||||
}
|
||||
cpu.lower(CPU::Interrupt::HorizontalBlank);
|
||||
for(uint x : range(320)) {
|
||||
run();
|
||||
step(1);
|
||||
}
|
||||
if(io.horizontalBlankInterruptEnable) {
|
||||
cpu.raise(CPU::Interrupt::HorizontalBlank);
|
||||
}
|
||||
step(22);
|
||||
} else {
|
||||
if(state.y == 240) {
|
||||
if(io.verticalBlankInterruptEnable) {
|
||||
cpu.raise(CPU::Interrupt::VerticalBlank);
|
||||
}
|
||||
}
|
||||
step(342);
|
||||
}
|
||||
step(22);
|
||||
}
|
||||
|
||||
auto VDP::step(uint clocks) -> void {
|
||||
|
@ -44,6 +57,7 @@ auto VDP::power() -> void {
|
|||
planeA.power();
|
||||
window.power();
|
||||
planeB.power();
|
||||
sprite.power();
|
||||
}
|
||||
|
||||
auto VDP::reset() -> void {
|
||||
|
@ -54,6 +68,7 @@ auto VDP::reset() -> void {
|
|||
planeA.reset();
|
||||
window.reset();
|
||||
planeB.reset();
|
||||
sprite.reset();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -24,7 +24,9 @@ struct VDP : Thread {
|
|||
|
||||
//dma.cpp
|
||||
auto dmaRun() -> void;
|
||||
auto dmaFillVRAM() -> void;
|
||||
auto dmaLoad() -> void;
|
||||
auto dmaFill() -> void;
|
||||
auto dmaCopy() -> void;
|
||||
|
||||
//render.cpp
|
||||
auto scanline() -> void;
|
||||
|
@ -33,7 +35,7 @@ struct VDP : Thread {
|
|||
|
||||
//background.cpp
|
||||
struct Background {
|
||||
auto scanline() -> void;
|
||||
auto scanline(uint y) -> void;
|
||||
auto run(uint x, uint y) -> void;
|
||||
|
||||
auto power() -> void;
|
||||
|
@ -54,15 +56,51 @@ struct VDP : Thread {
|
|||
Background window;
|
||||
Background planeB;
|
||||
|
||||
//sprite.cpp
|
||||
struct Sprite {
|
||||
auto frame() -> void;
|
||||
auto scanline(uint y) -> void;
|
||||
auto run(uint x, uint y) -> void;
|
||||
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
|
||||
struct IO {
|
||||
uint15 attributeAddress;
|
||||
uint1 nametableAddressBase;
|
||||
} io;
|
||||
|
||||
struct Object {
|
||||
uint10 x;
|
||||
uint10 y;
|
||||
uint width;
|
||||
uint height;
|
||||
bool horizontalFlip;
|
||||
bool verticalFlip;
|
||||
uint2 palette;
|
||||
uint1 priority;
|
||||
uint15 address;
|
||||
};
|
||||
|
||||
struct Output {
|
||||
uint6 color;
|
||||
boolean priority;
|
||||
} output;
|
||||
|
||||
array<Object, 80> oam;
|
||||
array<Object, 20> object;
|
||||
};
|
||||
Sprite sprite;
|
||||
|
||||
private:
|
||||
uint16 vram[32768];
|
||||
uint16 vramExpansion[32768]; //not present in stock Mega Drive hardware
|
||||
uint9 cram[64];
|
||||
uint10 vsram[40];
|
||||
|
||||
private:
|
||||
struct IO {
|
||||
//internal state
|
||||
boolean dmaActive;
|
||||
boolean dmaFillWait;
|
||||
uint8 dmaFillWord;
|
||||
|
||||
//command
|
||||
|
@ -73,7 +111,7 @@ private:
|
|||
//$00 mode register 1
|
||||
uint1 displayOverlayEnable;
|
||||
uint1 counterLatch;
|
||||
uint1 horizontalInterruptEnable;
|
||||
uint1 horizontalBlankInterruptEnable;
|
||||
uint1 leftColumnBlank;
|
||||
|
||||
//$01 mode register 2
|
||||
|
@ -84,12 +122,6 @@ private:
|
|||
uint1 displayEnable;
|
||||
uint1 externalVRAM;
|
||||
|
||||
//$05 sprite attribute table location
|
||||
uint8 attrtableSprite;
|
||||
|
||||
//$06 sprite pattern base address
|
||||
uint1 nametableBaseSprite;
|
||||
|
||||
//$07 background color
|
||||
uint6 backgroundColor;
|
||||
|
||||
|
|
|
@ -46,6 +46,16 @@ auto M68K::supervisor() -> bool {
|
|||
}
|
||||
|
||||
auto M68K::exception(uint exception, uint vector) -> void {
|
||||
auto pc = r.pc;
|
||||
auto sr = readSR();
|
||||
|
||||
r.s = 1;
|
||||
r.t = 0;
|
||||
|
||||
push<Long>(pc);
|
||||
push<Word>(sr);
|
||||
|
||||
r.pc = read<Long>(vector << 2);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -32,16 +32,19 @@ struct M68K {
|
|||
Unprivileged,
|
||||
|
||||
Trap,
|
||||
Interrupt,
|
||||
};};
|
||||
|
||||
struct Vector { enum : uint {
|
||||
Illegal = 4,
|
||||
DivisionByZero = 5,
|
||||
BoundsCheck = 6,
|
||||
Overflow = 7,
|
||||
Unprivileged = 8,
|
||||
IllegalLineA = 10,
|
||||
IllegalLineF = 11,
|
||||
Illegal = 4,
|
||||
DivisionByZero = 5,
|
||||
BoundsCheck = 6,
|
||||
Overflow = 7,
|
||||
Unprivileged = 8,
|
||||
IllegalLineA = 10,
|
||||
IllegalLineF = 11,
|
||||
HorizontalBlank = 28,
|
||||
VerticalBlank = 30,
|
||||
};};
|
||||
|
||||
M68K();
|
||||
|
|
|
@ -199,7 +199,28 @@ auto Presentation::updateEmulator() -> void {
|
|||
emulator->set("Scanline Emulation", scanlineEmulation.checked());
|
||||
}
|
||||
|
||||
auto Presentation::clearViewport() -> void {
|
||||
if(!video) return;
|
||||
|
||||
uint32_t* output;
|
||||
uint length = 0;
|
||||
uint width = viewport.geometry().width();
|
||||
uint height = viewport.geometry().height();
|
||||
if(video->lock(output, length, width, height)) {
|
||||
for(uint y : range(height)) {
|
||||
auto dp = output + y * (length >> 2);
|
||||
for(uint x : range(width)) *dp++ = 0xff000000;
|
||||
}
|
||||
|
||||
video->unlock();
|
||||
video->refresh();
|
||||
}
|
||||
}
|
||||
|
||||
auto Presentation::resizeViewport() -> void {
|
||||
//clear video area before resizing to avoid seeing distorted video momentarily
|
||||
clearViewport();
|
||||
|
||||
uint scale = 2;
|
||||
if(settings["Video/Scale"].text() == "Small" ) scale = 2;
|
||||
if(settings["Video/Scale"].text() == "Medium") scale = 3;
|
||||
|
@ -219,7 +240,6 @@ auto Presentation::resizeViewport() -> void {
|
|||
|
||||
if(!emulator) {
|
||||
viewport.setGeometry({0, 0, windowWidth, windowHeight});
|
||||
draw(Resource::Logo::higan);
|
||||
} else {
|
||||
auto videoSize = emulator->videoSize(windowWidth, windowHeight, aspectCorrection);
|
||||
viewport.setGeometry({
|
||||
|
@ -227,6 +247,9 @@ auto Presentation::resizeViewport() -> void {
|
|||
videoSize.width, videoSize.height
|
||||
});
|
||||
}
|
||||
|
||||
//clear video area again to ensure entire viewport area has been painted in
|
||||
clearViewport();
|
||||
}
|
||||
|
||||
auto Presentation::toggleFullScreen() -> void {
|
||||
|
@ -243,43 +266,9 @@ auto Presentation::toggleFullScreen() -> void {
|
|||
menuBar.setVisible(true);
|
||||
statusBar.setVisible(settings["UserInterface/ShowStatusBar"].boolean());
|
||||
}
|
||||
|
||||
Application::processEvents();
|
||||
resizeViewport();
|
||||
}
|
||||
|
||||
auto Presentation::draw(image logo) -> void {
|
||||
if(!video) return;
|
||||
|
||||
uint32_t* output;
|
||||
uint length = 0;
|
||||
uint width = viewport.geometry().width();
|
||||
uint height = viewport.geometry().height();
|
||||
if(video->lock(output, length, width, height)) {
|
||||
uint cx = (width - logo.width()) - 10;
|
||||
uint cy = (height - logo.height()) - 10;
|
||||
|
||||
image backdrop;
|
||||
backdrop.allocate(width, height);
|
||||
if(logo && !program->hasQuit) {
|
||||
backdrop.sphericalGradient(0xff0000bf, 0xff000000, logo.width(), logo.height() / 2, width, height);
|
||||
backdrop.impose(image::blend::sourceAlpha, cx, cy, logo, 0, 0, logo.width(), logo.height());
|
||||
} else {
|
||||
backdrop.fill(0xff000000);
|
||||
}
|
||||
|
||||
auto data = (uint32_t*)backdrop.data();
|
||||
for(auto y : range(height)) {
|
||||
auto dp = output + y * (length >> 2);
|
||||
auto sp = data + y * width;
|
||||
for(auto x : range(width)) *dp++ = *sp++;
|
||||
}
|
||||
|
||||
video->unlock();
|
||||
video->refresh();
|
||||
}
|
||||
}
|
||||
|
||||
auto Presentation::loadShaders() -> void {
|
||||
auto pathname = locate("Video Shaders/");
|
||||
|
||||
|
|
|
@ -11,9 +11,9 @@ struct AboutWindow : Window {
|
|||
struct Presentation : Window {
|
||||
Presentation();
|
||||
auto updateEmulator() -> void;
|
||||
auto clearViewport() -> void;
|
||||
auto resizeViewport() -> void;
|
||||
auto toggleFullScreen() -> void;
|
||||
auto draw(image logo = {}) -> void;
|
||||
auto loadShaders() -> void;
|
||||
|
||||
MenuBar menuBar{this};
|
||||
|
|
|
@ -29,7 +29,6 @@ auto Program::loadMedium(Emulator::Interface& interface, const Emulator::Interfa
|
|||
}
|
||||
updateAudioDriver();
|
||||
updateAudioEffects();
|
||||
presentation->draw();
|
||||
emulator->power();
|
||||
|
||||
presentation->resizeViewport();
|
||||
|
@ -45,14 +44,13 @@ auto Program::loadMedium(Emulator::Interface& interface, const Emulator::Interfa
|
|||
auto Program::unloadMedium() -> void {
|
||||
if(!emulator) return;
|
||||
|
||||
presentation->draw();
|
||||
presentation->clearViewport();
|
||||
toolsManager->cheatEditor.saveCheats();
|
||||
emulator->unload();
|
||||
emulator = nullptr;
|
||||
mediumPaths.reset();
|
||||
|
||||
presentation->resizeViewport();
|
||||
presentation->draw(Resource::Logo::higan);
|
||||
presentation->setTitle({"higan v", Emulator::Version});
|
||||
presentation->systemMenu.setVisible(false);
|
||||
presentation->toolsMenu.setVisible(false);
|
||||
|
|
|
@ -31,7 +31,7 @@ Program::Program(string_vector args) {
|
|||
video->set(Video::Synchronize, settings["Video/Synchronize"].boolean());
|
||||
if(!video->init()) video = Video::create("None");
|
||||
|
||||
presentation->draw(Resource::Logo::higan);
|
||||
presentation->clearViewport();
|
||||
|
||||
audio = Audio::create(settings["Audio/Driver"].text());
|
||||
audio->set(Audio::Device, settings["Audio/Device"].text());
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
#pragma once
|
||||
|
||||
#include <nall/range.hpp>
|
||||
|
||||
namespace nall {
|
||||
|
||||
template<typename T, uint Capacity>
|
||||
struct array {
|
||||
auto capacity() const -> uint { return Capacity; }
|
||||
auto size() const -> uint { return _size; }
|
||||
|
||||
auto reset() -> void {
|
||||
for(uint n : range(_size)) _pool.t[n].~T();
|
||||
_size = 0;
|
||||
}
|
||||
|
||||
auto operator[](uint index) -> T& {
|
||||
return _pool.t[index];
|
||||
}
|
||||
|
||||
auto operator[](uint index) const -> const T& {
|
||||
return _pool.t[index];
|
||||
}
|
||||
|
||||
auto append() -> T& {
|
||||
new(_pool.t + _size) T;
|
||||
return _pool.t[_size++];
|
||||
}
|
||||
|
||||
auto append(const T& value) -> void {
|
||||
new(_pool.t + _size++) T(value);
|
||||
}
|
||||
|
||||
auto append(T&& value) -> void {
|
||||
new(_pool.t + _size++) T(move(value));
|
||||
}
|
||||
|
||||
auto begin() { return &_pool.t[0]; }
|
||||
auto end() { return &_pool.t[_size]; }
|
||||
|
||||
auto begin() const { return &_pool.t[0]; }
|
||||
auto end() const { return &_pool.t[_size]; }
|
||||
|
||||
private:
|
||||
union U {
|
||||
U() {}
|
||||
~U() {}
|
||||
T t[Capacity];
|
||||
} _pool;
|
||||
uint _size = 0;
|
||||
};
|
||||
|
||||
}
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
#include <nall/algorithm.hpp>
|
||||
#include <nall/any.hpp>
|
||||
#include <nall/array.hpp>
|
||||
#include <nall/atoi.hpp>
|
||||
#include <nall/bit.hpp>
|
||||
#include <nall/bit-field.hpp>
|
||||
|
|
|
@ -3,12 +3,7 @@
|
|||
#include <sys/ioctl.h>
|
||||
#include <sys/soundcard.h>
|
||||
|
||||
//OSS4 soundcard.h includes below SNDCTL defines, but OSS3 does not
|
||||
//However, OSS4 soundcard.h does not reside in <sys/>
|
||||
//Therefore, attempt to manually define SNDCTL values if using OSS3 header
|
||||
//Note that if the defines below fail to work on any specific platform, one can point soundcard.h
|
||||
//above to the correct location for OSS4 (usually /usr/lib/oss/include/sys/soundcard.h)
|
||||
//Failing that, one can disable OSS4 ioctl calls inside init() and remove the below defines
|
||||
//OSSv4 features: define fallbacks for OSSv3 (where these ioctls are ignored)
|
||||
|
||||
#ifndef SNDCTL_DSP_COOKEDMODE
|
||||
#define SNDCTL_DSP_COOKEDMODE _IOW('P', 30, int)
|
||||
|
@ -31,12 +26,14 @@ struct AudioOSS : Audio {
|
|||
string device = "/dev/dsp";
|
||||
bool synchronize = true;
|
||||
uint frequency = 48000;
|
||||
uint latency = 60;
|
||||
} settings;
|
||||
|
||||
auto cap(const string& name) -> bool {
|
||||
if(name == Audio::Device) return true;
|
||||
if(name == Audio::Synchronize) return true;
|
||||
if(name == Audio::Frequency) return true;
|
||||
if(name == Audio::Latency) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -44,6 +41,7 @@ struct AudioOSS : Audio {
|
|||
if(name == Audio::Device) return settings.device;
|
||||
if(name == Audio::Synchronize) return settings.synchronize;
|
||||
if(name == Audio::Frequency) return settings.frequency;
|
||||
if(name == Audio::Latency) return settings.latency;
|
||||
return {};
|
||||
}
|
||||
|
||||
|
@ -66,6 +64,12 @@ struct AudioOSS : Audio {
|
|||
return true;
|
||||
}
|
||||
|
||||
if(name == Audio::Latency && value.is<uint>()) {
|
||||
settings.latency = value.get<uint>();
|
||||
if(device.fd >= 0) init();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -81,13 +85,11 @@ struct AudioOSS : Audio {
|
|||
device.fd = open(settings.device, O_WRONLY, O_NONBLOCK);
|
||||
if(device.fd < 0) return false;
|
||||
|
||||
#if 1 //SOUND_VERSION >= 0x040000
|
||||
//attempt to enable OSS4-specific features regardless of version
|
||||
//OSS3 ioctl calls will silently fail, but sound will still work
|
||||
int cooked = 1, policy = 4; //policy should be 0 - 10, lower = less latency, more CPU usage
|
||||
int cooked = 1;
|
||||
ioctl(device.fd, SNDCTL_DSP_COOKEDMODE, &cooked);
|
||||
//policy: 0 = minimum latency (higher CPU usage); 10 = maximum latency (lower CPU usage)
|
||||
int policy = min(10, settings.latency / 20); //note: latency measurement isn't exact
|
||||
ioctl(device.fd, SNDCTL_DSP_POLICY, &policy);
|
||||
#endif
|
||||
int frequency = settings.frequency;
|
||||
ioctl(device.fd, SNDCTL_DSP_CHANNELS, &device.channels);
|
||||
ioctl(device.fd, SNDCTL_DSP_SETFMT, &device.format);
|
||||
|
|
Loading…
Reference in New Issue