bsnes/higan/video/video.cpp

165 lines
4.2 KiB
C++

#include <emulator/emulator.hpp>
namespace Emulator {
#include "sprite.cpp"
Video video;
Video::~Video() {
reset();
}
auto Video::reset() -> void {
interface = nullptr;
sprites.reset();
delete output;
output = nullptr;
delete palette;
palette = nullptr;
width = 0;
height = 0;
effects.colorBleed = false;
effects.interframeBlending = false;
}
auto Video::setInterface(Interface* interface) -> void {
this->interface = interface;
}
auto Video::setPalette() -> void {
if(!interface) return;
delete palette;
colors = interface->videoColors();
palette = new uint32[colors];
for(auto index : range(colors)) {
uint64 color = interface->videoColor(index);
uint16 b = color.bits( 0,15);
uint16 g = color.bits(16,31);
uint16 r = color.bits(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);
}
//convert color from 16-bits/channel to 8-bits/channel; force alpha to 1.0
palette[index] = a.byte(1) << 24 | r.byte(1) << 16 | g.byte(1) << 8 | b.byte(1) << 0;
}
}
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>();
}
if(effect == Effect::InterframeBlending && value.is<bool>()) {
effects.interframeBlending = 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)) {
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;
output = new uint32[width * height]();
this->width = width;
this->height = height;
}
pitch >>= 2; //bytes to words
for(uint y : range(height)) {
auto source = input + y * pitch;
auto target = output + y * width;
if(!effects.interframeBlending) {
for(uint x : range(width)) {
auto color = palette[*source++];
*target++ = color;
}
} else {
for(uint x : range(width)) {
auto a = *target;
auto b = palette[*source++];
*target++ = (a + b - ((a ^ b) & 0x01010101)) >> 1;
}
}
}
if(effects.colorBleed) {
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) & 0x01010101)) >> 1;
}
}
}
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);
}
}