mirror of https://github.com/bsnes-emu/bsnes.git
Update to v106r56 release.
byuu says: I fixed all outstanding bugs that I'm aware of, including all of the errata I listed yesterday. And now it's time for lots of regression testing. After that, I need to add Talarubi's XAudio2 DRC code, and then get a new public bsnes WIP out for final testing. New errata: when setting an icon (nall::image) larger than a Canvas on Windows, it's not centering the image, so you end up seeing the overscan area in the state manager previews, and the bottom of the image gets cut off. I also need to forcefully disable the Xlib screensaver disable support. I think I'll remove the GUI option to bypass it as well, and just force screensaver disable always on with Windows. I'll improve it in the future to toggle the effect between emulator pauses.
This commit is contained in:
parent
b2b51d544f
commit
3b4e8b6d75
|
@ -13,7 +13,7 @@ using namespace nall;
|
|||
|
||||
namespace Emulator {
|
||||
static const string Name = "higan";
|
||||
static const string Version = "106.55";
|
||||
static const string Version = "106.56";
|
||||
static const string Author = "byuu";
|
||||
static const string License = "GPLv3";
|
||||
static const string Website = "https://byuu.org/";
|
||||
|
|
|
@ -214,29 +214,13 @@ auto Presentation::updateStatusIcon() -> void {
|
|||
icon.fill(0xff202020);
|
||||
|
||||
if(emulator->loaded()) {
|
||||
image emblem{program.verified() ? Icon::Emblem::Program : Icon::Emblem::Binary};
|
||||
image emblem{program.verified() ? (image)Icon::Emblem::Program : (image)Icon::Emblem::Binary};
|
||||
icon.impose(image::blend::sourceAlpha, 0, (StatusHeight - 16) / 2, emblem, 0, 0, 16, 16);
|
||||
}
|
||||
|
||||
statusIcon.setIcon(icon);
|
||||
}
|
||||
|
||||
auto Presentation::drawIcon(uint32_t* output, uint length, uint width, uint height) -> void {
|
||||
return;
|
||||
|
||||
int ox = width - 144;
|
||||
int oy = height - 128;
|
||||
if(ox >= 0 && oy >= 0) {
|
||||
image icon{Resource::Icon};
|
||||
icon.alphaBlend(0xff000000);
|
||||
for(uint y : range(128)) {
|
||||
auto target = output + (y + oy) * (length >> 2) + ox;
|
||||
auto source = (uint32_t*)icon.data() + y * 128;
|
||||
memory::copy<uint32_t>(target, source, 128);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto Presentation::clearViewport() -> void {
|
||||
if(!emulator->loaded()) viewportLayout.setPadding();
|
||||
if(!visible() || !video) return;
|
||||
|
@ -250,7 +234,6 @@ auto Presentation::clearViewport() -> void {
|
|||
auto line = output + y * (length >> 2);
|
||||
for(uint x : range(width)) *line++ = 0xff000000;
|
||||
}
|
||||
if(!emulator->loaded()) drawIcon(output, length, width, height);
|
||||
video.release();
|
||||
video.output();
|
||||
}
|
||||
|
|
|
@ -26,7 +26,6 @@ struct Presentation : Window {
|
|||
enum : uint { StatusHeight = 24 };
|
||||
|
||||
auto updateStatusIcon() -> void;
|
||||
auto drawIcon(uint32_t* output, uint length, uint width, uint height) -> void;
|
||||
auto clearViewport() -> void;
|
||||
auto resizeViewport() -> void;
|
||||
auto resizeWindow() -> void;
|
||||
|
@ -103,8 +102,8 @@ struct Presentation : Window {
|
|||
VerticalLayout layout{this};
|
||||
HorizontalLayout viewportLayout{&layout, Size{~0, ~0}, 0};
|
||||
Viewport viewport{&viewportLayout, Size{~0, ~0}, 0};
|
||||
VerticalLayout iconLayout{&viewportLayout, Size{0, ~0}};
|
||||
Widget iconSpacer{&iconLayout, Size{144, ~0}};
|
||||
VerticalLayout iconLayout{&viewportLayout, Size{0, ~0}, 0};
|
||||
Widget iconSpacer{&iconLayout, Size{144, ~0}, 0};
|
||||
Canvas iconCanvas{&iconLayout, Size{128, 128}, 0};
|
||||
HorizontalLayout statusLayout{&layout, Size{~0, StatusHeight}, 0};
|
||||
Label spacerIcon{&statusLayout, Size{8, ~0}, 0};
|
||||
|
|
|
@ -79,11 +79,15 @@ auto Program::saveState(string filename) -> bool {
|
|||
if(!s.size()) return showMessage({"Failed to save [", prefix, "]"}), false;
|
||||
auto serializerRLE = Encode::RLE<uint8_t>(s.data(), s.size());
|
||||
|
||||
vector<uint8_t> previewRLE;
|
||||
//this can be null if a state is captured before the first frame of video output after power/reset
|
||||
if(screenshot.data) {
|
||||
image preview;
|
||||
preview.allocate(screenshot.data, screenshot.pitch, screenshot.width, screenshot.height);
|
||||
preview.copy(screenshot.data, screenshot.pitch, screenshot.width, screenshot.height);
|
||||
if(preview.width() != 256 || preview.height() != 240) preview.scale(256, 240, true);
|
||||
preview.transform(0, 15, 0x8000, 0x7c00, 0x03e0, 0x001f);
|
||||
auto previewRLE = Encode::RLE<uint16_t>(preview.data(), preview.size());
|
||||
previewRLE = Encode::RLE<uint16_t>(preview.data(), preview.size() / sizeof(uint16_t));
|
||||
}
|
||||
|
||||
vector<uint8_t> saveState;
|
||||
saveState.resize(3 * sizeof(uint));
|
||||
|
|
|
@ -35,7 +35,7 @@ auto Program::captureScreenshot() -> bool {
|
|||
if(emulator->loaded() && screenshot.data) {
|
||||
if(auto filename = screenshotPath()) {
|
||||
image capture;
|
||||
capture.allocate(screenshot.data, screenshot.pitch, screenshot.width, screenshot.height);
|
||||
capture.copy(screenshot.data, screenshot.pitch, screenshot.width, screenshot.height);
|
||||
|
||||
//normalize pixel aspect ratio to 1:1
|
||||
if(capture.width() == 512 && capture.height() == 240) capture.scale(512, 480, false); //hires
|
||||
|
|
|
@ -52,10 +52,12 @@ auto CheatDatabase::addCheats() -> void {
|
|||
|
||||
auto CheatWindow::create() -> void {
|
||||
layout.setPadding(5);
|
||||
tableLayout.setSize({2, 2});
|
||||
tableLayout.cell(0).setAlignment({1.0, 0.5});
|
||||
tableLayout.cell(2).setAlignment({1.0, 0.0});
|
||||
nameLabel.setText("Name:");
|
||||
nameValue.onActivate([&] { if(acceptButton.enabled()) acceptButton.doActivate(); });
|
||||
nameValue.onChange([&] { doChange(); });
|
||||
codeLayout.setAlignment(0.0);
|
||||
codeLabel.setText("Code(s):");
|
||||
codeValue.setFont(Font().setFamily(Font::Mono));
|
||||
codeValue.onChange([&] { doChange(); });
|
||||
|
|
|
@ -149,16 +149,22 @@ auto StateManager::updateSelection() -> void {
|
|||
statePreview.setColor({0, 0, 0});
|
||||
if(batched.size() == 1) {
|
||||
if(auto saveState = program.loadStateData(batched.first().property("name"))) {
|
||||
uint skip = memory::readl<sizeof(uint)>(saveState.data() + sizeof(uint));
|
||||
uint seek = 3 * sizeof(uint) + skip;
|
||||
auto preview = Decode::RLE<uint16_t>(saveState.data() + seek, max(seek, saveState.size()) - seek);
|
||||
if(saveState.size() >= 3 * sizeof(uint)) {
|
||||
uint signature = memory::readl<sizeof(uint)>(saveState.data() + 0 * sizeof(uint));
|
||||
uint serializer = memory::readl<sizeof(uint)>(saveState.data() + 1 * sizeof(uint));
|
||||
uint preview = memory::readl<sizeof(uint)>(saveState.data() + 2 * sizeof(uint));
|
||||
if(signature == Program::State::Signature && preview) {
|
||||
uint offset = 3 * sizeof(uint) + serializer;
|
||||
auto preview = Decode::RLE<uint16_t>(saveState.data() + offset, max(offset, saveState.size()) - offset);
|
||||
image icon{0, 15, 0x8000, 0x7c00, 0x03e0, 0x001f};
|
||||
icon.allocate(preview.data(), 256 * sizeof(uint16_t), 256, 240);
|
||||
icon.copy(preview.data(), 256 * sizeof(uint16_t), 256, 240);
|
||||
icon.transform();
|
||||
statePreview.setIcon(icon);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto StateManager::stateEvent(string name) -> void {
|
||||
if(locked() || !name.beginsWith(type())) return;
|
||||
|
|
|
@ -35,15 +35,14 @@ struct CheatWindow : Window {
|
|||
|
||||
public:
|
||||
VerticalLayout layout{this};
|
||||
HorizontalLayout nameLayout{&layout, Size{~0, 0}};
|
||||
Label nameLabel{&nameLayout, Size{40, 0}};
|
||||
LineEdit nameValue{&nameLayout, Size{~0, 0}};
|
||||
HorizontalLayout codeLayout{&layout, Size{~0, ~0}};
|
||||
Label codeLabel{&codeLayout, Size{40, 0}};
|
||||
TextEdit codeValue{&codeLayout, Size{~0, ~0}};
|
||||
TableLayout tableLayout{&layout, Size{~0, ~0}};
|
||||
Label nameLabel{&tableLayout, Size{0, 0}};
|
||||
LineEdit nameValue{&tableLayout, Size{~0, 0}};
|
||||
Label codeLabel{&tableLayout, Size{0, 0}};
|
||||
TextEdit codeValue{&tableLayout, Size{~0, ~0}};
|
||||
HorizontalLayout controlLayout{&layout, Size{~0, 0}};
|
||||
Widget spacer{&controlLayout, Size{40, 0}};
|
||||
CheckLabel enableOption{&controlLayout, Size{~0, 0}};
|
||||
Widget controlSpacer{&controlLayout, Size{~0, 0}};
|
||||
CheckLabel enableOption{&controlLayout, Size{0, 0}};
|
||||
Button acceptButton{&controlLayout, Size{80, 0}};
|
||||
Button cancelButton{&controlLayout, Size{80, 0}};
|
||||
};
|
||||
|
|
|
@ -116,16 +116,17 @@ Presentation::Presentation() {
|
|||
statusBar.setFont(Font().setBold());
|
||||
statusBar.setVisible(settings["UserInterface/ShowStatusBar"].boolean());
|
||||
|
||||
image icon{Resource::Icon};
|
||||
icon.alphaBlend(0xff000000);
|
||||
canvas.setIcon(icon).setVisible(false);
|
||||
|
||||
viewport.setDroppable().onDrop([&](auto locations) {
|
||||
if(!directory::exists(locations(0))) return;
|
||||
program->gameQueue.append(locations(0));
|
||||
viewport.setDroppable().onDrop([&](vector<string> locations) {
|
||||
if(!locations || !directory::exists(locations.first())) return;
|
||||
program->gameQueue.append(locations.first());
|
||||
program->load();
|
||||
});
|
||||
|
||||
iconLayout.setAlignment(0.0);
|
||||
image icon{Resource::Icon};
|
||||
icon.alphaBlend(0x000000);
|
||||
iconCanvas.setIcon(icon);
|
||||
|
||||
onSize([&] {
|
||||
resizeViewport(false);
|
||||
});
|
||||
|
@ -237,22 +238,6 @@ auto Presentation::updateEmulatorDeviceSelections() -> void {
|
|||
}
|
||||
}
|
||||
|
||||
auto Presentation::drawIcon(uint32_t* output, uint length, uint width, uint height) -> void {
|
||||
return;
|
||||
|
||||
int ox = width - 128;
|
||||
int oy = height - 128;
|
||||
if(ox >= 0 && oy >= 0) {
|
||||
image icon{Resource::Icon};
|
||||
icon.alphaBlend(0xff000000);
|
||||
for(uint y : range(112)) {
|
||||
auto target = output + (y + oy) * (length >> 2) + ox;
|
||||
auto source = (uint32_t*)icon.data() + y * 112;
|
||||
memory::copy<uint32_t>(target, source, 112);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto Presentation::clearViewport() -> void {
|
||||
if(!video) return;
|
||||
|
||||
|
@ -269,7 +254,6 @@ auto Presentation::clearViewport() -> void {
|
|||
auto line = output + y * (length >> 2);
|
||||
for(uint x : range(width)) *line++ = 0xff000000;
|
||||
}
|
||||
if(!emulator || !emulator->loaded()) drawIcon(output, length, width, height);
|
||||
video->release();
|
||||
video->output();
|
||||
}
|
||||
|
|
|
@ -12,7 +12,6 @@ struct Presentation : Window {
|
|||
Presentation();
|
||||
auto updateEmulatorMenu() -> void;
|
||||
auto updateEmulatorDeviceSelections() -> void;
|
||||
auto drawIcon(uint32_t* output, uint length, uint width, uint height) -> void;
|
||||
auto clearViewport() -> void;
|
||||
auto resizeViewport(bool resizeWindow = true) -> void;
|
||||
auto toggleFullScreen() -> void;
|
||||
|
@ -79,9 +78,13 @@ struct Presentation : Window {
|
|||
MenuSeparator helpMenuSeparator{&helpMenu};
|
||||
MenuItem about{&helpMenu};
|
||||
|
||||
FixedLayout layout{this};
|
||||
Canvas canvas{&layout, Geometry{0, 0, 1, 1}};
|
||||
Viewport viewport{&layout, Geometry{0, 0, 1, 1}};
|
||||
VerticalLayout layout{this};
|
||||
HorizontalLayout viewportLayout{&layout, Size{~0, ~0}, 0};
|
||||
Viewport viewport{&viewportLayout, Size{~0, ~0}, 0};
|
||||
VerticalLayout iconLayout{&viewportLayout, Size{0, ~0}, 0};
|
||||
Widget iconBefore{&iconLayout, Size{128, ~0}, 0};
|
||||
Canvas iconCanvas{&iconLayout, Size{112, 112}, 0};
|
||||
Widget iconAfter{&iconLayout, Size{128, 8}, 0};
|
||||
|
||||
StatusBar statusBar{this};
|
||||
};
|
||||
|
|
|
@ -40,6 +40,7 @@ auto Program::load(Emulator::Interface& interface) -> void {
|
|||
updateAudioDriver();
|
||||
updateAudioEffects();
|
||||
|
||||
presentation->viewportLayout.remove(presentation->iconLayout);
|
||||
presentation->resizeViewport();
|
||||
presentation->setTitle(emulator->titles().merge(" + "));
|
||||
presentation->systemMenu.setText(information.name).setVisible(true);
|
||||
|
@ -60,6 +61,7 @@ auto Program::unload() -> void {
|
|||
emulator = nullptr;
|
||||
gamePaths.reset();
|
||||
|
||||
presentation->viewportLayout.append(presentation->iconLayout, Size{0, ~0});
|
||||
presentation->resizeViewport();
|
||||
presentation->setTitle({"higan v", Emulator::Version});
|
||||
presentation->systemMenu.setVisible(false);
|
||||
|
|
|
@ -349,6 +349,8 @@ auto pWindow::setMaximized(bool maximized) -> void {
|
|||
}
|
||||
|
||||
auto pWindow::setMaximumSize(Size size) -> void {
|
||||
if(!state().resizable) size = state().geometry.size();
|
||||
|
||||
//TODO: this doesn't have any effect in GTK2 or GTK3
|
||||
GdkGeometry geometry;
|
||||
if(size.height()) size.setHeight(size.height() + _menuHeight() + _statusHeight());
|
||||
|
@ -367,8 +369,12 @@ auto pWindow::setMinimized(bool minimized) -> void {
|
|||
}
|
||||
|
||||
auto pWindow::setMinimumSize(Size size) -> void {
|
||||
gtk_widget_set_size_request(formContainer, size.width(), size.height()); //for GTK3
|
||||
if(!state().resizable) size = state().geometry.size();
|
||||
|
||||
//for GTK3
|
||||
gtk_widget_set_size_request(formContainer, size.width(), size.height());
|
||||
|
||||
//for GTK2
|
||||
GdkGeometry geometry;
|
||||
if(size.height()) size.setHeight(size.height() + _menuHeight() + _statusHeight());
|
||||
geometry.min_width = !state().resizable ? state().geometry.width() : size.width() ? size.width() : 1;
|
||||
|
@ -400,6 +406,9 @@ auto pWindow::setResizable(bool resizable) -> void {
|
|||
if(auto statusBar = state().statusBar) statusBarVisible = statusBar->visible();
|
||||
gtk_window_set_has_resize_grip(GTK_WINDOW(widget), resizable && statusBarVisible);
|
||||
#endif
|
||||
|
||||
setMaximumSize(state().maximumSize);
|
||||
setMinimumSize(state().minimumSize);
|
||||
}
|
||||
|
||||
auto pWindow::setTitle(const string& title) -> void {
|
||||
|
|
|
@ -184,6 +184,16 @@ public slots:
|
|||
};
|
||||
#endif
|
||||
|
||||
#if defined(Hiro_Label)
|
||||
struct QtLabel : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
QtLabel(pLabel& p) : p(p) {}
|
||||
auto paintEvent(QPaintEvent*) -> void;
|
||||
pLabel& p;
|
||||
};
|
||||
#endif
|
||||
|
||||
#if defined(Hiro_LineEdit)
|
||||
struct QtLineEdit : public QLineEdit {
|
||||
Q_OBJECT
|
||||
|
|
|
@ -954,6 +954,67 @@ int hiro::QtHorizontalSlider::qt_metacall(QMetaObject::Call _c, int _id, void **
|
|||
}
|
||||
return _id;
|
||||
}
|
||||
static const uint qt_meta_data_hiro__QtLabel[] = {
|
||||
|
||||
// content:
|
||||
6, // revision
|
||||
0, // classname
|
||||
0, 0, // classinfo
|
||||
0, 0, // methods
|
||||
0, 0, // properties
|
||||
0, 0, // enums/sets
|
||||
0, 0, // constructors
|
||||
0, // flags
|
||||
0, // signalCount
|
||||
|
||||
0 // eod
|
||||
};
|
||||
|
||||
static const char qt_meta_stringdata_hiro__QtLabel[] = {
|
||||
"hiro::QtLabel\0"
|
||||
};
|
||||
|
||||
void hiro::QtLabel::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
|
||||
{
|
||||
Q_UNUSED(_o);
|
||||
Q_UNUSED(_id);
|
||||
Q_UNUSED(_c);
|
||||
Q_UNUSED(_a);
|
||||
}
|
||||
|
||||
const QMetaObjectExtraData hiro::QtLabel::staticMetaObjectExtraData = {
|
||||
0, qt_static_metacall
|
||||
};
|
||||
|
||||
const QMetaObject hiro::QtLabel::staticMetaObject = {
|
||||
{ &QWidget::staticMetaObject, qt_meta_stringdata_hiro__QtLabel,
|
||||
qt_meta_data_hiro__QtLabel, &staticMetaObjectExtraData }
|
||||
};
|
||||
|
||||
#ifdef Q_NO_DATA_RELOCATION
|
||||
const QMetaObject &hiro::QtLabel::getStaticMetaObject() { return staticMetaObject; }
|
||||
#endif //Q_NO_DATA_RELOCATION
|
||||
|
||||
const QMetaObject *hiro::QtLabel::metaObject() const
|
||||
{
|
||||
return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject;
|
||||
}
|
||||
|
||||
void *hiro::QtLabel::qt_metacast(const char *_clname)
|
||||
{
|
||||
if (!_clname) return 0;
|
||||
if (!strcmp(_clname, qt_meta_stringdata_hiro__QtLabel))
|
||||
return static_cast<void*>(const_cast< QtLabel*>(this));
|
||||
return QWidget::qt_metacast(_clname);
|
||||
}
|
||||
|
||||
int hiro::QtLabel::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
|
||||
{
|
||||
_id = QWidget::qt_metacall(_c, _id, _a);
|
||||
if (_id < 0)
|
||||
return _id;
|
||||
return _id;
|
||||
}
|
||||
static const uint qt_meta_data_hiro__QtLineEdit[] = {
|
||||
|
||||
// content:
|
||||
|
|
|
@ -3,13 +3,10 @@
|
|||
namespace hiro {
|
||||
|
||||
auto pLabel::construct() -> void {
|
||||
qtWidget = qtLabel = new QLabel;
|
||||
qtWidget = qtLabel = new QtLabel(*this);
|
||||
|
||||
pWidget::construct();
|
||||
setAlignment(state().alignment);
|
||||
setBackgroundColor(state().backgroundColor);
|
||||
setForegroundColor(state().foregroundColor);
|
||||
setText(state().text);
|
||||
qtLabel->update();
|
||||
}
|
||||
|
||||
auto pLabel::destruct() -> void {
|
||||
|
@ -23,29 +20,40 @@ auto pLabel::minimumSize() const -> Size {
|
|||
}
|
||||
|
||||
auto pLabel::setAlignment(Alignment alignment) -> void {
|
||||
if(!alignment) alignment = {0.0, 0.5};
|
||||
qtLabel->setAlignment((Qt::Alignment)CalculateAlignment(alignment));
|
||||
qtLabel->update();
|
||||
}
|
||||
|
||||
auto pLabel::setBackgroundColor(Color color) -> void {
|
||||
static auto defaultColor = qtLabel->palette().color(QPalette::Base);
|
||||
qtLabel->update();
|
||||
}
|
||||
|
||||
auto palette = qtLabel->palette();
|
||||
palette.setColor(QPalette::Base, CreateColor(color, defaultColor));
|
||||
qtLabel->setPalette(palette);
|
||||
qtLabel->setAutoFillBackground((bool)color);
|
||||
auto pLabel::setFont(const Font& font) -> void {
|
||||
pWidget::setFont(font);
|
||||
qtLabel->update();
|
||||
}
|
||||
|
||||
auto pLabel::setForegroundColor(Color color) -> void {
|
||||
static auto defaultColor = qtLabel->palette().color(QPalette::Text);
|
||||
|
||||
auto palette = qtLabel->palette();
|
||||
palette.setColor(QPalette::Text, CreateColor(color, defaultColor));
|
||||
qtLabel->setPalette(palette);
|
||||
qtLabel->update();
|
||||
}
|
||||
|
||||
auto pLabel::setText(const string& text) -> void {
|
||||
qtLabel->setText(QString::fromUtf8(text));
|
||||
qtLabel->update();
|
||||
}
|
||||
|
||||
//QLabel ignores QPalette ... so we have to implement our own Label class atop QWidget ...
|
||||
auto QtLabel::paintEvent(QPaintEvent* event) -> void {
|
||||
QPainter painter(p.qtLabel);
|
||||
if(auto& color = p.state().backgroundColor) {
|
||||
painter.fillRect(event->rect(), CreateColor(color));
|
||||
}
|
||||
if(auto& text = p.state().text) {
|
||||
if(auto& color = p.state().foregroundColor) {
|
||||
QPen pen(CreateColor(color));
|
||||
painter.setPen(pen);
|
||||
}
|
||||
auto alignment = p.state().alignment ? p.state().alignment : Alignment{0.0, 0.5};
|
||||
painter.drawText(event->rect(), CalculateAlignment(alignment), QString::fromUtf8(text));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -8,10 +8,11 @@ struct pLabel : pWidget {
|
|||
auto minimumSize() const -> Size override;
|
||||
auto setAlignment(Alignment alignment) -> void;
|
||||
auto setBackgroundColor(Color color) -> void;
|
||||
auto setFont(const Font& font) -> void override;
|
||||
auto setForegroundColor(Color color) -> void;
|
||||
auto setText(const string& text) -> void;
|
||||
|
||||
QLabel* qtLabel = nullptr;
|
||||
QtLabel* qtLabel = nullptr;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -54,9 +54,6 @@ auto pWidget::setFont(const Font& font) -> void {
|
|||
|
||||
auto pWidget::setGeometry(Geometry geometry) -> void {
|
||||
if(!qtWidget) return;
|
||||
// Position displacement = GetDisplacement(&widget);
|
||||
// geometry.x -= displacement.x;
|
||||
// geometry.y -= displacement.y;
|
||||
qtWidget->setGeometry(geometry.x(), geometry.y(), geometry.width(), geometry.height());
|
||||
self().doSize();
|
||||
}
|
||||
|
@ -66,12 +63,6 @@ auto pWidget::setVisible(bool visible) -> void {
|
|||
qtWidget->setVisible(visible);
|
||||
}
|
||||
|
||||
//pWidget::constructor() called before p{Derived}::constructor(); ergo qtWidget is not yet valid
|
||||
//pWidget::synchronizeState() is called to finish construction of p{Derived}::constructor()
|
||||
//void pWidget::synchronizeState() {
|
||||
// setFont(widget.font());
|
||||
//}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -168,6 +168,7 @@ auto pWindow::setMaximized(bool maximized) -> void {
|
|||
auto pWindow::setMaximumSize(Size size) -> void {
|
||||
static auto maximumSize = qtWindow->maximumSize();
|
||||
|
||||
if(!state().resizable) size = state().geometry.size();
|
||||
if(size) {
|
||||
qtWindow->setMaximumSize(size.width(), size.height() + _menuHeight() + _statusHeight());
|
||||
} else {
|
||||
|
@ -180,6 +181,7 @@ auto pWindow::setMinimized(bool minimized) -> void {
|
|||
}
|
||||
|
||||
auto pWindow::setMinimumSize(Size size) -> void {
|
||||
if(!state().resizable) size = state().geometry.size();
|
||||
qtWindow->setMinimumSize(size.width(), size.height() + _menuHeight() + _statusHeight());
|
||||
}
|
||||
|
||||
|
@ -204,12 +206,15 @@ auto pWindow::setModal(bool modal) -> void {
|
|||
auto pWindow::setResizable(bool resizable) -> void {
|
||||
if(resizable) {
|
||||
qtLayout->setSizeConstraint(QLayout::SetDefaultConstraint);
|
||||
qtContainer->setMinimumSize(state().geometry.width(), state().geometry.height());
|
||||
//qtContainer->setMinimumSize(state().geometry.width(), state().geometry.height());
|
||||
} else {
|
||||
qtLayout->setSizeConstraint(QLayout::SetFixedSize);
|
||||
qtContainer->setFixedSize(state().geometry.width(), state().geometry.height());
|
||||
//qtContainer->setFixedSize(state().geometry.width(), state().geometry.height());
|
||||
}
|
||||
qtStatusBar->setSizeGripEnabled(resizable);
|
||||
|
||||
setMaximumSize(state().maximumSize);
|
||||
setMinimumSize(state().minimumSize);
|
||||
}
|
||||
|
||||
auto pWindow::setTitle(const string& text) -> void {
|
||||
|
|
|
@ -205,20 +205,6 @@ static auto CALLBACK Shared_windowProc(WindowProc windowProc, HWND hwnd, UINT ms
|
|||
break;
|
||||
}
|
||||
|
||||
case WM_DRAWITEM: {
|
||||
auto drawItem = (LPDRAWITEMSTRUCT)lparam;
|
||||
auto object = (mObject*)GetWindowLongPtr((HWND)drawItem->hwndItem, GWLP_USERDATA);
|
||||
if(!object) break;
|
||||
|
||||
#if defined(Hiro_TabFrame)
|
||||
if(auto tabFrame = dynamic_cast<mTabFrame*>(object)) {
|
||||
return tabFrame->self()->onDrawItem(lparam), true;
|
||||
}
|
||||
#endif
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case WM_GETMINMAXINFO: {
|
||||
auto info = (LPMINMAXINFO)lparam;
|
||||
auto frameMargin = pWindow->frameMargin();
|
||||
|
|
|
@ -105,45 +105,28 @@ auto pTabFrame::_buildImageList() -> void {
|
|||
}
|
||||
}
|
||||
|
||||
//hide all TabFrameItems, and then display the selected TabFrameItem
|
||||
auto pTabFrame::_synchronizeSizable() -> void {
|
||||
for(auto& item : state().items) {
|
||||
if(auto& sizable = item->state.sizable) {
|
||||
sizable->setVisible(item->selected());
|
||||
if(auto& sizable = item->state.sizable) sizable->setVisible(false);
|
||||
}
|
||||
//without this call, widgets from the previous tab will remain visible
|
||||
//alongside widgets from the newly selected tab for about one frame ...
|
||||
Application::processEvents();
|
||||
uint selected = TabCtrl_GetCurSel(hwnd);
|
||||
if(auto item = self().item(selected)) {
|
||||
if(auto& sizable = item->state.sizable) sizable->setVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
auto pTabFrame::onChange() -> void {
|
||||
unsigned selected = TabCtrl_GetCurSel(hwnd);
|
||||
uint selected = TabCtrl_GetCurSel(hwnd);
|
||||
for(auto& item : state().items) item->state.selected = false;
|
||||
if(auto item = self().item(selected)) item->state.selected = true;
|
||||
_synchronizeSizable();
|
||||
self().doChange();
|
||||
}
|
||||
|
||||
//called only if TCS_OWNERDRAWFIXED style is used
|
||||
//this style disables XP/Vista theming of the TabFrame
|
||||
auto pTabFrame::onDrawItem(LPARAM lparam) -> void {
|
||||
/*
|
||||
auto item = (LPDRAWITEMSTRUCT)lparam;
|
||||
FillRect(item->hDC, &item->rcItem, GetSysColorBrush(COLOR_3DFACE));
|
||||
SetBkMode(item->hDC, TRANSPARENT);
|
||||
SetTextColor(item->hDC, GetSysColor(COLOR_BTNTEXT));
|
||||
|
||||
unsigned selection = item->itemID;
|
||||
if(selection < tabFrame.state.text.size()) {
|
||||
string text = tabFrame.state.text[selection];
|
||||
Size size = pFont::size(hfont, text);
|
||||
unsigned width = item->rcItem.right - item->rcItem.left + 1;
|
||||
if(tabFrame.state.image[selection]) {
|
||||
width += size.height + 2;
|
||||
ImageList_Draw(imageList, selection, item->hDC, item->rcItem.left + (width - size.width) / 2 - (size.height + 3), item->rcItem.top + 2, ILD_NORMAL);
|
||||
}
|
||||
TextOut(item->hDC, item->rcItem.left + (width - size.width) / 2, item->rcItem.top + 2, utf16_t(text), text.size());
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -13,7 +13,6 @@ struct pTabFrame : pWidget {
|
|||
auto setVisible(bool visible) -> void override;
|
||||
|
||||
auto onChange() -> void;
|
||||
auto onDrawItem(LPARAM lparam) -> void;
|
||||
|
||||
auto _buildImageList() -> void;
|
||||
auto _synchronizeSizable() -> void;
|
||||
|
|
|
@ -64,8 +64,8 @@ struct image {
|
|||
|
||||
inline auto free() -> void;
|
||||
inline auto load(const string& filename) -> bool;
|
||||
inline auto copy(const void* data, uint pitch, uint width, uint height) -> void;
|
||||
inline auto allocate(uint width, uint height) -> void;
|
||||
inline auto allocate(const void* data, uint pitch, uint width, uint height) -> void;
|
||||
|
||||
//fill.hpp
|
||||
inline auto fill(uint64_t color = 0) -> void;
|
||||
|
|
|
@ -142,6 +142,16 @@ auto image::load(const string& filename) -> bool {
|
|||
return false;
|
||||
}
|
||||
|
||||
//assumes image and data are in the same format; pitch is adapted to image
|
||||
auto image::copy(const void* data, uint pitch, uint width, uint height) -> void {
|
||||
allocate(width, height);
|
||||
for(uint y : range(height)) {
|
||||
auto input = (const uint8_t*)data + y * pitch;
|
||||
auto output = (uint8_t*)_data + y * this->pitch();
|
||||
memory::copy(output, input, width * stride());
|
||||
}
|
||||
}
|
||||
|
||||
auto image::allocate(unsigned width, unsigned height) -> void {
|
||||
if(_data && _width == width && _height == height) return;
|
||||
free();
|
||||
|
@ -150,6 +160,7 @@ auto image::allocate(unsigned width, unsigned height) -> void {
|
|||
_data = allocate(_width, _height, stride());
|
||||
}
|
||||
|
||||
//private
|
||||
auto image::allocate(unsigned width, unsigned height, unsigned stride) -> uint8_t* {
|
||||
//allocate 1x1 larger than requested; so that linear interpolation does not require bounds-checking
|
||||
unsigned size = width * height * stride;
|
||||
|
@ -159,14 +170,4 @@ auto image::allocate(unsigned width, unsigned height, unsigned stride) -> uint8_
|
|||
return data;
|
||||
}
|
||||
|
||||
//assumes image and data are in the same format; pitch is adapted to image
|
||||
auto image::allocate(const void* data, uint pitch, uint width, uint height) -> void {
|
||||
allocate(width, height);
|
||||
for(uint y : range(height)) {
|
||||
auto input = (const uint8_t*)data + y * pitch;
|
||||
auto output = (uint8_t*)_data + y * this->pitch();
|
||||
memory::copy(output, input, width * stride());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#include <nall/windows/registry.hpp>
|
||||
#include "asio.hpp"
|
||||
|
||||
struct AudioASIO : Audio {
|
||||
struct AudioASIO : AudioDriver {
|
||||
static AudioASIO* instance;
|
||||
AudioASIO& self = *this;
|
||||
AudioASIO(Audio& super) : AudioDriver(super) { instance = this; }
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#include <pulse/pulseaudio.h>
|
||||
|
||||
struct AudioPulseAudio : Audio {
|
||||
struct AudioPulseAudio : AudioDriver {
|
||||
AudioPulseAudio& self = *this;
|
||||
AudioPulseAudio(Audio& super) : AudioDriver(super) {}
|
||||
~AudioPulseAudio() { terminate(); }
|
||||
|
|
|
@ -8,22 +8,31 @@
|
|||
|
||||
struct AudioWASAPI : AudioDriver {
|
||||
AudioWASAPI& self = *this;
|
||||
AudioWASAPI(Audio& super) : AudioDriver(super) {}
|
||||
AudioWASAPI(Audio& super) : AudioDriver(super) { enumerate(); }
|
||||
~AudioWASAPI() { terminate(); }
|
||||
|
||||
auto create() -> bool override {
|
||||
IMMDevice* defaultDevice = nullptr;
|
||||
if(self.enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &defaultDevice) != S_OK) return false;
|
||||
for(auto& device : self.devices) {
|
||||
if(device.device != defaultDevice) continue;
|
||||
super.setDevice(device.name);
|
||||
super.setLatency(40);
|
||||
return initialize();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
auto driver() -> string override { return "WASAPI"; }
|
||||
auto ready() -> bool override { return _ready; }
|
||||
auto ready() -> bool override { return self.isReady; }
|
||||
|
||||
auto hasExclusive() -> bool override { return true; }
|
||||
auto hasBlocking() -> bool override { return true; }
|
||||
|
||||
auto hasDevices() -> vector<string> override {
|
||||
return _devices;
|
||||
vector<string> devices;
|
||||
for(auto& device : self.devices) devices.append(device.name);
|
||||
return devices;
|
||||
}
|
||||
|
||||
auto hasChannels() -> vector<uint> override {
|
||||
|
@ -34,7 +43,7 @@ struct AudioWASAPI : AudioDriver {
|
|||
return {self.frequency};
|
||||
}
|
||||
|
||||
auto availableLatencies() -> vector<uint> override {
|
||||
auto hasLatencies() -> vector<uint> override {
|
||||
return {0, 20, 40, 60, 80, 100};
|
||||
}
|
||||
|
||||
|
@ -45,153 +54,152 @@ struct AudioWASAPI : AudioDriver {
|
|||
auto setLatency(uint latency) -> bool override { return initialize(); }
|
||||
|
||||
auto clear() -> void override {
|
||||
_queue.read = 0;
|
||||
_queue.write = 0;
|
||||
_queue.count = 0;
|
||||
_audioClient->Stop();
|
||||
_audioClient->Reset();
|
||||
_audioClient->Start();
|
||||
self.queue.read = 0;
|
||||
self.queue.write = 0;
|
||||
self.queue.count = 0;
|
||||
self.audioClient->Stop();
|
||||
self.audioClient->Reset();
|
||||
self.audioClient->Start();
|
||||
}
|
||||
|
||||
auto output(const double samples[]) -> void override {
|
||||
for(uint n : range(_channels)) {
|
||||
_queue.samples[_queue.write][n] = samples[n];
|
||||
for(uint n : range(self.channels)) {
|
||||
self.queue.samples[self.queue.write][n] = samples[n];
|
||||
}
|
||||
_queue.write++;
|
||||
_queue.count++;
|
||||
self.queue.write++;
|
||||
self.queue.count++;
|
||||
|
||||
if(_queue.count >= _bufferSize) {
|
||||
if(WaitForSingleObject(_eventHandle, self.blocking ? INFINITE : 0) == WAIT_OBJECT_0) {
|
||||
if(self.queue.count >= self.bufferSize) {
|
||||
if(WaitForSingleObject(self.eventHandle, self.blocking ? INFINITE : 0) == WAIT_OBJECT_0) {
|
||||
write();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
auto initialize() -> bool {
|
||||
terminate();
|
||||
auto enumerate() -> bool {
|
||||
if(CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void**)&self.enumerator) != S_OK) return false;
|
||||
|
||||
if(CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void**)&_enumerator) != S_OK) return false;
|
||||
|
||||
//enumerate all audio endpoint devices, and select the first to match the device() name
|
||||
IMMDeviceCollection* deviceCollection = nullptr;
|
||||
if(_enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &deviceCollection) != S_OK) return false;
|
||||
if(self.enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &deviceCollection) != S_OK) return false;
|
||||
uint deviceCount = 0;
|
||||
if(deviceCollection->GetCount(&deviceCount) != S_OK) return false;
|
||||
for(uint deviceIndex : range(deviceCount)) {
|
||||
IMMDevice* device = nullptr;
|
||||
if(deviceCollection->Item(deviceIndex, &device) != S_OK) return false;
|
||||
if(deviceCollection->Item(deviceIndex, &device) != S_OK) continue;
|
||||
IPropertyStore* propertyStore = nullptr;
|
||||
device->OpenPropertyStore(STGM_READ, &propertyStore);
|
||||
PROPVARIANT propVariant;
|
||||
propertyStore->GetValue(PKEY_Device_FriendlyName, &propVariant);
|
||||
_devices.append((const char*)utf8_t(propVariant.pwszVal));
|
||||
Device item;
|
||||
item.name = (const char*)utf8_t(propVariant.pwszVal);
|
||||
item.device = device;
|
||||
self.devices.append(item);
|
||||
propertyStore->Release();
|
||||
if(!_audioDevice && _devices.right() == _device) {
|
||||
_audioDevice = device;
|
||||
} else {
|
||||
device->Release();
|
||||
}
|
||||
}
|
||||
deviceCollection->Release();
|
||||
|
||||
//if no match is found, choose the default audio endpoint for the device()
|
||||
if(!_audioDevice) {
|
||||
if(_enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &_audioDevice) != S_OK) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if(_audioDevice->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, (void**)&_audioClient) != S_OK) return false;
|
||||
auto initialize() -> bool {
|
||||
terminate();
|
||||
if(auto index = self.devices.find([&](auto& device) { return device.name == self.device; })) {
|
||||
self.audioDevice = self.devices[*index].device;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(self.audioDevice->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, (void**)&self.audioClient) != S_OK) return false;
|
||||
|
||||
WAVEFORMATEXTENSIBLE waveFormat = {};
|
||||
if(_exclusive) {
|
||||
if(self.exclusive) {
|
||||
IPropertyStore* propertyStore = nullptr;
|
||||
if(_audioDevice->OpenPropertyStore(STGM_READ, &propertyStore) != S_OK) return false;
|
||||
if(self.audioDevice->OpenPropertyStore(STGM_READ, &propertyStore) != S_OK) return false;
|
||||
PROPVARIANT propVariant;
|
||||
if(propertyStore->GetValue(PKEY_AudioEngine_DeviceFormat, &propVariant) != S_OK) return false;
|
||||
waveFormat = *(WAVEFORMATEXTENSIBLE*)propVariant.blob.pBlobData;
|
||||
propertyStore->Release();
|
||||
if(_audioClient->GetDevicePeriod(nullptr, &_devicePeriod) != S_OK) return false;
|
||||
auto latency = max(_devicePeriod, (REFERENCE_TIME)self.latency * 10'000); //1ms to 100ns units
|
||||
auto result = _audioClient->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, latency, latency, &waveFormat.Format, nullptr);
|
||||
if(self.audioClient->GetDevicePeriod(nullptr, &self.devicePeriod) != S_OK) return false;
|
||||
auto latency = max(self.devicePeriod, (REFERENCE_TIME)self.latency * 10'000); //1ms to 100ns units
|
||||
auto result = self.audioClient->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, latency, latency, &waveFormat.Format, nullptr);
|
||||
if(result == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) {
|
||||
if(_audioClient->GetBufferSize(&_bufferSize) != S_OK) return false;
|
||||
_audioClient->Release();
|
||||
latency = (REFERENCE_TIME)(10'000 * 1'000 * _bufferSize / waveFormat.Format.nSamplesPerSec);
|
||||
if(_audioDevice->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, (void**)&_audioClient) != S_OK) return false;
|
||||
result = _audioClient->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, latency, latency, &waveFormat.Format, nullptr);
|
||||
if(self.audioClient->GetBufferSize(&self.bufferSize) != S_OK) return false;
|
||||
self.audioClient->Release();
|
||||
latency = (REFERENCE_TIME)(10'000 * 1'000 * self.bufferSize / waveFormat.Format.nSamplesPerSec);
|
||||
if(self.audioDevice->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, (void**)&self.audioClient) != S_OK) return false;
|
||||
result = self.audioClient->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, latency, latency, &waveFormat.Format, nullptr);
|
||||
}
|
||||
if(result != S_OK) return false;
|
||||
DWORD taskIndex = 0;
|
||||
_taskHandle = AvSetMmThreadCharacteristics(L"Pro Audio", &taskIndex);
|
||||
self.taskHandle = AvSetMmThreadCharacteristics(L"Pro Audio", &taskIndex);
|
||||
} else {
|
||||
WAVEFORMATEX* waveFormatEx = nullptr;
|
||||
if(_audioClient->GetMixFormat(&waveFormatEx) != S_OK) return false;
|
||||
if(self.audioClient->GetMixFormat(&waveFormatEx) != S_OK) return false;
|
||||
waveFormat = *(WAVEFORMATEXTENSIBLE*)waveFormatEx;
|
||||
CoTaskMemFree(waveFormatEx);
|
||||
if(_audioClient->GetDevicePeriod(&_devicePeriod, nullptr)) return false;
|
||||
auto latency = max(_devicePeriod, (REFERENCE_TIME)self.latency * 10'000); //1ms to 100ns units
|
||||
if(_audioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, latency, 0, &waveFormat.Format, nullptr) != S_OK) return false;
|
||||
if(self.audioClient->GetDevicePeriod(&self.devicePeriod, nullptr)) return false;
|
||||
auto latency = max(self.devicePeriod, (REFERENCE_TIME)self.latency * 10'000); //1ms to 100ns units
|
||||
if(self.audioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, latency, 0, &waveFormat.Format, nullptr) != S_OK) return false;
|
||||
}
|
||||
|
||||
_eventHandle = CreateEvent(nullptr, false, false, nullptr);
|
||||
if(_audioClient->SetEventHandle(_eventHandle) != S_OK) return false;
|
||||
if(_audioClient->GetService(IID_IAudioRenderClient, (void**)&_renderClient) != S_OK) return false;
|
||||
if(_audioClient->GetBufferSize(&_bufferSize) != S_OK) return false;
|
||||
self.eventHandle = CreateEvent(nullptr, false, false, nullptr);
|
||||
if(self.audioClient->SetEventHandle(self.eventHandle) != S_OK) return false;
|
||||
if(self.audioClient->GetService(IID_IAudioRenderClient, (void**)&self.renderClient) != S_OK) return false;
|
||||
if(self.audioClient->GetBufferSize(&self.bufferSize) != S_OK) return false;
|
||||
|
||||
self.channels = waveFormat.Format.nChannels;
|
||||
self.frequency = waveFormat.Format.nSamplesPerSec;
|
||||
_mode = waveFormat.SubFormat.Data1;
|
||||
_precision = waveFormat.Format.wBitsPerSample;
|
||||
self.mode = waveFormat.SubFormat.Data1;
|
||||
self.precision = waveFormat.Format.wBitsPerSample;
|
||||
|
||||
_ready = true;
|
||||
self.isReady = true;
|
||||
clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
auto terminate() -> void {
|
||||
_ready = false;
|
||||
_devices.reset();
|
||||
if(_audioClient) _audioClient->Stop();
|
||||
if(_renderClient) _renderClient->Release(), _renderClient = nullptr;
|
||||
if(_audioClient) _audioClient->Release(), _audioClient = nullptr;
|
||||
if(_audioDevice) _audioDevice->Release(), _audioDevice = nullptr;
|
||||
if(_eventHandle) CloseHandle(_eventHandle), _eventHandle = nullptr;
|
||||
if(_taskHandle) AvRevertMmThreadCharacteristics(_taskHandle), _taskHandle = nullptr;
|
||||
self.isReady = false;
|
||||
if(self.audioClient) self.audioClient->Stop();
|
||||
if(self.renderClient) self.renderClient->Release(), self.renderClient = nullptr;
|
||||
if(self.audioClient) self.audioClient->Release(), self.audioClient = nullptr;
|
||||
if(self.audioDevice) self.audioDevice->Release(), self.audioDevice = nullptr;
|
||||
if(self.eventHandle) CloseHandle(self.eventHandle), self.eventHandle = nullptr;
|
||||
if(self.taskHandle) AvRevertMmThreadCharacteristics(self.taskHandle), self.taskHandle = nullptr;
|
||||
}
|
||||
|
||||
auto write() -> void {
|
||||
uint32_t available = _bufferSize;
|
||||
if(!_exclusive) {
|
||||
uint32_t available = self.bufferSize;
|
||||
if(!self.exclusive) {
|
||||
uint32_t padding = 0;
|
||||
_audioClient->GetCurrentPadding(&padding);
|
||||
available = _bufferSize - padding;
|
||||
self.audioClient->GetCurrentPadding(&padding);
|
||||
available = self.bufferSize - padding;
|
||||
}
|
||||
uint32_t length = min(available, _queue.count);
|
||||
uint32_t length = min(available, self.queue.count);
|
||||
|
||||
uint8_t* buffer = nullptr;
|
||||
if(_renderClient->GetBuffer(length, &buffer) == S_OK) {
|
||||
if(self.renderClient->GetBuffer(length, &buffer) == S_OK) {
|
||||
uint bufferFlags = 0;
|
||||
for(uint _ : range(length)) {
|
||||
double samples[8] = {};
|
||||
if(_queue.count) {
|
||||
for(uint n : range(_channels)) {
|
||||
samples[n] = _queue.samples[_queue.read][n];
|
||||
if(self.queue.count) {
|
||||
for(uint n : range(self.channels)) {
|
||||
samples[n] = self.queue.samples[self.queue.read][n];
|
||||
}
|
||||
_queue.read++;
|
||||
_queue.count--;
|
||||
self.queue.read++;
|
||||
self.queue.count--;
|
||||
}
|
||||
|
||||
if(_mode == 1 && _precision == 16) {
|
||||
if(self.mode == 1 && self.precision == 16) {
|
||||
auto output = (uint16_t*)buffer;
|
||||
for(uint n : range(_channels)) *output++ = (uint16_t)sclamp<16>(samples[n] * (32768.0 - 1.0));
|
||||
for(uint n : range(self.channels)) *output++ = (uint16_t)sclamp<16>(samples[n] * (32768.0 - 1.0));
|
||||
buffer = (uint8_t*)output;
|
||||
} else if(_mode == 1 && _precision == 32) {
|
||||
} else if(self.mode == 1 && self.precision == 32) {
|
||||
auto output = (uint32_t*)buffer;
|
||||
for(uint n : range(_channels)) *output++ = (uint32_t)sclamp<32>(samples[n] * (65536.0 * 32768.0 - 1.0));
|
||||
for(uint n : range(self.channels)) *output++ = (uint32_t)sclamp<32>(samples[n] * (65536.0 * 32768.0 - 1.0));
|
||||
buffer = (uint8_t*)output;
|
||||
} else if(_mode == 3 && _precision == 32) {
|
||||
} else if(self.mode == 3 && self.precision == 32) {
|
||||
auto output = (float*)buffer;
|
||||
for(uint n : range(_channels)) *output++ = float(max(-1.0, min(+1.0, samples[n])));
|
||||
for(uint n : range(self.channels)) *output++ = float(max(-1.0, min(+1.0, samples[n])));
|
||||
buffer = (uint8_t*)output;
|
||||
} else {
|
||||
//output silence for unsupported sample formats
|
||||
|
@ -199,29 +207,34 @@ private:
|
|||
break;
|
||||
}
|
||||
}
|
||||
_renderClient->ReleaseBuffer(length, bufferFlags);
|
||||
self.renderClient->ReleaseBuffer(length, bufferFlags);
|
||||
}
|
||||
}
|
||||
|
||||
bool _ready = false;
|
||||
bool isReady = false;
|
||||
|
||||
uint _mode = 0;
|
||||
uint _precision = 0;
|
||||
uint mode = 0;
|
||||
uint precision = 0;
|
||||
|
||||
struct Device {
|
||||
string name;
|
||||
IMMDevice* device;
|
||||
};
|
||||
|
||||
struct Queue {
|
||||
double samples[65536][8];
|
||||
uint16_t read;
|
||||
uint16_t write;
|
||||
uint16_t count;
|
||||
} _queue;
|
||||
} queue;
|
||||
|
||||
IMMDeviceEnumerator* _enumerator = nullptr;
|
||||
vector<string> _devices;
|
||||
IMMDevice* _audioDevice = nullptr;
|
||||
IAudioClient* _audioClient = nullptr;
|
||||
IAudioRenderClient* _renderClient = nullptr;
|
||||
HANDLE _eventHandle = nullptr;
|
||||
HANDLE _taskHandle = nullptr;
|
||||
REFERENCE_TIME _devicePeriod = 0;
|
||||
uint32_t _bufferSize = 0; //in frames
|
||||
IMMDeviceEnumerator* enumerator = nullptr;
|
||||
vector<Device> devices;
|
||||
IMMDevice* audioDevice = nullptr;
|
||||
IAudioClient* audioClient = nullptr;
|
||||
IAudioRenderClient* renderClient = nullptr;
|
||||
HANDLE eventHandle = nullptr;
|
||||
HANDLE taskHandle = nullptr;
|
||||
REFERENCE_TIME devicePeriod = 0;
|
||||
uint32_t bufferSize = 0; //in frames
|
||||
};
|
||||
|
|
|
@ -7,6 +7,7 @@ struct AudioXAudio2 : AudioDriver, public IXAudio2VoiceCallback {
|
|||
~AudioXAudio2() { terminate(); }
|
||||
|
||||
auto create() -> bool override {
|
||||
super.setChannels(2);
|
||||
super.setFrequency(48000);
|
||||
super.setLatency(40);
|
||||
return initialize();
|
||||
|
@ -85,11 +86,11 @@ private:
|
|||
if(deviceDetails.Role & DefaultGameDevice) deviceID = deviceIndex;
|
||||
}
|
||||
|
||||
if(FAILED(_interface->CreateMasteringVoice(&_masterVoice, _channels, self.frequency, 0, deviceID, nullptr))) return terminate(), false;
|
||||
if(FAILED(_interface->CreateMasteringVoice(&_masterVoice, self.channels, self.frequency, 0, deviceID, nullptr))) return terminate(), false;
|
||||
|
||||
WAVEFORMATEX waveFormat;
|
||||
waveFormat.wFormatTag = WAVE_FORMAT_PCM;
|
||||
waveFormat.nChannels = _channels;
|
||||
waveFormat.nChannels = self.channels;
|
||||
waveFormat.nSamplesPerSec = self.frequency;
|
||||
waveFormat.nBlockAlign = 4;
|
||||
waveFormat.wBitsPerSample = 16;
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
struct InputWindows : InputDriver {
|
||||
InputWindows& self = *this;
|
||||
InputWindows(Input& driver) : InputDriver(super), keyboard(super), mouse(super), joypadXInput(super), joypadDirectInput(super) {}
|
||||
InputWindows(Input& super) : InputDriver(super), keyboard(super), mouse(super), joypadXInput(super), joypadDirectInput(super) {}
|
||||
~InputWindows() { terminate(); }
|
||||
|
||||
auto create() -> bool override {
|
||||
|
@ -48,6 +48,7 @@ private:
|
|||
terminate();
|
||||
if(!self.context) return false;
|
||||
|
||||
//TODO: this won't work if Input is recreated post-initialization; nor will it work with multiple Input instances
|
||||
if(!rawinput.initialized) {
|
||||
rawinput.initialized = true;
|
||||
rawinput.mutex = CreateMutex(nullptr, false, nullptr);
|
||||
|
|
|
@ -56,7 +56,7 @@ struct VideoDirect3D : VideoDriver {
|
|||
//if output size changed, driver must be re-initialized.
|
||||
//failure to do so causes scaling issues on some video drivers.
|
||||
RECT rectangle;
|
||||
GetClientRect((HWND)_context, &rectangle);
|
||||
GetClientRect((HWND)self.context, &rectangle);
|
||||
if(_windowWidth != rectangle.right || _windowHeight != rectangle.bottom) initialize();
|
||||
|
||||
if(width != _inputWidth || height != _inputHeight) {
|
||||
|
@ -84,7 +84,7 @@ struct VideoDirect3D : VideoDriver {
|
|||
|
||||
_device->BeginScene();
|
||||
uint x = 0, y = 0;
|
||||
if(_exclusive) {
|
||||
if(self.exclusive) {
|
||||
//center output in exclusive mode fullscreen window
|
||||
x = (_monitorWidth - _windowWidth) / 2;
|
||||
y = (_monitorHeight - _windowHeight) / 2;
|
||||
|
@ -94,7 +94,7 @@ struct VideoDirect3D : VideoDriver {
|
|||
_device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
|
||||
_device->EndScene();
|
||||
|
||||
if(_blocking) {
|
||||
if(self.blocking) {
|
||||
D3DRASTER_STATUS status;
|
||||
while(true) { //wait for a previous vblank to finish, if necessary
|
||||
_device->GetRasterStatus(0, &status);
|
||||
|
@ -169,7 +169,7 @@ private:
|
|||
if(!_device) return false;
|
||||
if(_lost && !recover()) return false;
|
||||
|
||||
auto filter = !_smooth ? D3DTEXF_POINT : D3DTEXF_LINEAR;
|
||||
auto filter = !self.smooth ? D3DTEXF_POINT : D3DTEXF_LINEAR;
|
||||
_device->SetSamplerState(0, D3DSAMP_MINFILTER, filter);
|
||||
_device->SetSamplerState(0, D3DSAMP_MAGFILTER, filter);
|
||||
return true;
|
||||
|
@ -209,9 +209,9 @@ private:
|
|||
|
||||
auto initialize() -> bool {
|
||||
terminate();
|
||||
if(!_context) return false;
|
||||
if(!self.context) return false;
|
||||
|
||||
HMONITOR monitor = MonitorFromWindow((HWND)_context, MONITOR_DEFAULTTOPRIMARY);
|
||||
HMONITOR monitor = MonitorFromWindow((HWND)self.context, MONITOR_DEFAULTTOPRIMARY);
|
||||
MONITORINFOEX information = {};
|
||||
information.cbSize = sizeof(MONITORINFOEX);
|
||||
GetMonitorInfo(monitor, &information);
|
||||
|
@ -236,7 +236,7 @@ private:
|
|||
nullptr, nullptr, GetModuleHandle(0), nullptr);
|
||||
|
||||
RECT rectangle;
|
||||
GetClientRect((HWND)_context, &rectangle);
|
||||
GetClientRect((HWND)self.context, &rectangle);
|
||||
_windowWidth = rectangle.right;
|
||||
_windowHeight = rectangle.bottom;
|
||||
|
||||
|
@ -253,15 +253,15 @@ private:
|
|||
_presentation.AutoDepthStencilFormat = D3DFMT_UNKNOWN;
|
||||
_presentation.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
|
||||
|
||||
if(!_exclusive) {
|
||||
_presentation.hDeviceWindow = (HWND)_context;
|
||||
if(!self.exclusive) {
|
||||
_presentation.hDeviceWindow = (HWND)self.context;
|
||||
_presentation.Windowed = true;
|
||||
_presentation.BackBufferFormat = D3DFMT_UNKNOWN;
|
||||
_presentation.BackBufferWidth = 0;
|
||||
_presentation.BackBufferHeight = 0;
|
||||
|
||||
ShowWindow((HWND)_exclusiveContext, SW_HIDE);
|
||||
if(_instance->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, (HWND)_context,
|
||||
if(_instance->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, (HWND)self.context,
|
||||
D3DCREATE_FPU_PRESERVE | D3DCREATE_SOFTWARE_VERTEXPROCESSING, &_presentation, &_device) != D3D_OK) {
|
||||
return false;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue