Update to v098r12 release.

byuu says:

Changelog:
- higan/video: added support for Emulator::Sprite
- higan/resource: a new system for accessing embedded binary files
  inside the emulation cores; holds the sprites
- higan/sfc/superscope,justifier: re-enabled display of crosshairs
- higan/sfc/superscope: fixed turbo toggle (also shows different
  crosshair color when in turbo mode)
- higan/sfc/ppu: always outputs at 512x480 resolution now
  - causes a slight speed-hit from ~127fps to ~125fps;
  - but allows high-resolution 32x32 cursors that look way better;
  - also avoids the need to implement sprite scaling logic

Right now, the PPU code to always output at 480-height is a really gross
hack. Don't worry, I'll make that nicer before release.

Also, superscope.cpp and justifier.cpp are built around a 256x240
screen. But since we now have 512x480, we can make the cursor's movement
much smoother by doubling the resolution on both axes. The actual games
won't see any accuracy improvements when firing the light guns, but the
cursors will animate nicer so I think it's still worth it. I'll work on
that before the next release as well.

The current 32x32 cursors are nicer, but we can do better now with full
24-bit color. So feel free to submit alternatives. I'll probably reject
them, but you can always try :D

The sprites don't support alpha blending, just color keying (0x00000000
= transparent; anything else is 0xff......). We can revisit that later
if necessary.

The way I have it designed, the only files that do anything with
Emulator::Sprite at all are the superscope and justifier folders.
I didn't have to add any hooks anywhere else. Rendering the sprite is
a lot cleaner than the old code, too.
This commit is contained in:
Tim Allen 2016-05-26 21:20:15 +10:00
parent ae5d380d06
commit 7f3cfa17b9
21 changed files with 217 additions and 35 deletions

View File

@ -5,7 +5,7 @@ target := tomoko
# console := true
flags += -I. -I.. -O3
objects := libco audio video
objects := libco audio video resource
# profile-guided optimization mode
# pgo := instrument
@ -56,6 +56,7 @@ all: build;
obj/libco.o: ../libco/libco.c $(call rwildcard,../libco/)
obj/audio.o: audio/audio.cpp $(call rwildcard,audio/)
obj/video.o: video/video.cpp $(call rwildcard,video/)
obj/resource.o: resource/resource.cpp $(call rwildcard,resource/)
ui := target-$(target)
include $(ui)/GNUmakefile

View File

@ -5,10 +5,11 @@ using namespace nall;
#include <audio/audio.hpp>
#include <video/video.hpp>
#include <resource/resource.hpp>
namespace Emulator {
static const string Name = "higan";
static const string Version = "098.11";
static const string Version = "098.12";
static const string Author = "byuu";
static const string License = "GPLv3";
static const string Website = "http://byuu.org/";

View File

@ -0,0 +1,2 @@
all:
sourcery resource.bml resource.cpp resource.hpp

View File

@ -0,0 +1,5 @@
namespace name=Resource
namespace name=Sprite
binary name=CrosshairRed file=sprite/crosshair-red.png
binary name=CrosshairGreen file=sprite/crosshair-green.png
binary name=CrosshairBlue file=sprite/crosshair-blue.png

View File

@ -0,0 +1,46 @@
#include <nall/nall.hpp>
#include "resource.hpp"
namespace Resource {
namespace Sprite {
const nall::vector<uint8_t> CrosshairRed = { //size: 342
137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,32,0,0,0,32,8,6,0,0,0,115,122,122,
244,0,0,0,4,115,66,73,84,8,8,8,8,124,8,100,136,0,0,0,9,112,72,89,115,0,0,14,196,0,0,14,
196,1,149,43,14,27,0,0,0,248,73,68,65,84,88,133,205,87,65,14,196,32,8,132,102,255,255,101,246,176,177,139,
148,81,80,27,229,212,70,102,6,212,0,50,229,77,26,107,156,37,139,2,228,241,209,39,11,113,71,156,68,139,106,128,
56,255,198,175,203,223,114,16,79,68,253,138,90,99,141,113,112,80,231,131,196,11,83,52,19,43,196,53,135,147,7,38,
150,104,244,212,32,86,235,228,236,20,6,200,207,191,117,215,70,12,242,94,139,133,166,236,173,236,67,252,111,139,67,157,
237,71,48,27,192,244,142,93,228,23,148,144,184,228,131,96,254,3,164,4,176,213,108,37,52,5,208,53,47,227,81,28,
49,153,102,163,88,96,149,68,150,193,21,223,59,128,68,43,69,13,103,4,199,246,8,34,151,240,209,249,38,112,251,47,
97,177,209,74,152,246,95,93,9,211,51,160,181,99,142,128,104,115,55,124,59,136,115,7,146,237,51,33,2,71,166,226,
94,23,13,77,214,104,44,103,174,163,143,86,189,244,187,224,232,151,81,21,132,39,210,33,91,246,54,132,193,44,226,219,
107,95,57,136,120,253,172,254,16,23,0,0,0,0,73,69,78,68,174,66,96,130,
};
const nall::vector<uint8_t> CrosshairGreen = { //size: 329
137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,32,0,0,0,32,8,6,0,0,0,115,122,122,
244,0,0,0,4,115,66,73,84,8,8,8,8,124,8,100,136,0,0,0,9,112,72,89,115,0,0,14,196,0,0,14,
196,1,149,43,14,27,0,0,0,235,73,68,65,84,88,133,213,87,65,18,195,32,8,196,78,31,230,211,253,153,61,180,
52,18,145,1,193,97,178,39,141,44,139,24,69,11,216,209,133,177,98,117,166,37,92,162,77,176,170,118,223,26,163,78,
68,71,145,198,244,169,157,57,35,84,248,43,222,255,109,154,254,113,140,114,102,222,18,239,165,120,251,181,42,0,232,103,
114,217,85,226,163,27,124,232,163,87,142,115,153,82,137,71,98,233,247,21,44,228,194,169,217,171,252,159,22,95,234,164,
47,129,55,128,144,140,237,166,63,132,151,190,4,247,147,16,103,35,157,90,220,140,119,121,80,224,94,108,0,164,227,119,
182,221,229,13,182,82,193,225,176,42,56,59,188,105,9,52,5,3,109,58,243,205,202,203,255,9,17,251,91,202,169,227,
205,128,235,198,19,17,64,40,82,171,225,233,32,158,113,33,65,164,222,9,105,16,50,81,55,238,88,210,212,119,1,0,
238,241,241,126,143,125,62,216,173,151,209,35,222,134,235,96,98,252,229,226,3,112,72,179,236,202,138,114,18,0,0,0,
0,73,69,78,68,174,66,96,130,
};
const nall::vector<uint8_t> CrosshairBlue = { //size: 332
137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,32,0,0,0,32,8,6,0,0,0,115,122,122,
244,0,0,0,4,115,66,73,84,8,8,8,8,124,8,100,136,0,0,0,9,112,72,89,115,0,0,14,196,0,0,14,
196,1,149,43,14,27,0,0,0,238,73,68,65,84,88,133,213,87,91,18,195,32,8,196,78,15,232,81,189,161,253,9,
25,52,98,121,57,76,246,43,137,44,11,24,69,11,232,209,55,99,69,235,76,74,184,69,107,229,245,91,27,220,137,124,
75,140,58,21,165,34,181,246,199,251,100,167,174,200,32,124,137,119,124,134,177,252,116,108,224,44,120,44,190,156,56,102,
163,204,228,182,107,173,80,31,93,225,67,30,189,112,124,85,41,145,120,36,88,191,159,96,33,23,78,101,47,242,127,90,
156,213,73,159,2,111,0,33,21,179,150,63,132,151,62,5,243,78,136,217,236,118,173,85,198,86,30,20,152,154,13,192,
118,251,125,216,90,121,212,118,215,112,86,224,26,142,133,247,152,2,73,195,64,155,190,248,166,229,229,255,132,8,243,146,
242,234,120,43,224,58,241,68,4,16,138,212,110,120,58,136,119,28,72,16,169,103,194,33,136,63,68,209,184,103,74,83,
239,5,0,215,26,167,231,123,124,103,130,53,221,140,94,113,55,100,131,9,242,151,139,31,79,50,234,237,105,206,30,22,
0,0,0,0,73,69,78,68,174,66,96,130,
};
}
}

View File

@ -0,0 +1,7 @@
namespace Resource {
namespace Sprite {
extern const nall::vector<uint8_t> CrosshairRed;
extern const nall::vector<uint8_t> CrosshairGreen;
extern const nall::vector<uint8_t> CrosshairBlue;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 329 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 B

View File

@ -13,6 +13,9 @@ Controller::Controller(bool port) : port(port) {
if(!thread) create(Controller::Enter, 1);
}
Controller::~Controller() {
}
auto Controller::Enter() -> void {
while(true) {
scheduler.synchronize();

View File

@ -15,6 +15,7 @@ struct Controller : Cothread {
enum : bool { Port1 = 0, Port2 = 1 };
Controller(bool port);
virtual ~Controller();
static auto Enter() -> void;
virtual auto main() -> void;

View File

@ -3,17 +3,21 @@ Controller(port),
chained(chained),
device(chained == false ? Device::Justifier : Device::Justifiers)
{
create(Controller::Enter, 21477272);
create(Controller::Enter, 21'477'272);
latched = 0;
counter = 0;
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;
@ -28,14 +32,19 @@ device(chained == false ? Device::Justifier : Device::Justifiers)
}
}
auto Justifier::main() -> void {
unsigned next = cpu.vcounter() * 1364 + cpu.hcounter();
Justifier::~Justifier() {
Emulator::video.removeSprite(player1.sprite);
Emulator::video.removeSprite(player2.sprite);
}
signed x = (active == 0 ? player1.x : player2.x), y = (active == 0 ? player1.y : player2.y);
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 == false) {
unsigned target = y * 1364 + (x + 24) * 4;
if(!offscreen) {
uint target = y * 1364 + (x + 24) * 4;
if(next >= target && prev < target) {
//CRT raster detected, toggle iobit to latch counters
iobit(0);
@ -50,6 +59,8 @@ auto Justifier::main() -> void {
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) {
@ -59,6 +70,8 @@ auto Justifier::main() -> void {
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;

View File

@ -4,6 +4,7 @@ struct Justifier : Controller {
};
Justifier(bool port, bool chained);
~Justifier();
auto main() -> void;
auto data() -> uint2;
@ -11,14 +12,17 @@ struct Justifier : Controller {
//private:
const bool chained; //true if the second justifier is attached to the first
const unsigned device;
const uint device;
bool latched;
unsigned counter;
unsigned prev;
uint counter;
uint prev;
bool active;
struct Player {
signed x, y;
bool trigger, start;
shared_pointer<Emulator::Sprite> sprite;
int x;
int y;
bool trigger;
bool start;
} player1, player2;
};

View File

@ -11,7 +11,10 @@
//Note that no commercial game ever utilizes a Super Scope in port 1.
SuperScope::SuperScope(bool port) : Controller(port) {
create(Controller::Enter, 21477272);
create(Controller::Enter, 21'477'272);
sprite = Emulator::video.createSprite(32, 32);
sprite->setPixels(Resource::Sprite::CrosshairGreen);
latched = 0;
counter = 0;
@ -25,18 +28,22 @@ SuperScope::SuperScope(bool port) : Controller(port) {
pause = false;
offscreen = false;
turbolock = false;
oldturbo = false;
triggerlock = false;
pauselock = false;
prev = 0;
}
auto SuperScope::main() -> void {
unsigned next = cpu.vcounter() * 1364 + cpu.hcounter();
SuperScope::~SuperScope() {
Emulator::video.removeSprite(sprite);
}
if(offscreen == false) {
unsigned target = y * 1364 + (x + 24) * 4;
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);
@ -53,6 +60,8 @@ auto SuperScope::main() -> void {
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;
@ -65,12 +74,11 @@ auto SuperScope::data() -> uint2 {
if(counter == 0) {
//turbo is a switch; toggle is edge sensitive
bool newturbo = interface->inputPoll(port, Device::SuperScope, Turbo);
if(newturbo && !turbo) {
if(newturbo && !oldturbo) {
turbo = !turbo; //toggle state
turbolock = true;
} else {
turbolock = false;
sprite->setPixels(turbo ? Resource::Sprite::CrosshairRed : Resource::Sprite::CrosshairGreen);
}
oldturbo = newturbo;
//trigger is a button
//if turbo is active, trigger is level sensitive; otherwise, it is edge sensitive

View File

@ -1,19 +1,23 @@
struct SuperScope : Controller {
shared_pointer<Emulator::Sprite> sprite;
enum : uint {
X, Y, Trigger, Cursor, Turbo, Pause,
};
SuperScope(bool port);
~SuperScope();
auto main() -> void;
auto data() -> uint2;
auto latch(bool data) -> void;
//private:
private:
bool latched;
unsigned counter;
uint counter;
signed x, y;
int x;
int y;
bool trigger;
bool cursor;
@ -21,9 +25,9 @@ struct SuperScope : Controller {
bool pause;
bool offscreen;
bool turbolock;
bool oldturbo;
bool triggerlock;
bool pauselock;
unsigned prev;
uint prev;
};

View File

@ -227,9 +227,9 @@ auto PPU::frame() -> void {
auto PPU::refresh() -> void {
auto output = this->output;
if(!overscan()) output -= 14 * 512;
auto pitch = 1024 >> interlace();
auto pitch = 512;
auto width = 512;
auto height = !interlace() ? 240 : 480;
auto height = 480;
Emulator::video.refresh(output, pitch * sizeof(uint32), width, height);
}

View File

@ -2,8 +2,9 @@ PPU::Screen::Screen(PPU& self) : self(self) {
}
auto PPU::Screen::scanline() -> void {
line = self.output + self.vcounter() * 1024;
if(self.display.interlace && self.field()) line += 512;
lineA = self.output + self.vcounter() * 1024;
lineB = lineA + (self.display.interlace ? 0 : 512);
if(self.display.interlace && self.field()) lineA += 512, lineB += 512;
//the first hires pixel of each scanline is transparent
//note: exact value initializations are not confirmed on hardware
@ -25,8 +26,8 @@ auto PPU::Screen::run() -> void {
auto sscolor = get_pixel_sub(hires);
auto mscolor = get_pixel_main();
*line++ = (self.regs.display_brightness << 15) | (hires ? sscolor : mscolor);
*line++ = (self.regs.display_brightness << 15) | (mscolor);
*lineA++ = *lineB++ = (self.regs.display_brightness << 15) | (hires ? sscolor : mscolor);
*lineA++ = *lineB++ = (self.regs.display_brightness << 15) | (mscolor);
}
auto PPU::Screen::get_pixel_sub(bool hires) -> uint16 {

View File

@ -1,5 +1,6 @@
struct Screen {
uint32* line;
uint32* lineA;
uint32* lineB;
struct Regs {
bool addsub_mode;

20
higan/video/sprite.cpp Normal file
View File

@ -0,0 +1,20 @@
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

@ -2,6 +2,7 @@
namespace Emulator {
#include "sprite.cpp"
Video video;
Video::~Video() {
@ -10,6 +11,7 @@ Video::~Video() {
auto Video::reset() -> void {
interface = nullptr;
sprites.reset();
delete output;
output = nullptr;
delete palette;
@ -85,6 +87,22 @@ auto Video::setEffect(Effect effect, const any& value) -> void {
}
}
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)) {
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 output;
@ -123,6 +141,23 @@ auto Video::refresh(uint32* input, uint pitch, uint width, uint height) -> void
}
}
for(auto& sprite : sprites) {
if(!sprite->visible) continue;
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] = 0xff000000 | pixel;
}
}
}
interface->videoRefresh(output, width * sizeof(uint32), width, height);
}

View File

@ -3,6 +3,8 @@
namespace Emulator {
struct Interface;
struct Video;
struct Sprite;
struct Video {
enum class Effect : uint {
@ -22,12 +24,18 @@ struct Video {
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:
Emulator::Interface* interface = nullptr;
vector<shared_pointer<Sprite>> sprites;
uint32* output = nullptr;
uint32* palette = nullptr;
uint width = 0;
uint height = 0;
uint colors = 0;
@ -40,6 +48,28 @@ private:
bool colorBleed = false;
bool interframeBlending = 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;