* fix support for light guns (Super Scope, Justifier(s))
This commit is contained in:
byuu 2019-07-26 00:44:36 +09:00
parent 42e5bcc604
commit c2a181dbc5
18 changed files with 148 additions and 367 deletions

View File

@ -1,7 +1,5 @@
#include <emulator/emulator.hpp>
#include <emulator/audio/audio.cpp>
#include <emulator/video/video.cpp>
#include <emulator/resource/resource.cpp>
namespace Emulator {

View File

@ -22,20 +22,20 @@
using namespace nall;
#include <libco/libco.h>
#include <emulator/bits.hpp>
#include <emulator/types.hpp>
#include <emulator/memory/readable.hpp>
#include <emulator/memory/writable.hpp>
#include <emulator/audio/audio.hpp>
#include <emulator/video/video.hpp>
#include <emulator/resource/resource.hpp>
namespace Emulator {
static const string Name = "bsnes";
static const string Version = "107.13";
static const string Version = "107.14";
static const string Author = "byuu";
static const string License = "GPLv3";
static const string Website = "https://byuu.org/";
static const string Website = "https://byuu.org";
//incremented only when serialization format changes
static const string SerializerVersion = "107.3";
@ -49,7 +49,7 @@ namespace Emulator {
//nall/vfs shorthand constants for open(), load()
namespace File {
static const auto Read = vfs::file::mode::read;
static const auto Read = vfs::file::mode::read;
static const auto Write = vfs::file::mode::write;
static const auto Optional = false;
static const auto Required = true;

View File

@ -1,20 +0,0 @@
Sprite::Sprite(uint width, uint height) : width(width), height(height) {
pixels = new uint32[width * height]();
}
Sprite::~Sprite() {
delete[] pixels;
}
auto Sprite::setPixels(const nall::image& image) -> void {
memory::copy(this->pixels, width * height * sizeof(uint32), image.data(), image.size());
}
auto Sprite::setVisible(bool visible) -> void {
this->visible = visible;
}
auto Sprite::setPosition(int x, int y) -> void {
this->x = x;
this->y = y;
}

View File

@ -1,160 +0,0 @@
namespace Emulator {
#include "sprite.cpp"
Video video;
Video::~Video() {
reset(nullptr);
}
auto Video::reset(Interface* interface) -> void {
this->interface = interface;
sprites.reset();
delete buffer;
buffer = nullptr;
delete rotate;
rotate = nullptr;
delete palette;
palette = nullptr;
width = 0;
height = 0;
effects.colorBleed = false;
}
auto Video::setPalette() -> void {
if(!interface) return;
delete palette;
colors = interface->display().colors;
palette = new uint32[colors];
for(auto index : range(colors)) {
uint64 color = interface->color(index);
uint16 b = bits(color, 0-15);
uint16 g = bits(color,16-31);
uint16 r = bits(color,32-47);
uint16 a = 0xffff;
if(saturation != 1.0) {
uint16 grayscale = uclamp<16>((r + g + b) / 3);
double inverse = max(0.0, 1.0 - saturation);
r = uclamp<16>(r * saturation + grayscale * inverse);
g = uclamp<16>(g * saturation + grayscale * inverse);
b = uclamp<16>(b * saturation + grayscale * inverse);
}
if(gamma != 1.0) {
double reciprocal = 1.0 / 32767.0;
r = r > 32767 ? r : uint16(32767 * pow(r * reciprocal, gamma));
g = g > 32767 ? g : uint16(32767 * pow(g * reciprocal, gamma));
b = b > 32767 ? b : uint16(32767 * pow(b * reciprocal, gamma));
}
if(luminance != 1.0) {
r = uclamp<16>(r * luminance);
g = uclamp<16>(g * luminance);
b = uclamp<16>(b * luminance);
}
switch(depth) {
case 24: palette[index] = r >> 8 << 16 | g >> 8 << 8 | b >> 8 << 0; break;
case 30: palette[index] = r >> 6 << 20 | g >> 6 << 10 | b >> 6 << 0; break;
}
}
}
auto Video::setDepth(uint depth) -> void {
this->depth = depth;
}
auto Video::setSaturation(double saturation) -> void {
this->saturation = saturation;
}
auto Video::setGamma(double gamma) -> void {
this->gamma = gamma;
}
auto Video::setLuminance(double luminance) -> void {
this->luminance = luminance;
}
auto Video::setEffect(Effect effect, const any& value) -> void {
if(effect == Effect::ColorBleed && value.is<bool>()) {
effects.colorBleed = value.get<bool>();
}
}
auto Video::createSprite(uint width, uint height) -> shared_pointer<Sprite> {
shared_pointer<Sprite> sprite = new Sprite{width, height};
sprites.append(sprite);
return sprite;
}
auto Video::removeSprite(shared_pointer<Sprite> sprite) -> bool {
for(uint n : range(sprites.size())) {
if(sprite == sprites[n]) {
sprites.remove(n);
return true;
}
}
return false;
}
auto Video::refresh(uint32* input, uint pitch, uint width, uint height) -> void {
if(this->width != width || this->height != height) {
delete buffer;
delete rotate;
buffer = new uint32[width * height]();
rotate = new uint32[height * width]();
this->width = width;
this->height = height;
}
auto output = buffer;
pitch >>= 2; //bytes to words
for(uint y : range(height)) {
auto source = input + y * pitch;
auto target = output + y * width;
for(uint x : range(width)) {
auto color = palette[*source++];
*target++ = color;
}
}
if(effects.colorBleed) {
uint32 mask = depth == 30 ? 0x40100401 : 0x01010101;
for(uint y : range(height)) {
auto target = output + y * width;
for(uint x : range(width)) {
auto a = target[x];
auto b = target[x + (x != width - 1)];
target[x] = (a + b - ((a ^ b) & mask)) >> 1;
}
}
}
for(auto& sprite : sprites) {
if(!sprite->visible) continue;
uint32 opaqueAlpha = depth == 30 ? 0xc0000000 : 0xff000000;
for(int y : range(sprite->height)) {
for(int x : range(sprite->width)) {
int pixelY = sprite->y + y;
if(pixelY < 0 || pixelY >= height) continue;
int pixelX = sprite->x + x;
if(pixelX < 0 || pixelX >= width) continue;
auto pixel = sprite->pixels[y * sprite->width + x];
if(pixel) output[pixelY * width + pixelX] = opaqueAlpha | pixel;
}
}
}
//platform->videoFrame((const uint16*)output, width * sizeof(uint32), width, height);
}
}

View File

@ -1,76 +0,0 @@
#pragma once
namespace Emulator {
struct Interface;
struct Video;
struct Sprite;
struct Video {
enum class Effect : uint {
ColorBleed,
};
~Video();
auto reset(Interface* interface) -> void;
auto setPalette() -> void;
auto setDepth(uint depth) -> void;
auto setSaturation(double saturation) -> void;
auto setGamma(double gamma) -> void;
auto setLuminance(double luminance) -> void;
auto setEffect(Effect effect, const any& value) -> void;
auto createSprite(uint width, uint height) -> shared_pointer<Sprite>;
auto removeSprite(shared_pointer<Sprite> sprite) -> bool;
auto refresh(uint32* input, uint pitch, uint width, uint height) -> void;
private:
Interface* interface = nullptr;
vector<shared_pointer<Sprite>> sprites;
uint32* buffer = nullptr;
uint32* rotate = nullptr;
uint32* palette = nullptr;
uint width = 0;
uint height = 0;
uint colors = 0;
uint depth = 24;
double saturation = 1.0;
double gamma = 1.0;
double luminance = 1.0;
struct Effects {
bool colorBleed = false;
} effects;
friend class Sprite;
};
struct Sprite {
Sprite(uint width, uint height);
~Sprite();
auto setPixels(const nall::image& image) -> void;
auto setVisible(bool visible) -> void;
auto setPosition(int x, int y) -> void;
private:
const uint width;
const uint height;
uint32* pixels = nullptr;
bool visible = false;
int x = 0;
int y = 0;
friend class Video;
};
extern Video video;
}

View File

@ -19,6 +19,8 @@ struct Controller {
auto iobit(bool data) -> void;
virtual auto data() -> uint2 { return 0; }
virtual auto latch(bool data) -> void {}
virtual auto latch() -> void {} //light guns
virtual auto draw(uint16_t* output, uint pitch, uint width, uint height) -> void {} //light guns
const uint port;
};

View File

@ -8,15 +8,11 @@ device(!chained ? ID::Device::Justifier : ID::Device::Justifiers)
active = 0;
prev = 0;
player1.sprite = Emulator::video.createSprite(32, 32);
player1.sprite->setPixels(Resource::Sprite::CrosshairGreen);
player1.x = 256 / 2;
player1.y = 240 / 2;
player1.trigger = false;
player2.start = false;
player2.sprite = Emulator::video.createSprite(32, 32);
player2.sprite->setPixels(Resource::Sprite::CrosshairRed);
player2.x = 256 / 2;
player2.y = 240 / 2;
player2.trigger = false;
@ -31,55 +27,6 @@ device(!chained ? ID::Device::Justifier : ID::Device::Justifiers)
}
}
Justifier::~Justifier() {
Emulator::video.removeSprite(player1.sprite);
Emulator::video.removeSprite(player2.sprite);
}
/*
auto Justifier::main() -> void {
uint next = cpu.vcounter() * 1364 + cpu.hcounter();
int x = (active == 0 ? player1.x : player2.x), y = (active == 0 ? player1.y : player2.y);
bool offscreen = (x < 0 || y < 0 || x >= 256 || y >= ppu.vdisp());
if(!offscreen) {
uint target = y * 1364 + (x + 24) * 4;
if(next >= target && prev < target) {
//CRT raster detected, toggle iobit to latch counters
iobit(0);
iobit(1);
}
}
if(next < prev) {
int nx1 = platform->inputPoll(port, device, 0 + X);
int ny1 = platform->inputPoll(port, device, 0 + Y);
nx1 += player1.x;
ny1 += player1.y;
player1.x = max(-16, min(256 + 16, nx1));
player1.y = max(-16, min(240 + 16, ny1));
player1.sprite->setPosition(player1.x * 2 - 16, player1.y * 2 - 16);
player1.sprite->setVisible(true);
}
if(next < prev && chained) {
int nx2 = platform->inputPoll(port, device, 4 + X);
int ny2 = platform->inputPoll(port, device, 4 + Y);
nx2 += player2.x;
ny2 += player2.y;
player2.x = max(-16, min(256 + 16, nx2));
player2.y = max(-16, min(240 + 16, ny2));
player2.sprite->setPosition(player2.x * 2 - 16, player2.y * 2 - 16);
player2.sprite->setVisible(true);
}
prev = next;
step(2);
synchronize(cpu);
}
*/
auto Justifier::data() -> uint2 {
if(counter >= 32) return 1;
@ -141,3 +88,75 @@ auto Justifier::latch(bool data) -> void {
counter = 0;
if(latched == 0) active = !active; //toggle between both controllers, even when unchained
}
auto Justifier::latch() -> void {
/* active value is inverted here ... */
if(active != 0) {
int nx = platform->inputPoll(port, device, 0 + X);
int ny = platform->inputPoll(port, device, 0 + Y);
player1.x = max(-16, min(256 + 16, nx + player1.x));
player1.y = max(-16, min((int)ppu.vdisp() + 16, ny + player1.y));
bool offscreen = (player1.x < 0 || player1.y < 0 || player1.x >= 256 || player1.y >= (int)ppu.vdisp());
if(!offscreen) ppu.latchCounters(player1.x, player1.y);
}
if(active != 1) {
int nx = platform->inputPoll(port, device, 4 + X);
int ny = platform->inputPoll(port, device, 4 + Y);
player2.x = max(-16, min(256 + 16, nx + player2.x));
player2.y = max(-16, min((int)ppu.vdisp() + 16, ny + player2.y));
bool offscreen = (player2.x < 0 || player2.y < 0 || player2.x >= 256 || player2.y >= (int)ppu.vdisp());
if(!offscreen) ppu.latchCounters(player2.x, player2.y);
}
}
auto Justifier::draw(uint16_t* data, uint pitch, uint width, uint height) -> void {
pitch >>= 1;
float scaleX = (float)width / 256.0;
float scaleY = (float)height / (float)ppu.vdisp();
int length = (float)width / 256.0 * 4.0;
auto plot = [&](int x, int y, uint16_t color) -> void {
if(x >= 0 && y >= 0 && x < (int)width && y < (int)height) {
data[y * pitch + x] = color;
}
};
{ int x = player1.x * scaleX;
int y = player1.y * scaleY;
uint16_t color = 0x03e0;
uint16_t black = 0x0000;
for(int px = x - length - 1; px <= x + length + 1; px++) plot(px, y - 1, black);
for(int px = x - length - 1; px <= x + length + 1; px++) plot(px, y + 1, black);
for(int py = y - length - 1; py <= y + length + 1; py++) plot(x - 1, py, black);
for(int py = y - length - 1; py <= y + length + 1; py++) plot(x + 1, py, black);
plot(x - length - 1, y, black);
plot(x + length + 1, y, black);
plot(x, y - length - 1, black);
plot(x, y + length + 1, black);
for(int px = x - length; px <= x + length; px++) plot(px, y, color);
for(int py = y - length; py <= y + length; py++) plot(x, py, color);
}
if(chained)
{ int x = player2.x * scaleX;
int y = player2.y * scaleY;
uint16_t color = 0x7c00;
uint16_t black = 0x0000;
for(int px = x - length - 1; px <= x + length + 1; px++) plot(px, y - 1, black);
for(int px = x - length - 1; px <= x + length + 1; px++) plot(px, y + 1, black);
for(int py = y - length - 1; py <= y + length + 1; py++) plot(x - 1, py, black);
for(int py = y - length - 1; py <= y + length + 1; py++) plot(x + 1, py, black);
plot(x - length - 1, y, black);
plot(x + length + 1, y, black);
plot(x, y - length - 1, black);
plot(x, y + length + 1, black);
for(int px = x - length; px <= x + length; px++) plot(px, y, color);
for(int py = y - length; py <= y + length; py++) plot(x, py, color);
}
}

View File

@ -4,10 +4,11 @@ struct Justifier : Controller {
};
Justifier(uint port, bool chained);
~Justifier();
auto data() -> uint2;
auto latch(bool data) -> void;
auto latch() -> void override;
auto draw(uint16_t* data, uint pitch, uint width, uint height) -> void override;
//private:
const bool chained; //true if the second justifier is attached to the first
@ -18,7 +19,6 @@ struct Justifier : Controller {
bool active;
struct Player {
shared_pointer<Emulator::Sprite> sprite;
int x;
int y;
bool trigger;

View File

@ -11,9 +11,6 @@
//Note that no commercial game ever utilizes a Super Scope in port 1.
SuperScope::SuperScope(uint port) : Controller(port) {
sprite = Emulator::video.createSprite(32, 32);
sprite->setPixels(Resource::Sprite::CrosshairGreen);
latched = 0;
counter = 0;
@ -34,42 +31,6 @@ SuperScope::SuperScope(uint port) : Controller(port) {
prev = 0;
}
SuperScope::~SuperScope() {
Emulator::video.removeSprite(sprite);
}
/*
auto SuperScope::main() -> void {
uint next = cpu.vcounter() * 1364 + cpu.hcounter();
if(!offscreen) {
uint target = y * 1364 + (x + 24) * 4;
if(next >= target && prev < target) {
//CRT raster detected, toggle iobit to latch counters
iobit(0);
iobit(1);
}
}
if(next < prev) {
//Vcounter wrapped back to zero; update cursor coordinates for start of new frame
int nx = platform->inputPoll(port, ID::Device::SuperScope, X);
int ny = platform->inputPoll(port, ID::Device::SuperScope, Y);
nx += x;
ny += y;
x = max(-16, min(256 + 16, nx));
y = max(-16, min(240 + 16, ny));
offscreen = (x < 0 || y < 0 || x >= 256 || y >= ppu.vdisp());
sprite->setPosition(x * 2 - 16, y * 2 - 16);
sprite->setVisible(true);
}
prev = next;
step(2);
synchronize(cpu);
}
*/
auto SuperScope::data() -> uint2 {
if(counter >= 8) return 1;
@ -78,7 +39,6 @@ auto SuperScope::data() -> uint2 {
bool newturbo = platform->inputPoll(port, ID::Device::SuperScope, Turbo);
if(newturbo && !oldturbo) {
turbo = !turbo; //toggle state
sprite->setPixels(turbo ? (image)Resource::Sprite::CrosshairRed : (image)Resource::Sprite::CrosshairGreen);
}
oldturbo = newturbo;
@ -128,3 +88,42 @@ auto SuperScope::latch(bool data) -> void {
latched = data;
counter = 0;
}
auto SuperScope::latch() -> void {
int nx = platform->inputPoll(port, ID::Device::SuperScope, X);
int ny = platform->inputPoll(port, ID::Device::SuperScope, Y);
x = max(-16, min(256 + 16, nx + x));
y = max(-16, min((int)ppu.vdisp() + 16, ny + y));
offscreen = (x < 0 || y < 0 || x >= 256 || y >= (int)ppu.vdisp());
if(!offscreen) ppu.latchCounters(x, y);
}
auto SuperScope::draw(uint16_t* data, uint pitch, uint width, uint height) -> void {
pitch >>= 1;
float scaleX = (float)width / 256.0;
float scaleY = (float)height / (float)ppu.vdisp();
int length = (float)width / 256.0 * 4.0;
int x = this->x * scaleX;
int y = this->y * scaleY;
auto plot = [&](int x, int y, uint16_t color) -> void {
if(x >= 0 && y >= 0 && x < (int)width && y < (int)height) {
data[y * pitch + x] = color;
}
};
uint16_t color = turbo ? 0x7c00 : 0x03e0;
uint16_t black = 0x0000;
for(int px = x - length - 1; px <= x + length + 1; px++) plot(px, y - 1, black);
for(int px = x - length - 1; px <= x + length + 1; px++) plot(px, y + 1, black);
for(int py = y - length - 1; py <= y + length + 1; py++) plot(x - 1, py, black);
for(int py = y - length - 1; py <= y + length + 1; py++) plot(x + 1, py, black);
plot(x - length - 1, y, black);
plot(x + length + 1, y, black);
plot(x, y - length - 1, black);
plot(x, y + length + 1, black);
for(int px = x - length; px <= x + length; px++) plot(px, y, color);
for(int py = y - length; py <= y + length; py++) plot(x, py, color);
}

View File

@ -1,15 +1,14 @@
struct SuperScope : Controller {
shared_pointer<Emulator::Sprite> sprite;
enum : uint {
X, Y, Trigger, Cursor, Turbo, Pause,
};
SuperScope(uint port);
~SuperScope();
auto data() -> uint2;
auto latch(bool data) -> void;
auto latch() -> void override;
auto draw(uint16_t* data, uint pitch, uint width, uint height) -> void override;
private:
bool latched;

View File

@ -1,3 +1,9 @@
auto PPU::latchCounters(uint hcounter, uint vcounter) -> void {
io.hcounter = hcounter;
io.vcounter = vcounter;
latch.counters = 1;
}
auto PPU::latchCounters() -> void {
io.hcounter = cpu.hdot();
io.vcounter = cpu.vcounter();

View File

@ -109,8 +109,9 @@ auto PPU::scanline() -> void {
latch.ss |= io.bgMode == 7 && hdScale() > 1 && hdSupersample() == 1;
}
if(vcounter() == vdisp() && !io.displayDisable) {
oamAddressReset();
if(vcounter() == vdisp()) {
if(auto device = controllerPort2.device) device->latch(); //light guns
if(!io.displayDisable) oamAddressReset();
}
if(vcounter() == 240) {
@ -144,6 +145,7 @@ auto PPU::refresh() -> void {
}
}
if(auto device = controllerPort2.device) device->draw(output, pitch * sizeof(uint16), width, height);
platform->videoFrame(output, pitch * sizeof(uint16), width, height, hd() ? hdScale() : 1);
frame.pitch = pitch;

View File

@ -236,6 +236,7 @@ public:
};
//io.cpp
auto latchCounters(uint hcounter, uint vcounter) -> void;
auto latchCounters() -> void;
alwaysinline auto vramAddress() const -> uint;
alwaysinline auto readVRAM() -> uint16;

View File

@ -1,3 +1,13 @@
auto PPU::latchCounters(uint hcounter, uint vcounter) -> void {
if(system.fastPPU()) {
return ppufast.latchCounters(hcounter, vcounter);
}
io.hcounter = hcounter;
io.vcounter = vcounter;
latch.counters = 1;
}
auto PPU::latchCounters() -> void {
if(system.fastPPU()) {
return ppufast.latchCounters();

View File

@ -231,6 +231,10 @@ auto PPU::scanline() -> void {
window.scanline();
screen.scanline();
if(vcounter() == vdisp()) {
if(auto device = controllerPort2.device) device->latch(); //light guns
}
if(vcounter() == 240) {
scheduler.exit(Scheduler::Event::Frame);
}
@ -245,6 +249,7 @@ auto PPU::refresh() -> void {
auto pitch = 512;
auto width = 512;
auto height = 480;
if(auto device = controllerPort2.device) device->draw(output, pitch * sizeof(uint16), width, height);
platform->videoFrame(output, pitch * sizeof(uint16), width, height, /* scale = */ 1);
}

View File

@ -13,6 +13,7 @@ struct PPU : Thread, PPUcounter {
auto power(bool reset) -> void;
//io.cpp
auto latchCounters(uint hcounter, uint vcounter) -> void;
auto latchCounters() -> void;
//serialization.cpp

View File

@ -92,9 +92,6 @@ auto System::unload() -> void {
}
auto System::power(bool reset) -> void {
Emulator::video.reset(interface);
Emulator::video.setPalette();
Emulator::audio.reset(interface);
random.entropy(Random::Entropy::Low);

View File

@ -49,8 +49,6 @@ auto Program::updateVideoFormat() -> void {
settings.video.format = video.format();
}
video.setFormat(settings.video.format);
Emulator::video.setDepth(settings.video.format == "RGB30" ? 30 : 24);
Emulator::video.setPalette();
}
auto Program::updateVideoShader() -> void {