mirror of
https://github.com/ares-emulator/ares.git
synced 2026-11-13 12:31:50 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
41d1b7e57d | ||
|
|
c28ba81684 | ||
|
|
1561a1d0b4 | ||
|
|
0a302dcf0e | ||
|
|
07051ab738 | ||
|
|
973030ab67 | ||
|
|
67716bcedf | ||
|
|
e06b748516 | ||
|
|
02dac5b4b7 | ||
|
|
a4b8571b61 | ||
|
|
a3fa22b5fc | ||
|
|
9e9b6f8642 | ||
|
|
5f6a0983bd | ||
|
|
2fcab5ca5f | ||
|
|
778a32700d | ||
|
|
a6577318b6 | ||
|
|
badd476d3b | ||
|
|
5a3548402b | ||
|
|
c1f0307a8d | ||
|
|
77c0718df4 | ||
|
|
30bff8dd06 | ||
|
|
b1cf9bf4e5 | ||
|
|
bafc2b6ef4 | ||
|
|
565591568f | ||
|
|
9df891606f | ||
|
|
eea666317b | ||
|
|
ceff1d47a6 | ||
|
|
81dea4cf7e | ||
|
|
17fa3b58a6 | ||
|
|
70fa70fff4 | ||
|
|
137afe5b5b | ||
|
|
545790f048 | ||
|
|
807ef03005 | ||
|
|
ce89a31749 | ||
|
|
3e7675cafb | ||
|
|
4132375f26 | ||
|
|
5e0bd5b3b8 | ||
|
|
d00c84ff1b | ||
|
|
97f3a7a6fb | ||
|
|
8c656cdfd3 | ||
|
|
bea395b24b | ||
|
|
6992197fb8 | ||
|
|
097698ab63 | ||
|
|
46abceebf5 | ||
|
|
06130778b7 | ||
|
|
85b8d6c5a5 | ||
|
|
8436634bc1 | ||
|
|
5972b4e1aa | ||
|
|
426606bd02 | ||
|
|
f9604e29e6 | ||
|
|
7d7b8ea4b2 | ||
|
|
2ed529d564 | ||
|
|
73148db456 | ||
|
|
2fbc9c391c | ||
|
|
12f3d6d89d | ||
|
|
78c13dddef | ||
|
|
be19b58307 | ||
|
|
bca80dc784 | ||
|
|
e6fe07b084 | ||
|
|
c28e47cb93 | ||
|
|
c217662211 | ||
|
|
975a35e7a7 | ||
|
|
2e9233e534 | ||
|
|
4c6d151f7f | ||
|
|
383931bde9 | ||
|
|
1ac8c602c9 | ||
|
|
d5135ca35f | ||
|
|
8265726879 | ||
|
|
5261757f3c | ||
|
|
c099be2b85 | ||
|
|
de8143ef47 | ||
|
|
b80f08956b | ||
|
|
a3088f6d4c | ||
|
|
fdaf0b65a1 | ||
|
|
71e0ad1f5b | ||
|
|
c87be1b4f0 |
No files matched your search
@@ -42,3 +42,8 @@ do
|
||||
zip -r ../ares-${package}.zip ${outdir}
|
||||
cd -
|
||||
done
|
||||
|
||||
# Package the source archive
|
||||
pushd src
|
||||
git archive --format=tar.gz -o ../ares-source.tar.gz HEAD
|
||||
popd
|
||||
@@ -89,6 +89,7 @@ jobs:
|
||||
TARGET_PRESET: ${{ matrix.platform.target-cmake-preset }}
|
||||
- name: "Windows: sign"
|
||||
if: runner.os == 'Windows' && inputs.codesign
|
||||
continue-on-error: true
|
||||
uses: DanaBear/code-sign-action@98c79121b376beab8d6a9484f445089db4461bca
|
||||
with:
|
||||
certificate: '${{ secrets.WIN_CERTIFICATE }}'
|
||||
|
||||
@@ -73,6 +73,7 @@ jobs:
|
||||
name: ares ${{ steps.check.outputs.versionName }}
|
||||
body: ${{ steps.check.outputs.description }}
|
||||
files: |
|
||||
${{ github.workspace }}/ares-source.tar.gz
|
||||
${{ github.workspace }}/ares-macos-universal.zip
|
||||
${{ github.workspace }}/ares-macos-universal-dSYMs.zip
|
||||
${{ github.workspace }}/ares-windows-x64.zip
|
||||
|
||||
+2
-1
@@ -102,7 +102,8 @@
|
||||
"ARES_BUILD_LOCAL": false,
|
||||
"ARES_BUILD_OPTIONAL_TARGETS": true,
|
||||
"ARES_UNITY_CORES": true,
|
||||
"ARES_PRECOMPILE_HEADERS": true
|
||||
"ARES_PRECOMPILE_HEADERS": true,
|
||||
"CMAKE_MSVC_RUNTIME_LIBRARY": {"type": "STRING", "value": "MultiThreaded"}
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -828,4 +828,32 @@ You may add additional accurate notices of copyright ownership.
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
||||
|
||||
----------------------------------------------------------------------
|
||||
----------------------------------------------------------------------
|
||||
|
||||
----------------------------------------------------------------------
|
||||
qoi/qon
|
||||
|
||||
Copyright (c) 2025, Roger Sanders
|
||||
Revised QOI format, achieving higher compression with equivalent performance.
|
||||
|
||||
Based on the "Quite OK Image" format for fast, lossless image compression
|
||||
Copyright (c) 2021, Dominic Szablewski - https://phoboslab.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
----------------------------------------------------------------------
|
||||
@@ -5,6 +5,57 @@ struct Axis : Input {
|
||||
auto value() const -> s64 { return _value; }
|
||||
auto setValue(s64 value) -> void { _value = value; }
|
||||
|
||||
auto setOperatingRange(s16 position, double saturationRadius, double offset) -> double {
|
||||
return position * saturationRadius / 32767.0 + offset;
|
||||
}
|
||||
|
||||
auto processDeadzoneAndResponseCurve(double position, double innerDeadzone, double cardinalMax, double offset) -> double {
|
||||
auto lengthAbsolute = abs(position - offset);
|
||||
if(lengthAbsolute <= innerDeadzone) {
|
||||
position = offset;
|
||||
} else {
|
||||
lengthAbsolute = (lengthAbsolute - innerDeadzone) * (cardinalMax) / (cardinalMax - innerDeadzone) / lengthAbsolute;
|
||||
position = (position - offset) * lengthAbsolute + offset;
|
||||
}
|
||||
return position;
|
||||
}
|
||||
|
||||
auto revisePosition(double position, double length, double saturationRadius, double offset) -> double {
|
||||
if(length > saturationRadius) {
|
||||
length = saturationRadius / length;
|
||||
position = (position - offset) * length + offset;
|
||||
}
|
||||
return position;
|
||||
}
|
||||
|
||||
auto applyGateBoundaries(double innerDeadzone, double cardinalMax, double diagonalMax, double positionX, double positionY, double offset, double& boundedPositionX, double& boundedPositionY) -> void {
|
||||
if(positionX != offset && positionY != offset) {
|
||||
auto slope = (positionY - offset) / (positionX - offset);
|
||||
auto edgex = copysign(cardinalMax / (abs(slope) + (cardinalMax - diagonalMax) / diagonalMax), positionX);
|
||||
auto edgey = copysign(min(abs(edgex * slope), cardinalMax / (1.0 / abs(slope) + (cardinalMax - diagonalMax) / diagonalMax)), positionY);
|
||||
edgex = edgey / slope;
|
||||
|
||||
auto distanceToEdge = hypot(edgex, edgey);
|
||||
|
||||
auto length = hypot(positionX - offset, positionY - offset);
|
||||
if(length > distanceToEdge) {
|
||||
positionX = edgex + offset;
|
||||
positionY = edgey + offset;
|
||||
}
|
||||
}
|
||||
boundedPositionX = positionX;
|
||||
boundedPositionY = positionY;
|
||||
}
|
||||
|
||||
auto clampAxisToNearestBoundary(double position, double offset, double cardinalMax) -> double {
|
||||
if(abs(position - offset) > cardinalMax) position = copysign(cardinalMax, position - offset) + offset;
|
||||
return position;
|
||||
}
|
||||
|
||||
auto counteractPrecisionError(double position) -> double {
|
||||
return copysign(abs(position) + 1e-09, position);
|
||||
}
|
||||
|
||||
protected:
|
||||
s64 _value = 0;
|
||||
s64 _minimum = -32768;
|
||||
|
||||
@@ -7,6 +7,8 @@ Screen::Screen(string name, u32 width, u32 height) : Video(name) {
|
||||
_inputB = new u32[width * height]();
|
||||
_output = new u32[width * height]();
|
||||
_rotate = new u32[width * height]();
|
||||
_lineOverrideActive.resize(width * height, false);
|
||||
_lineOverride.resize(width * height, nullptr);
|
||||
|
||||
if constexpr(ares::Video::Threaded) {
|
||||
_thread = nall::thread::create({&Screen::main, this});
|
||||
@@ -50,6 +52,8 @@ auto Screen::power() -> void {
|
||||
memory::fill<u32>(_inputB.data(), _canvasWidth * _canvasHeight, _fillColor);
|
||||
memory::fill<u32>(_output.data(), _canvasWidth * _canvasHeight, _fillColor);
|
||||
memory::fill<u32>(_rotate.data(), _canvasWidth * _canvasHeight, _fillColor);
|
||||
memory::fill<n1>(_lineOverrideActive.data(), _canvasWidth * _canvasHeight, false);
|
||||
memory::fill<const u32*>(_lineOverride.data(), _canvasWidth * _canvasHeight, nullptr);
|
||||
}
|
||||
|
||||
auto Screen::pixels(bool frame) -> array_span<u32> {
|
||||
@@ -61,6 +65,7 @@ auto Screen::pixels(bool frame) -> array_span<u32> {
|
||||
auto Screen::resetPalette() -> void {
|
||||
lock_guard<recursive_mutex> lock(_mutex);
|
||||
_palette.reset();
|
||||
refreshPalette();
|
||||
}
|
||||
|
||||
auto Screen::resetSprites() -> void {
|
||||
@@ -117,18 +122,21 @@ auto Screen::setSaturation(f64 saturation) -> void {
|
||||
lock_guard<recursive_mutex> lock(_mutex);
|
||||
_saturation = saturation;
|
||||
_palette.reset();
|
||||
refreshPalette();
|
||||
}
|
||||
|
||||
auto Screen::setGamma(f64 gamma) -> void {
|
||||
lock_guard<recursive_mutex> lock(_mutex);
|
||||
_gamma = gamma;
|
||||
_palette.reset();
|
||||
refreshPalette();
|
||||
}
|
||||
|
||||
auto Screen::setLuminance(f64 luminance) -> void {
|
||||
lock_guard<recursive_mutex> lock(_mutex);
|
||||
_luminance = luminance;
|
||||
_palette.reset();
|
||||
refreshPalette();
|
||||
}
|
||||
|
||||
auto Screen::setFillColor(u32 fillColor) -> void {
|
||||
@@ -187,6 +195,7 @@ auto Screen::colors(u32 colors, function<n64 (n32)> color) -> void {
|
||||
_colors = colors;
|
||||
_color = color;
|
||||
_palette.reset();
|
||||
refreshPalette();
|
||||
}
|
||||
|
||||
auto Screen::frame() -> void {
|
||||
@@ -226,7 +235,13 @@ auto Screen::refresh() -> void {
|
||||
auto source = input + y * pitch;
|
||||
auto target = output + y * width;
|
||||
|
||||
if(_interlace) {
|
||||
if (_lineOverrideActive[y]) {
|
||||
auto source = _lineOverride[y];
|
||||
for(u32 x : range(width)) {
|
||||
auto color = *source++;
|
||||
*target++ = color;
|
||||
}
|
||||
} else if(_interlace) {
|
||||
if((_interlaceField & 1) == (y & 1)) {
|
||||
for(u32 x : range(width)) {
|
||||
auto color = _palette[*source++];
|
||||
@@ -334,6 +349,20 @@ auto Screen::refresh() -> void {
|
||||
memory::fill<u32>(_inputB.data(), width * height, _fillColor);
|
||||
}
|
||||
|
||||
auto Screen::lookupPalette(u32 index) -> u32 {
|
||||
return _palette[index];
|
||||
}
|
||||
|
||||
auto Screen::overrideLineDraw(u32 y, const u32* source) -> void {
|
||||
_lineOverride[y] = source;
|
||||
_lineOverrideActive[y] = true;
|
||||
}
|
||||
|
||||
auto Screen::clearOverrideLineDraw(u32 y) -> void {
|
||||
_lineOverrideActive[y] = false;
|
||||
_lineOverride[y] = nullptr;
|
||||
}
|
||||
|
||||
auto Screen::refreshPalette() -> void {
|
||||
lock_guard<recursive_mutex> lock(_mutex);
|
||||
if(_palette) return;
|
||||
|
||||
@@ -61,6 +61,9 @@ struct Screen : Video {
|
||||
auto colors(u32 colors, function<n64 (n32)> color) -> void;
|
||||
auto frame() -> void;
|
||||
auto refresh() -> void;
|
||||
auto lookupPalette(u32 index) -> u32;
|
||||
auto overrideLineDraw(u32 y, const u32* source) -> void;
|
||||
auto clearOverrideLineDraw(u32 y) -> void;
|
||||
|
||||
auto serialize(string& output, string depth) -> void override;
|
||||
auto unserialize(Markup::Node node) -> void override;
|
||||
@@ -94,6 +97,8 @@ protected:
|
||||
unique_pointer<u32[]> _output;
|
||||
unique_pointer<u32[]> _rotate;
|
||||
unique_pointer<u32[]> _palette;
|
||||
vector<n1> _lineOverrideActive;
|
||||
vector<const u32*> _lineOverride;
|
||||
vector<Node::Video::Sprite> _sprites;
|
||||
|
||||
//unserialized:
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
list(REMOVE_DUPLICATES ares.components)
|
||||
|
||||
set(ARES_COMPONENTS_LIST "${ares.components}" CACHE LIST "Enabled shared hardware components" FORCE)
|
||||
|
||||
mark_as_advanced(ARES_COMPONENTS_LIST)
|
||||
|
||||
ares_add_sources(INCLUDED CMakeLists.txt)
|
||||
|
||||
macro(component_sources)
|
||||
|
||||
@@ -104,12 +104,12 @@ auto ARM7TDMI::armInstructionDataRegisterShift
|
||||
|
||||
auto ARM7TDMI::armInstructionMemorySwap
|
||||
(n4 m, n4 d, n4 n, n1 byte) -> void {
|
||||
n32 rm = r(m) + (m == 15 ? 4 : 0);
|
||||
lock();
|
||||
n32 word = load((byte ? Byte : Word), r(n));
|
||||
store((byte ? Byte : Word), r(n), rm);
|
||||
r(d) = word;
|
||||
store((byte ? Byte : Word), r(n), r(m) + (m == 15 ? 4 : 0));
|
||||
unlock();
|
||||
idle();
|
||||
r(d) = word;
|
||||
}
|
||||
|
||||
auto ARM7TDMI::armInstructionMoveHalfImmediate
|
||||
@@ -123,7 +123,10 @@ auto ARM7TDMI::armInstructionMoveHalfImmediate
|
||||
if(pre == 0) rn = up ? rn + immediate : rn - immediate;
|
||||
|
||||
if(pre == 0 || writeback) r(n) = rn + (n == 15 ? 4 : 0);
|
||||
if(mode == 1) r(d) = rd;
|
||||
if(mode == 1) {
|
||||
idle();
|
||||
r(d) = rd;
|
||||
}
|
||||
}
|
||||
|
||||
auto ARM7TDMI::armInstructionMoveHalfRegister
|
||||
@@ -138,7 +141,10 @@ auto ARM7TDMI::armInstructionMoveHalfRegister
|
||||
if(pre == 0) rn = up ? rn + rm : rn - rm;
|
||||
|
||||
if(pre == 0 || writeback) r(n) = rn + (n == 15 ? 4 : 0);
|
||||
if(mode == 1) r(d) = rd;
|
||||
if(mode == 1) {
|
||||
idle();
|
||||
r(d) = rd;
|
||||
}
|
||||
}
|
||||
|
||||
auto ARM7TDMI::armInstructionMoveImmediateOffset
|
||||
@@ -152,7 +158,10 @@ auto ARM7TDMI::armInstructionMoveImmediateOffset
|
||||
if(pre == 0) rn = up ? rn + immediate : rn - immediate;
|
||||
|
||||
if(pre == 0 || writeback) r(n) = rn;
|
||||
if(mode == 1) r(d) = rd;
|
||||
if(mode == 1) {
|
||||
idle();
|
||||
r(d) = rd;
|
||||
}
|
||||
}
|
||||
|
||||
auto ARM7TDMI::armInstructionMoveMultiple
|
||||
@@ -219,7 +228,10 @@ auto ARM7TDMI::armInstructionMoveRegisterOffset
|
||||
if(pre == 0) rn = up ? rn + rm : rn - rm;
|
||||
|
||||
if(pre == 0 || writeback) r(n) = rn;
|
||||
if(mode == 1) r(d) = rd;
|
||||
if(mode == 1) {
|
||||
idle();
|
||||
r(d) = rd;
|
||||
}
|
||||
}
|
||||
|
||||
auto ARM7TDMI::armInstructionMoveSignedImmediate
|
||||
@@ -233,7 +245,10 @@ auto ARM7TDMI::armInstructionMoveSignedImmediate
|
||||
if(pre == 0) rn = up ? rn + immediate : rn - immediate;
|
||||
|
||||
if(pre == 0 || writeback) r(n) = rn + (n == 15 ? 4 : 0);
|
||||
if(mode == 1) r(d) = rd;
|
||||
if(mode == 1) {
|
||||
idle();
|
||||
r(d) = rd;
|
||||
}
|
||||
}
|
||||
|
||||
auto ARM7TDMI::armInstructionMoveSignedRegister
|
||||
@@ -248,7 +263,10 @@ auto ARM7TDMI::armInstructionMoveSignedRegister
|
||||
if(pre == 0) rn = up ? rn + rm : rn - rm;
|
||||
|
||||
if(pre == 0 || writeback) r(n) = rn + (n == 15 ? 4 : 0);
|
||||
if(mode == 1) r(d) = rd;
|
||||
if(mode == 1) {
|
||||
idle();
|
||||
r(d) = rd;
|
||||
}
|
||||
}
|
||||
|
||||
auto ARM7TDMI::armInstructionMoveToCoprocessorFromRegister
|
||||
|
||||
@@ -106,13 +106,14 @@ auto ARM7TDMI::thumbInstructionLoadLiteral
|
||||
(n8 displacement, n3 d) -> void {
|
||||
n32 address = (r(15) & ~3) + (displacement << 2);
|
||||
r(d) = load(Word, address);
|
||||
idle();
|
||||
}
|
||||
|
||||
auto ARM7TDMI::thumbInstructionMoveByteImmediate
|
||||
(n3 d, n3 n, n5 offset, n1 mode) -> void {
|
||||
switch(mode) {
|
||||
case 0: store(Byte, r(n) + offset, r(d)); break; //STRB
|
||||
case 1: r(d) = load(Byte, r(n) + offset); break; //LDRB
|
||||
case 1: r(d) = load(Byte, r(n) + offset); idle(); break; //LDRB
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,7 +121,7 @@ auto ARM7TDMI::thumbInstructionMoveHalfImmediate
|
||||
(n3 d, n3 n, n5 offset, n1 mode) -> void {
|
||||
switch(mode) {
|
||||
case 0: store(Half, r(n) + offset * 2, r(d)); break; //STRH
|
||||
case 1: r(d) = load(Half, r(n) + offset * 2); break; //LDRH
|
||||
case 1: r(d) = load(Half, r(n) + offset * 2); idle(); break; //LDRH
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,11 +159,11 @@ auto ARM7TDMI::thumbInstructionMoveRegisterOffset
|
||||
case 0: store(Word, r(n) + r(m), r(d)); break; //STR
|
||||
case 1: store(Half, r(n) + r(m), r(d)); break; //STRH
|
||||
case 2: store(Byte, r(n) + r(m), r(d)); break; //STRB
|
||||
case 3: r(d) = load(Byte | Signed, r(n) + r(m)); break; //LDSB
|
||||
case 4: r(d) = load(Word, r(n) + r(m)); break; //LDR
|
||||
case 5: r(d) = load(Half, r(n) + r(m)); break; //LDRH
|
||||
case 6: r(d) = load(Byte, r(n) + r(m)); break; //LDRB
|
||||
case 7: r(d) = load(Half | Signed, r(n) + r(m)); break; //LDSH
|
||||
case 3: r(d) = load(Byte | Signed, r(n) + r(m)); idle(); break; //LDSB
|
||||
case 4: r(d) = load(Word, r(n) + r(m)); idle(); break; //LDR
|
||||
case 5: r(d) = load(Half, r(n) + r(m)); idle(); break; //LDRH
|
||||
case 6: r(d) = load(Byte, r(n) + r(m)); idle(); break; //LDRB
|
||||
case 7: r(d) = load(Half | Signed, r(n) + r(m)); idle(); break; //LDSH
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,7 +171,7 @@ auto ARM7TDMI::thumbInstructionMoveStack
|
||||
(n8 immediate, n3 d, n1 mode) -> void {
|
||||
switch(mode) {
|
||||
case 0: store(Word, r(13) + immediate * 4, r(d)); break; //STR
|
||||
case 1: r(d) = load(Word, r(13) + immediate * 4); break; //LDR
|
||||
case 1: r(d) = load(Word, r(13) + immediate * 4); idle(); break; //LDR
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,7 +179,7 @@ auto ARM7TDMI::thumbInstructionMoveWordImmediate
|
||||
(n3 d, n3 n, n5 offset, n1 mode) -> void {
|
||||
switch(mode) {
|
||||
case 0: store(Word, r(n) + offset * 4, r(d)); break; //STR
|
||||
case 1: r(d) = load(Word, r(n) + offset * 4); break; //LDR
|
||||
case 1: r(d) = load(Word, r(n) + offset * 4); idle(); break; //LDR
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ auto ARM7TDMI::load(u32 mode, n32 address) -> n32 {
|
||||
} else {
|
||||
word = ROR(word, address.bit(0,1) << 3);
|
||||
}
|
||||
idle();
|
||||
return word;
|
||||
}
|
||||
|
||||
|
||||
+6
-6
@@ -30,15 +30,15 @@ auto PPU::load(Node::Object parent) -> void {
|
||||
screen->refreshRateHint(4 * 1024 * 1024, 456, 154);
|
||||
|
||||
if(Model::GameBoy()) {
|
||||
screen->colors(1 << 2, {&PPU::colorGameBoy, this});
|
||||
screen->setFillColor(0);
|
||||
|
||||
colorEmulationDMG = screen->append<Node::Setting::String>("Color Emulation", "Game Boy", [&](auto value) {
|
||||
screen->resetPalette();
|
||||
});
|
||||
colorEmulationDMG->setAllowedValues({"Game Boy", "Game Boy Pocket", "RGB"});
|
||||
colorEmulationDMG->setDynamic(true);
|
||||
|
||||
screen->colors(1 << 2, {&PPU::colorGameBoy, this});
|
||||
screen->setFillColor(0);
|
||||
|
||||
interframeBlending = screen->append<Node::Setting::Boolean>("Interframe Blending", true, [&](auto value) {
|
||||
screen->setInterframeBlending(value);
|
||||
});
|
||||
@@ -46,14 +46,14 @@ auto PPU::load(Node::Object parent) -> void {
|
||||
}
|
||||
|
||||
if(Model::GameBoyColor()) {
|
||||
screen->colors(1 << 15, {&PPU::colorGameBoyColor, this});
|
||||
screen->setFillColor(0x7fff);
|
||||
|
||||
colorEmulationCGB = screen->append<Node::Setting::Boolean>("Color Emulation", true, [&](auto value) {
|
||||
screen->resetPalette();
|
||||
});
|
||||
colorEmulationCGB->setDynamic(true);
|
||||
|
||||
screen->colors(1 << 15, {&PPU::colorGameBoyColor, this});
|
||||
screen->setFillColor(0x7fff);
|
||||
|
||||
interframeBlending = screen->append<Node::Setting::Boolean>("Interframe Blending", true, [&](auto value) {
|
||||
screen->setInterframeBlending(value);
|
||||
});
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
ares_components(
|
||||
arm7tdmi
|
||||
s3511a
|
||||
)
|
||||
ares_components(arm7tdmi s3511a)
|
||||
|
||||
ares_add_sources(
|
||||
CORE #
|
||||
|
||||
+24
-19
@@ -1,7 +1,8 @@
|
||||
auto CPU::sleep() -> void {
|
||||
dmaRun();
|
||||
ARM7TDMI::irq = irq.synchronizer;
|
||||
prefetchStep(1);
|
||||
if(!dmac.step()) {
|
||||
ARM7TDMI::irq = irq.synchronizer;
|
||||
prefetchStep(1);
|
||||
}
|
||||
}
|
||||
|
||||
template <bool UseDebugger>
|
||||
@@ -48,14 +49,18 @@ inline auto CPU::getBus(u32 mode, n32 address) -> n32 {
|
||||
mode = cartMode(mode, address);
|
||||
context.romAccess = true;
|
||||
if(mode & Prefetch && wait.prefetch) {
|
||||
if(mode & Word) address &= ~3; //prevents misaligned PC from reading incorrect values
|
||||
if(mode & Nonsequential && prefetch.cycle == 0 && prefetch.empty()) prefetchReset();
|
||||
prefetchSync(address);
|
||||
prefetchStep(1);
|
||||
word = prefetchRead();
|
||||
if(mode & Word) word |= prefetchRead() << 16;
|
||||
if(address == prefetch.addr && (!prefetch.empty() || prefetch.ahead)) {
|
||||
prefetchStepInternal(1);
|
||||
word = prefetchRead();
|
||||
if(mode & Word) word |= prefetchRead() << 16;
|
||||
} else {
|
||||
if(mode & Word) address &= ~3; //prevents misaligned PC from reading incorrect values
|
||||
prefetchSync(mode, address);
|
||||
step(waitCartridge(mode, address));
|
||||
word = cartridge.readRom<false>(mode, address);
|
||||
}
|
||||
} else {
|
||||
if(context.dmaActive) context.dmaRomAccess = true;
|
||||
if(mode & DMA) dmac.romBurst = true;
|
||||
prefetchReset();
|
||||
step(waitCartridge(mode, address));
|
||||
word = cartridge.readRom<false>(mode, address);
|
||||
@@ -81,11 +86,11 @@ inline auto CPU::getBus(u32 mode, n32 address) -> n32 {
|
||||
}
|
||||
|
||||
auto CPU::get(u32 mode, n32 address) -> n32 {
|
||||
dmaRun();
|
||||
if(!(mode & DMA)) dmac.runPending();
|
||||
ARM7TDMI::irq = irq.synchronizer;
|
||||
context.romAccess = false;
|
||||
u32 word = getBus<false>(mode, address);
|
||||
if(!context.romAccess && !context.dmaActive) cartridge.mrom.burst = false;
|
||||
if(!context.romAccess && !(mode & DMA)) cartridge.mrom.burst = false;
|
||||
return word;
|
||||
}
|
||||
|
||||
@@ -94,7 +99,7 @@ auto CPU::getDebugger(u32 mode, n32 address) -> n32 {
|
||||
}
|
||||
|
||||
auto CPU::set(u32 mode, n32 address, n32 word) -> void {
|
||||
dmaRun();
|
||||
if(!(mode & DMA)) dmac.runPending();
|
||||
ARM7TDMI::irq = irq.synchronizer;
|
||||
context.romAccess = false;
|
||||
|
||||
@@ -136,7 +141,7 @@ auto CPU::set(u32 mode, n32 address, n32 word) -> void {
|
||||
case 0x08: case 0x09: case 0x0a: case 0x0b: case 0x0c: case 0x0d:
|
||||
mode = cartMode(mode, address);
|
||||
context.romAccess = true;
|
||||
if(context.dmaActive) context.dmaRomAccess = true;
|
||||
if(mode & DMA) dmac.romBurst = true;
|
||||
prefetchReset();
|
||||
step(waitCartridge(mode, address));
|
||||
cartridge.writeRom(mode, address, word);
|
||||
@@ -155,11 +160,11 @@ auto CPU::set(u32 mode, n32 address, n32 word) -> void {
|
||||
}
|
||||
|
||||
openBus.set(mode, address, word);
|
||||
if(!context.romAccess && !context.dmaActive) cartridge.mrom.burst = false;
|
||||
if(!context.romAccess && !(mode & DMA)) cartridge.mrom.burst = false;
|
||||
}
|
||||
|
||||
auto CPU::lock() -> void {
|
||||
dmaRun();
|
||||
dmac.runPending();
|
||||
context.busLocked = true;
|
||||
}
|
||||
|
||||
@@ -194,11 +199,11 @@ auto CPU::cartMode(u32 mode, n32 address) -> u32 {
|
||||
if(cartridge.mrom.burst == false) return mode | Nonsequential;
|
||||
|
||||
//determine whether sequential access may be performed
|
||||
if(!context.dmaActive) {
|
||||
u32 sequential = nonsequential ? Nonsequential : Sequential;
|
||||
if(mode & DMA) {
|
||||
u32 sequential = dmac.romBurst ? Sequential : Nonsequential;
|
||||
mode |= sequential;
|
||||
} else {
|
||||
u32 sequential = context.dmaRomAccess ? Sequential : Nonsequential;
|
||||
u32 sequential = ARM7TDMI::nonsequential ? Nonsequential : Sequential;
|
||||
mode |= sequential;
|
||||
}
|
||||
|
||||
|
||||
+19
-39
@@ -44,7 +44,7 @@ auto CPU::main() -> void {
|
||||
}
|
||||
|
||||
if(halted()) {
|
||||
dmaRun();
|
||||
dmac.runPending();
|
||||
if(!(irq.enable[0] & irq.flag[0])) {
|
||||
return step(1);
|
||||
}
|
||||
@@ -56,32 +56,12 @@ auto CPU::main() -> void {
|
||||
instruction();
|
||||
}
|
||||
|
||||
auto CPU::dmaRun() -> void {
|
||||
if(!context.dmaActive && !context.busLocked) {
|
||||
context.dmaActive = true;
|
||||
while(true) {
|
||||
if(dma[0].run()) continue;
|
||||
if(dma[1].run()) continue;
|
||||
if(dma[2].run()) continue;
|
||||
if(dma[3].run()) continue;
|
||||
break;
|
||||
}
|
||||
if(context.dmaRan) {
|
||||
idle();
|
||||
context.dmaRan = false;
|
||||
context.dmaRomAccess = false;
|
||||
context.dmaActiveChannel = 0; //assign burst to a channel that cannot access ROM
|
||||
}
|
||||
context.dmaActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
auto CPU::setInterruptFlag(u32 source) -> void {
|
||||
irq.flag[1] |= source;
|
||||
}
|
||||
|
||||
inline auto CPU::stepIRQ() -> void {
|
||||
if(!context.dmaActive) {
|
||||
if(!dmac.stallingCPU) {
|
||||
irq.synchronizer = irq.ime[0] && (irq.enable[0] & irq.flag[0]);
|
||||
irq.enable[0] = irq.enable[1];
|
||||
irq.flag[0] = irq.flag[1];
|
||||
@@ -93,10 +73,10 @@ auto CPU::step(u32 clocks) -> void {
|
||||
if(!clocks) return;
|
||||
context.hcounter = (context.hcounter + clocks) % 1232;
|
||||
|
||||
dma[0].waiting = max(0, dma[0].waiting - (s32)clocks);
|
||||
dma[1].waiting = max(0, dma[1].waiting - (s32)clocks);
|
||||
dma[2].waiting = max(0, dma[2].waiting - (s32)clocks);
|
||||
dma[3].waiting = max(0, dma[3].waiting - (s32)clocks);
|
||||
dmac.channel[0].waiting = max(0, dmac.channel[0].waiting - (s32)clocks);
|
||||
dmac.channel[1].waiting = max(0, dmac.channel[1].waiting - (s32)clocks);
|
||||
dmac.channel[2].waiting = max(0, dmac.channel[2].waiting - (s32)clocks);
|
||||
dmac.channel[3].waiting = max(0, dmac.channel[3].waiting - (s32)clocks);
|
||||
|
||||
for(auto _ : range(clocks)) {
|
||||
stepIRQ();
|
||||
@@ -141,7 +121,7 @@ auto CPU::power() -> void {
|
||||
for(auto& byte : iwram) byte = 0x00;
|
||||
for(auto& byte : ewram) byte = 0x00;
|
||||
|
||||
for(auto n : range(4)) dma[n] = {n};
|
||||
for(auto n : range(4)) dmac.channel[n] = {n};
|
||||
for(auto n : range(4)) timer[n] = {n};
|
||||
serial = {};
|
||||
keypad = {};
|
||||
@@ -152,21 +132,21 @@ auto CPU::power() -> void {
|
||||
prefetch = {};
|
||||
context = {};
|
||||
|
||||
dma[0].source.setBits(27); dma[0].latch.source.setBits(27);
|
||||
dma[0].target.setBits(27); dma[0].latch.target.setBits(27);
|
||||
dma[0].length.setBits(14); dma[0].latch.length.setBits(14);
|
||||
dmac.channel[0].source.setBits(27); dmac.channel[0].latch.source.setBits(27);
|
||||
dmac.channel[0].target.setBits(27); dmac.channel[0].latch.target.setBits(27);
|
||||
dmac.channel[0].length.setBits(14); dmac.channel[0].latch.length.setBits(14);
|
||||
|
||||
dma[1].source.setBits(28); dma[1].latch.source.setBits(28);
|
||||
dma[1].target.setBits(27); dma[1].latch.target.setBits(27);
|
||||
dma[1].length.setBits(14); dma[1].latch.length.setBits(14);
|
||||
dmac.channel[1].source.setBits(28); dmac.channel[1].latch.source.setBits(28);
|
||||
dmac.channel[1].target.setBits(27); dmac.channel[1].latch.target.setBits(27);
|
||||
dmac.channel[1].length.setBits(14); dmac.channel[1].latch.length.setBits(14);
|
||||
|
||||
dma[2].source.setBits(28); dma[2].latch.source.setBits(28);
|
||||
dma[2].target.setBits(27); dma[2].latch.target.setBits(27);
|
||||
dma[2].length.setBits(14); dma[2].latch.length.setBits(14);
|
||||
dmac.channel[2].source.setBits(28); dmac.channel[2].latch.source.setBits(28);
|
||||
dmac.channel[2].target.setBits(27); dmac.channel[2].latch.target.setBits(27);
|
||||
dmac.channel[2].length.setBits(14); dmac.channel[2].latch.length.setBits(14);
|
||||
|
||||
dma[3].source.setBits(28); dma[3].latch.source.setBits(28);
|
||||
dma[3].target.setBits(28); dma[3].latch.target.setBits(28);
|
||||
dma[3].length.setBits(16); dma[3].latch.length.setBits(16);
|
||||
dmac.channel[3].source.setBits(28); dmac.channel[3].latch.source.setBits(28);
|
||||
dmac.channel[3].target.setBits(28); dmac.channel[3].latch.target.setBits(28);
|
||||
dmac.channel[3].length.setBits(16); dmac.channel[3].latch.length.setBits(16);
|
||||
|
||||
for(u32 n = 0x0b0; n <= 0x0df; n++) bus.io[n] = this; //DMA
|
||||
for(u32 n = 0x100; n <= 0x10f; n++) bus.io[n] = this; //Timers
|
||||
|
||||
+38
-30
@@ -46,14 +46,14 @@ struct CPU : ARM7TDMI, Thread, IO {
|
||||
auto unload() -> void;
|
||||
|
||||
auto main() -> void;
|
||||
auto dmaRun() -> void;
|
||||
auto setInterruptFlag(u32 source) -> void;
|
||||
auto stepIRQ() -> void;
|
||||
auto step(u32 clocks) -> void override;
|
||||
auto power() -> void;
|
||||
|
||||
//prefetch.cpp
|
||||
auto prefetchSync(n32 address) -> void;
|
||||
auto prefetchSync(u32 mode, n32 address) -> void;
|
||||
auto prefetchStepInternal(u32 clocks) -> void;
|
||||
auto prefetchStep(u32 clocks) -> void;
|
||||
auto prefetchReset() -> void;
|
||||
auto prefetchRead() -> n16;
|
||||
@@ -106,36 +106,48 @@ struct CPU : ARM7TDMI, Thread, IO {
|
||||
n32 mask;
|
||||
};
|
||||
|
||||
struct DMA {
|
||||
struct DMAC {
|
||||
//dma.cpp
|
||||
auto run() -> bool;
|
||||
auto transfer() -> void;
|
||||
auto step() -> bool;
|
||||
auto runPending() -> void;
|
||||
|
||||
n2 id;
|
||||
n1 romBurst;
|
||||
n1 active;
|
||||
n2 activeChannel;
|
||||
n1 stallingCPU;
|
||||
n1 writeCycle;
|
||||
|
||||
n1 active;
|
||||
i32 waiting;
|
||||
struct Channel {
|
||||
auto ready() -> bool;
|
||||
auto read() -> void;
|
||||
auto write() -> void;
|
||||
|
||||
n2 targetMode;
|
||||
n2 sourceMode;
|
||||
n1 repeat;
|
||||
n1 size;
|
||||
n1 drq;
|
||||
n2 timingMode;
|
||||
n1 irq;
|
||||
n1 enable;
|
||||
n2 id;
|
||||
|
||||
uintVN source;
|
||||
uintVN target;
|
||||
uintVN length;
|
||||
n1 active;
|
||||
i32 waiting;
|
||||
|
||||
n2 targetMode;
|
||||
n2 sourceMode;
|
||||
n1 repeat;
|
||||
n1 size;
|
||||
n1 drq;
|
||||
n2 timingMode;
|
||||
n1 irq;
|
||||
n1 enable;
|
||||
|
||||
struct Latch {
|
||||
uintVN source;
|
||||
uintVN target;
|
||||
uintVN length;
|
||||
u32 data;
|
||||
} latch;
|
||||
} dma[4];
|
||||
|
||||
struct Latch {
|
||||
uintVN source;
|
||||
uintVN target;
|
||||
uintVN length;
|
||||
u32 data;
|
||||
} latch;
|
||||
} channel[4];
|
||||
} dmac;
|
||||
|
||||
struct Timer {
|
||||
//timer.cpp
|
||||
@@ -175,7 +187,7 @@ struct CPU : ARM7TDMI, Thread, IO {
|
||||
n1 irqEnable;
|
||||
|
||||
n16 data[4];
|
||||
n8 data8;
|
||||
n16 dataMulti;
|
||||
} serial;
|
||||
|
||||
struct Keypad {
|
||||
@@ -252,9 +264,9 @@ struct CPU : ARM7TDMI, Thread, IO {
|
||||
n16 slot[8];
|
||||
n32 addr; //read location of slot buffer
|
||||
n32 load; //write location of slot buffer
|
||||
i32 wait = 1; //wait states for current load
|
||||
i32 cycle; //number of clocks elapsed on current load
|
||||
i32 wait = 1; //number of clocks before next slot load
|
||||
n1 stopped = 1;
|
||||
n1 ahead;
|
||||
} prefetch;
|
||||
|
||||
struct Coprocessor {
|
||||
@@ -270,10 +282,6 @@ struct CPU : ARM7TDMI, Thread, IO {
|
||||
n1 stopped;
|
||||
n1 booted; //set to true by the GBA BIOS
|
||||
n1 romAccess;
|
||||
n1 dmaRomAccess;
|
||||
n1 dmaRan;
|
||||
n1 dmaActive;
|
||||
n2 dmaActiveChannel;
|
||||
n1 timerLatched;
|
||||
n1 busLocked;
|
||||
n32 hcounter;
|
||||
|
||||
+72
-31
@@ -1,29 +1,59 @@
|
||||
inline auto CPU::DMA::run() -> bool {
|
||||
if(!active || waiting) return false;
|
||||
//returns true if DMA is currently active
|
||||
auto CPU::DMAC::step() -> bool {
|
||||
if(!active) {
|
||||
//DMA cannot start if bus is locked
|
||||
if(cpu.context.busLocked) return false;
|
||||
|
||||
transfer();
|
||||
if(irq) cpu.setInterruptFlag(CPU::Interrupt::DMA0 << id);
|
||||
if(drq && id == 3) cpu.setInterruptFlag(CPU::Interrupt::Cartridge);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto CPU::DMA::transfer() -> void {
|
||||
u32 seek = size ? 4 : 2;
|
||||
u32 mode = size ? Word : Half;
|
||||
|
||||
if(!cpu.context.dmaRan) {
|
||||
cpu.context.dmaRan = true;
|
||||
cpu.idle();
|
||||
//check if DMA is ready
|
||||
if(channel[0].ready() || channel[1].ready() || channel[2].ready() || channel[3].ready()) {
|
||||
//transfer control of bus from CPU to DMA
|
||||
active = true;
|
||||
cpu.ARM7TDMI::nonsequential = true;
|
||||
cpu.prefetchStep(1);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if(cpu.context.dmaActiveChannel != id) {
|
||||
//run write transaction, if read has finished prior
|
||||
if(writeCycle) { channel[activeChannel].write(); return true; }
|
||||
|
||||
//run read transaction
|
||||
if(channel[0].ready()) { channel[0].read(); return true; }
|
||||
if(channel[1].ready()) { channel[1].read(); return true; }
|
||||
if(channel[2].ready()) { channel[2].read(); return true; }
|
||||
if(channel[3].ready()) { channel[3].read(); return true; }
|
||||
|
||||
//DMA has finished - hand bus control back to CPU
|
||||
cpu.prefetchStep(1);
|
||||
romBurst = false;
|
||||
activeChannel = 0; //assign burst to a channel that cannot access ROM
|
||||
active = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto CPU::DMAC::runPending() -> void {
|
||||
stallingCPU = true;
|
||||
while(step());
|
||||
stallingCPU = false;
|
||||
}
|
||||
|
||||
inline auto CPU::DMAC::Channel::ready() -> bool {
|
||||
return active && !waiting;
|
||||
}
|
||||
|
||||
auto CPU::DMAC::Channel::read() -> void {
|
||||
u32 seek = size ? 4 : 2;
|
||||
u32 mode = DMA | (size ? Word : Half );
|
||||
|
||||
if(cpu.dmac.activeChannel != id) {
|
||||
//channel has switched - new burst transfer must be started
|
||||
cpu.context.dmaRomAccess = 0;
|
||||
cpu.context.dmaActiveChannel = id;
|
||||
cpu.dmac.romBurst = 0;
|
||||
cpu.dmac.activeChannel = id;
|
||||
}
|
||||
|
||||
if(latch.source() < 0x0200'0000) {
|
||||
cpu.idle(); //cannot access BIOS
|
||||
cpu.prefetchStep(1); //cannot access BIOS
|
||||
} else {
|
||||
n32 addr = latch.source();
|
||||
if(mode & Word) addr &= ~3;
|
||||
@@ -32,8 +62,20 @@ auto CPU::DMA::transfer() -> void {
|
||||
if(mode & Half) latch.data |= latch.data << 16;
|
||||
}
|
||||
|
||||
switch(sourceMode) {
|
||||
case 0: latch.source.data += seek; break;
|
||||
case 1: latch.source.data -= seek; break;
|
||||
}
|
||||
|
||||
cpu.dmac.writeCycle = true;
|
||||
}
|
||||
|
||||
auto CPU::DMAC::Channel::write() -> void {
|
||||
u32 seek = size ? 4 : 2;
|
||||
u32 mode = DMA | (size ? Word : Half );
|
||||
|
||||
if(latch.target() < 0x0200'0000) {
|
||||
cpu.idle(); //cannot access BIOS
|
||||
cpu.prefetchStep(1); //cannot access BIOS
|
||||
} else {
|
||||
n32 addr = latch.target();
|
||||
if(mode & Word) addr &= ~3;
|
||||
@@ -41,11 +83,6 @@ auto CPU::DMA::transfer() -> void {
|
||||
cpu.set(mode, addr, latch.data >> (addr & 2) * 8);
|
||||
}
|
||||
|
||||
switch(sourceMode) {
|
||||
case 0: latch.source.data += seek; break;
|
||||
case 1: latch.source.data -= seek; break;
|
||||
}
|
||||
|
||||
switch(targetMode) {
|
||||
case 0: latch.target.data += seek; break;
|
||||
case 1: latch.target.data -= seek; break;
|
||||
@@ -59,21 +96,25 @@ auto CPU::DMA::transfer() -> void {
|
||||
if(repeat == 1) latch.length = length;
|
||||
if(repeat == 0) enable = false;
|
||||
}
|
||||
|
||||
cpu.dmac.writeCycle = false;
|
||||
if(irq) cpu.setInterruptFlag(CPU::Interrupt::DMA0 << id);
|
||||
if(drq && id == 3) cpu.setInterruptFlag(CPU::Interrupt::Cartridge);
|
||||
}
|
||||
|
||||
auto CPU::dmaVblank() -> void {
|
||||
for(auto& dma : this->dma) {
|
||||
if(dma.enable && dma.timingMode == 1) dma.active = true;
|
||||
for(auto& channel : this->dmac.channel) {
|
||||
if(channel.enable && channel.timingMode == 1) channel.active = true;
|
||||
}
|
||||
}
|
||||
|
||||
auto CPU::dmaHblank() -> void {
|
||||
for(auto& dma : this->dma) {
|
||||
if(dma.enable && dma.timingMode == 2) dma.active = true;
|
||||
for(auto& channel : this->dmac.channel) {
|
||||
if(channel.enable && channel.timingMode == 2) channel.active = true;
|
||||
}
|
||||
}
|
||||
|
||||
auto CPU::dmaHDMA() -> void {
|
||||
auto& dma = this->dma[3];
|
||||
if(dma.enable && dma.timingMode == 3) dma.active = true;
|
||||
auto& channel = this->dmac.channel[3];
|
||||
if(channel.enable && channel.timingMode == 3) channel.active = true;
|
||||
}
|
||||
+44
-44
@@ -1,5 +1,5 @@
|
||||
auto CPU::readIO(n32 address) -> n8 {
|
||||
auto dma = [&]() -> DMA& { return this->dma[address / 12 & 3]; };
|
||||
auto channel = [&]() -> DMAC::Channel& { return this->dmac.channel[address / 12 & 3]; };
|
||||
auto timer = [&]() -> Timer& { return this->timer[address.bit(2,3)]; };
|
||||
|
||||
switch(address) {
|
||||
@@ -10,17 +10,17 @@ auto CPU::readIO(n32 address) -> n8 {
|
||||
|
||||
//DMA0CNT_H, DMA1CNT_H, DMA2CNT_H, DMA3CNT_H
|
||||
case 0x0400'00ba: case 0x0400'00c6: case 0x0400'00d2: case 0x0400'00de: return (
|
||||
dma().targetMode << 5
|
||||
| dma().sourceMode.bit(0) << 7
|
||||
channel().targetMode << 5
|
||||
| channel().sourceMode.bit(0) << 7
|
||||
);
|
||||
case 0x0400'00bb: case 0x0400'00c7: case 0x0400'00d3: case 0x0400'00df: return (
|
||||
dma().sourceMode.bit(1) << 0
|
||||
| dma().repeat << 1
|
||||
| dma().size << 2
|
||||
| dma().drq << 3
|
||||
| dma().timingMode << 4
|
||||
| dma().irq << 6
|
||||
| dma().enable << 7
|
||||
channel().sourceMode.bit(1) << 0
|
||||
| channel().repeat << 1
|
||||
| channel().size << 2
|
||||
| channel().drq << 3
|
||||
| channel().timingMode << 4
|
||||
| channel().irq << 6
|
||||
| channel().enable << 7
|
||||
);
|
||||
|
||||
//TM0CNT_L, TM1CNT_L, TM2CNT_L, TM3CNT_L
|
||||
@@ -61,8 +61,8 @@ auto CPU::readIO(n32 address) -> n8 {
|
||||
);
|
||||
|
||||
//SIOMLT_SEND (SIODATA8)
|
||||
case 0x0400'012a: return serial.data8;
|
||||
case 0x0400'012b: return 0;
|
||||
case 0x0400'012a: return serial.dataMulti.byte(0);
|
||||
case 0x0400'012b: return serial.dataMulti.byte(1); //upper 8 bits are always readable, but only used in multiplayer mode
|
||||
|
||||
//KEYINPUT
|
||||
case 0x04000130: {
|
||||
@@ -248,54 +248,54 @@ auto CPU::readIO(n32 address) -> n8 {
|
||||
}
|
||||
|
||||
auto CPU::writeIO(n32 address, n8 data) -> void {
|
||||
auto dma = [&]() -> DMA& { return this->dma[address / 12 & 3]; };
|
||||
auto channel = [&]() -> DMAC::Channel& { return this->dmac.channel[address / 12 & 3]; };
|
||||
auto timer = [&]() -> Timer& { return this->timer[address.bit(2,3)]; };
|
||||
|
||||
switch(address) {
|
||||
|
||||
//DMA0SAD, DMA1SAD, DMA2SAD, DMA3SAD
|
||||
case 0x0400'00b0: case 0x0400'00bc: case 0x0400'00c8: case 0x0400'00d4: dma().source.data.byte(0) = data; return;
|
||||
case 0x0400'00b1: case 0x0400'00bd: case 0x0400'00c9: case 0x0400'00d5: dma().source.data.byte(1) = data; return;
|
||||
case 0x0400'00b2: case 0x0400'00be: case 0x0400'00ca: case 0x0400'00d6: dma().source.data.byte(2) = data; return;
|
||||
case 0x0400'00b3: case 0x0400'00bf: case 0x0400'00cb: case 0x0400'00d7: dma().source.data.byte(3) = data; return;
|
||||
case 0x0400'00b0: case 0x0400'00bc: case 0x0400'00c8: case 0x0400'00d4: channel().source.data.byte(0) = data; return;
|
||||
case 0x0400'00b1: case 0x0400'00bd: case 0x0400'00c9: case 0x0400'00d5: channel().source.data.byte(1) = data; return;
|
||||
case 0x0400'00b2: case 0x0400'00be: case 0x0400'00ca: case 0x0400'00d6: channel().source.data.byte(2) = data; return;
|
||||
case 0x0400'00b3: case 0x0400'00bf: case 0x0400'00cb: case 0x0400'00d7: channel().source.data.byte(3) = data; return;
|
||||
|
||||
//DMA0DAD, DMA1DAD, DMA2DAD, DMA3DAD
|
||||
case 0x0400'00b4: case 0x0400'00c0: case 0x0400'00cc: case 0x0400'00d8: dma().target.data.byte(0) = data; return;
|
||||
case 0x0400'00b5: case 0x0400'00c1: case 0x0400'00cd: case 0x0400'00d9: dma().target.data.byte(1) = data; return;
|
||||
case 0x0400'00b6: case 0x0400'00c2: case 0x0400'00ce: case 0x0400'00da: dma().target.data.byte(2) = data; return;
|
||||
case 0x0400'00b7: case 0x0400'00c3: case 0x0400'00cf: case 0x0400'00db: dma().target.data.byte(3) = data; return;
|
||||
case 0x0400'00b4: case 0x0400'00c0: case 0x0400'00cc: case 0x0400'00d8: channel().target.data.byte(0) = data; return;
|
||||
case 0x0400'00b5: case 0x0400'00c1: case 0x0400'00cd: case 0x0400'00d9: channel().target.data.byte(1) = data; return;
|
||||
case 0x0400'00b6: case 0x0400'00c2: case 0x0400'00ce: case 0x0400'00da: channel().target.data.byte(2) = data; return;
|
||||
case 0x0400'00b7: case 0x0400'00c3: case 0x0400'00cf: case 0x0400'00db: channel().target.data.byte(3) = data; return;
|
||||
|
||||
//DMA0CNT_L, DMA1CNT_L, DMA2CNT_L, DMA3CNT_L
|
||||
case 0x0400'00b8: case 0x0400'00c4: case 0x0400'00d0: case 0x0400'00dc: dma().length.data.byte(0) = data; return;
|
||||
case 0x0400'00b9: case 0x0400'00c5: case 0x0400'00d1: case 0x0400'00dd: dma().length.data.byte(1) = data; return;
|
||||
case 0x0400'00b8: case 0x0400'00c4: case 0x0400'00d0: case 0x0400'00dc: channel().length.data.byte(0) = data; return;
|
||||
case 0x0400'00b9: case 0x0400'00c5: case 0x0400'00d1: case 0x0400'00dd: channel().length.data.byte(1) = data; return;
|
||||
|
||||
//DMA0CNT_H, DMA1CNT_H, DMA2CNT_H, DMA3CNT_H
|
||||
case 0x0400'00ba: case 0x0400'00c6: case 0x0400'00d2: case 0x0400'00de:
|
||||
dma().targetMode = data.bit(5,6);
|
||||
dma().sourceMode.bit(0) = data.bit(7);
|
||||
channel().targetMode = data.bit(5,6);
|
||||
channel().sourceMode.bit(0) = data.bit(7);
|
||||
return;
|
||||
case 0x0400'00bb: case 0x0400'00c7: case 0x0400'00d3: case 0x0400'00df: {
|
||||
bool enable = dma().enable;
|
||||
bool enable = channel().enable;
|
||||
if(address != 0x0400'00df) data.bit(3) = 0; //gamepad DRQ valid for DMA3 only
|
||||
|
||||
dma().sourceMode.bit(1) = data.bit(0);
|
||||
dma().repeat = data.bit(1);
|
||||
dma().size = data.bit(2);
|
||||
dma().drq = data.bit(3);
|
||||
dma().timingMode = data.bit(4,5);
|
||||
dma().irq = data.bit(6);
|
||||
dma().enable = data.bit(7);
|
||||
channel().sourceMode.bit(1) = data.bit(0);
|
||||
channel().repeat = data.bit(1);
|
||||
channel().size = data.bit(2);
|
||||
channel().drq = data.bit(3);
|
||||
channel().timingMode = data.bit(4,5);
|
||||
channel().irq = data.bit(6);
|
||||
channel().enable = data.bit(7);
|
||||
|
||||
if(!enable && dma().enable) { //0->1 transition
|
||||
if(dma().timingMode == 0) {
|
||||
dma().active = true; //immediate transfer mode
|
||||
dma().waiting = 2;
|
||||
if(!enable && channel().enable) { //0->1 transition
|
||||
if(channel().timingMode == 0) {
|
||||
channel().active = true; //immediate transfer mode
|
||||
channel().waiting = 2;
|
||||
}
|
||||
dma().latch.source = dma().source;
|
||||
dma().latch.target = dma().target;
|
||||
dma().latch.length = dma().length;
|
||||
} else if(!dma().enable) {
|
||||
dma().active = false;
|
||||
channel().latch.source = channel().source;
|
||||
channel().latch.target = channel().target;
|
||||
channel().latch.length = channel().length;
|
||||
} else if(!channel().enable) {
|
||||
channel().active = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -346,8 +346,8 @@ auto CPU::writeIO(n32 address, n8 data) -> void {
|
||||
return;
|
||||
|
||||
//SIOMLT_SEND (SIODATA8)
|
||||
case 0x0400'012a: serial.data8 = data; return;
|
||||
case 0x0400'012b: return;
|
||||
case 0x0400'012a: serial.dataMulti.byte(0) = data; return;
|
||||
case 0x0400'012b: serial.dataMulti.byte(1) = data; return; //upper 8 bits are always writable, but only used in multiplayer mode
|
||||
|
||||
//KEYCNT
|
||||
case 0x0400'0132:
|
||||
|
||||
+22
-18
@@ -1,48 +1,52 @@
|
||||
auto CPU::prefetchSync(n32 address) -> void {
|
||||
if(address == prefetch.addr) return;
|
||||
|
||||
if(prefetch.wait - prefetch.cycle == 1) step(1);
|
||||
auto CPU::prefetchSync(u32 mode, n32 address) -> void {
|
||||
if(prefetch.wait == 1) step(1);
|
||||
|
||||
n32 size = (mode & Word) ? 4 : 2;
|
||||
prefetch.stopped = false;
|
||||
prefetch.addr = address;
|
||||
prefetch.load = address;
|
||||
prefetch.wait = waitCartridge(Half | Nonsequential, prefetch.load);
|
||||
prefetch.cycle = 0;
|
||||
prefetch.ahead = false;
|
||||
prefetch.addr = address + size;
|
||||
prefetch.load = address + size;
|
||||
prefetch.wait = waitCartridge(Half | Sequential, prefetch.load);
|
||||
}
|
||||
|
||||
auto CPU::prefetchStep(u32 clocks) -> void {
|
||||
auto CPU::prefetchStepInternal(u32 clocks) -> void {
|
||||
step(clocks);
|
||||
if(!wait.prefetch || prefetch.stopped) return;
|
||||
|
||||
while(!prefetch.full() && clocks--) {
|
||||
prefetch.cycle++;
|
||||
if(prefetch.wait > prefetch.cycle) continue;
|
||||
while(clocks--) {
|
||||
if(prefetch.full()) {
|
||||
prefetch.stopped = true;
|
||||
prefetch.ahead = false;
|
||||
break;
|
||||
}
|
||||
if(--prefetch.wait) continue;
|
||||
prefetch.slot[prefetch.load >> 1 & 7] = cartridge.readRom<false>(Half | Nonsequential, prefetch.load); //todo: make access sequentiality consistent with timing used
|
||||
prefetch.load += 2;
|
||||
prefetch.wait = waitCartridge(Half | Sequential, prefetch.load);
|
||||
prefetch.cycle = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if(prefetch.full()) prefetch.stopped = true;
|
||||
auto CPU::prefetchStep(u32 clocks) -> void {
|
||||
if(wait.prefetch && !prefetch.stopped && prefetch.empty()) prefetch.ahead = true;
|
||||
prefetchStepInternal(clocks);
|
||||
}
|
||||
|
||||
auto CPU::prefetchReset() -> void {
|
||||
if(prefetch.wait - prefetch.cycle == 1) step(1);
|
||||
if(prefetch.wait == 1) step(1);
|
||||
|
||||
prefetch.stopped = true;
|
||||
prefetch.ahead = false;
|
||||
prefetch.wait = 0;
|
||||
prefetch.addr = 0;
|
||||
prefetch.load = 0;
|
||||
prefetch.cycle = 0;
|
||||
}
|
||||
|
||||
auto CPU::prefetchRead() -> n16 {
|
||||
if(prefetch.stopped && prefetch.empty()) {
|
||||
prefetch.stopped = false;
|
||||
prefetch.wait = waitCartridge(Half | Nonsequential, prefetch.load);
|
||||
prefetch.cycle = 0;
|
||||
}
|
||||
if(prefetch.empty()) prefetchStep(prefetch.wait - prefetch.cycle);
|
||||
if(prefetch.empty()) prefetchStepInternal(prefetch.wait);
|
||||
|
||||
n16 word = prefetch.slot[prefetch.addr >> 1 & 7];
|
||||
prefetch.addr += 2;
|
||||
|
||||
@@ -5,31 +5,36 @@ auto CPU::serialize(serializer& s) -> void {
|
||||
s(iwram);
|
||||
s(ewram);
|
||||
|
||||
for(auto& dma : this->dma) {
|
||||
s(dma.id);
|
||||
s(dma.active);
|
||||
s(dma.waiting);
|
||||
s(dma.targetMode);
|
||||
s(dma.sourceMode);
|
||||
s(dma.repeat);
|
||||
s(dma.size);
|
||||
s(dma.drq);
|
||||
s(dma.timingMode);
|
||||
s(dma.irq);
|
||||
s(dma.enable);
|
||||
s(dma.source.data);
|
||||
s(dma.source.mask);
|
||||
s(dma.target.data);
|
||||
s(dma.target.mask);
|
||||
s(dma.length.data);
|
||||
s(dma.length.mask);
|
||||
s(dma.latch.source.data);
|
||||
s(dma.latch.source.mask);
|
||||
s(dma.latch.target.data);
|
||||
s(dma.latch.target.mask);
|
||||
s(dma.latch.length.data);
|
||||
s(dma.latch.length.mask);
|
||||
s(dma.latch.data);
|
||||
s(dmac.romBurst);
|
||||
s(dmac.active);
|
||||
s(dmac.activeChannel);
|
||||
s(dmac.stallingCPU);
|
||||
s(dmac.writeCycle);
|
||||
for(auto& channel : this->dmac.channel) {
|
||||
s(channel.id);
|
||||
s(channel.active);
|
||||
s(channel.waiting);
|
||||
s(channel.targetMode);
|
||||
s(channel.sourceMode);
|
||||
s(channel.repeat);
|
||||
s(channel.size);
|
||||
s(channel.drq);
|
||||
s(channel.timingMode);
|
||||
s(channel.irq);
|
||||
s(channel.enable);
|
||||
s(channel.source.data);
|
||||
s(channel.source.mask);
|
||||
s(channel.target.data);
|
||||
s(channel.target.mask);
|
||||
s(channel.length.data);
|
||||
s(channel.length.mask);
|
||||
s(channel.latch.source.data);
|
||||
s(channel.latch.source.mask);
|
||||
s(channel.latch.target.data);
|
||||
s(channel.latch.target.mask);
|
||||
s(channel.latch.length.data);
|
||||
s(channel.latch.length.mask);
|
||||
s(channel.latch.data);
|
||||
}
|
||||
|
||||
for(auto& timer : this->timer) {
|
||||
@@ -56,7 +61,7 @@ auto CPU::serialize(serializer& s) -> void {
|
||||
s(serial.mode);
|
||||
s(serial.irqEnable);
|
||||
for(auto& value : serial.data) s(value);
|
||||
s(serial.data8);
|
||||
s(serial.dataMulti);
|
||||
|
||||
s(keypad.enable);
|
||||
s(keypad.condition);
|
||||
@@ -107,18 +112,14 @@ auto CPU::serialize(serializer& s) -> void {
|
||||
s(prefetch.addr);
|
||||
s(prefetch.load);
|
||||
s(prefetch.wait);
|
||||
s(prefetch.cycle);
|
||||
s(prefetch.stopped);
|
||||
s(prefetch.ahead);
|
||||
|
||||
s(context.clock);
|
||||
s(context.halted);
|
||||
s(context.stopped);
|
||||
s(context.booted);
|
||||
s(context.romAccess);
|
||||
s(context.dmaRomAccess);
|
||||
s(context.dmaRan);
|
||||
s(context.dmaActive);
|
||||
s(context.dmaActiveChannel);
|
||||
s(context.timerLatched);
|
||||
s(context.busLocked);
|
||||
s(context.hcounter);
|
||||
|
||||
@@ -59,12 +59,12 @@ auto CPU::runFIFO(u32 n) -> void {
|
||||
apu.fifo[n].read();
|
||||
if(apu.fifo[n].size() > 3) return;
|
||||
|
||||
auto& dma = this->dma[1 + n];
|
||||
if(dma.enable && dma.timingMode == 3) {
|
||||
dma.active = true;
|
||||
dma.waiting = 2;
|
||||
dma.targetMode = 2;
|
||||
dma.size = 1;
|
||||
dma.latch.length.data = 4;
|
||||
auto& channel = this->dmac.channel[1 + n];
|
||||
if(channel.enable && channel.timingMode == 3) {
|
||||
channel.active = true;
|
||||
channel.waiting = 2;
|
||||
channel.targetMode = 2;
|
||||
channel.size = 1;
|
||||
channel.latch.length.data = 4;
|
||||
}
|
||||
}
|
||||
@@ -58,8 +58,8 @@ auto Display::main() -> void {
|
||||
step(3);
|
||||
|
||||
if(io.vcounter == 162) {
|
||||
if(videoCapture) cpu.dma[3].enable = 0;
|
||||
videoCapture = !videoCapture && cpu.dma[3].timingMode == 3 && cpu.dma[3].enable;
|
||||
if(videoCapture) cpu.dmac.channel[3].enable = 0;
|
||||
videoCapture = !videoCapture && cpu.dmac.channel[3].timingMode == 3 && cpu.dmac.channel[3].enable;
|
||||
}
|
||||
if(io.vcounter >= 2 && io.vcounter < 162 && videoCapture) cpu.dmaHDMA();
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ namespace ares::GameBoyAdvance {
|
||||
Signed = 64, //sign extended
|
||||
Nonsequential = 128, //N cycle
|
||||
Sequential = 256, //S cycle
|
||||
DMA = 512, //DMA transaction
|
||||
};
|
||||
|
||||
struct Model {
|
||||
|
||||
@@ -24,6 +24,12 @@ auto PPU::load(Node::Object parent) -> void {
|
||||
node = parent->append<Node::Object>("PPU");
|
||||
|
||||
screen = node->append<Node::Video::Screen>("Screen", 240, 160);
|
||||
|
||||
colorEmulation = screen->append<Node::Setting::Boolean>("Color Emulation", true, [&](auto value) {
|
||||
screen->resetPalette();
|
||||
});
|
||||
colorEmulation->setDynamic(true);
|
||||
|
||||
screen->colors(1 << 15, {&PPU::color, this});
|
||||
screen->setSize(240, 160);
|
||||
screen->setScale(1.0, 1.0);
|
||||
@@ -31,11 +37,6 @@ auto PPU::load(Node::Object parent) -> void {
|
||||
screen->setViewport(0, 0, 240, 160);
|
||||
screen->refreshRateHint(system.frequency() / 4, 308, 228);
|
||||
|
||||
colorEmulation = screen->append<Node::Setting::Boolean>("Color Emulation", true, [&](auto value) {
|
||||
screen->resetPalette();
|
||||
});
|
||||
colorEmulation->setDynamic(true);
|
||||
|
||||
interframeBlending = screen->append<Node::Setting::Boolean>("Interframe Blending", true, [&](auto value) {
|
||||
screen->setInterframeBlending(value);
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
static const string SerializerVersion = "v144.3";
|
||||
static const string SerializerVersion = "v145.3";
|
||||
|
||||
auto System::serialize(bool synchronize) -> serializer {
|
||||
if(synchronize) scheduler.enter(Scheduler::Mode::Synchronize);
|
||||
|
||||
@@ -8,6 +8,8 @@ ares_components(
|
||||
m24c
|
||||
)
|
||||
|
||||
target_link_libraries(ares PRIVATE qon)
|
||||
|
||||
ares_add_sources(
|
||||
CORE #
|
||||
md
|
||||
@@ -142,6 +144,7 @@ ares_add_sources(
|
||||
mcd/cdc.cpp
|
||||
mcd/cdd-dac.cpp
|
||||
mcd/cdd.cpp
|
||||
mcd/megald.cpp
|
||||
mcd/debugger.cpp
|
||||
mcd/gpu.cpp
|
||||
mcd/io-external.cpp
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
auto MCD::read(n1 upper, n1 lower, n24 address, n16 data) -> n16 {
|
||||
address.bit(20,23) = 0; //mirroring
|
||||
|
||||
if(address >= 0x000000 && address <= 0x07ffff) {
|
||||
return pram[address >> 1];
|
||||
}
|
||||
@@ -27,17 +25,22 @@ auto MCD::read(n1 upper, n1 lower, n24 address, n16 data) -> n16 {
|
||||
return data;
|
||||
}
|
||||
|
||||
if(address >= 0x0e0000 && address <= 0x0effff) {
|
||||
if(MegaLD() && address >= 0xfd0000 && address <= 0xfdffff) {
|
||||
if (!lower) return data;
|
||||
return ld.read(address | 0x1);
|
||||
}
|
||||
|
||||
if(address >= 0xfe0000 && address <= 0xfeffff) {
|
||||
if(!lower) return data;
|
||||
return bram.read(address >> 1);
|
||||
}
|
||||
|
||||
if(address >= 0x0f0000 && address <= 0x0f7fff) {
|
||||
if(address >= 0xff0000 && address <= 0xff7fff) {
|
||||
if(!lower) return data;
|
||||
return pcm.read(address >> 1, data);
|
||||
}
|
||||
|
||||
if(address >= 0x0f8000 && address <= 0x0fffff) {
|
||||
if(address >= 0xff8000 && address <= 0xffffff) {
|
||||
return readIO(upper, lower, address, data);
|
||||
}
|
||||
|
||||
@@ -45,8 +48,6 @@ auto MCD::read(n1 upper, n1 lower, n24 address, n16 data) -> n16 {
|
||||
}
|
||||
|
||||
auto MCD::write(n1 upper, n1 lower, n24 address, n16 data) -> void {
|
||||
address.bit(20,23) = 0; //mirroring
|
||||
|
||||
if(address >= 0x000000 && address <= 0x07ffff
|
||||
&& address >= (n24)io.pramProtect << 9) {
|
||||
if(upper) pram[address >> 1].byte(1) = data.byte(1);
|
||||
@@ -97,17 +98,22 @@ auto MCD::write(n1 upper, n1 lower, n24 address, n16 data) -> void {
|
||||
}
|
||||
}
|
||||
|
||||
if(address >= 0x0e0000 && address <= 0x0effff) {
|
||||
if(MegaLD() && address >= 0xfd0000 && address <= 0xfdffff) {
|
||||
if (!lower) return;
|
||||
return ld.write(address | 0x1, data);
|
||||
}
|
||||
|
||||
if(address >= 0xfe0000 && address <= 0xfeffff) {
|
||||
if(!lower) return;
|
||||
return bram.write(address >> 1, data);
|
||||
}
|
||||
|
||||
if(address >= 0x0f0000 && address <= 0x0f7fff) {
|
||||
if(address >= 0xff0000 && address <= 0xff7fff) {
|
||||
if(!lower) return;
|
||||
return pcm.write(address >> 1, data);
|
||||
}
|
||||
|
||||
if(address >= 0x0f8000 && address <= 0x0fffff) {
|
||||
if(address >= 0xff8000 && address <= 0xffffff) {
|
||||
return writeIO(upper, lower, address, data);
|
||||
}
|
||||
|
||||
|
||||
+368
-16
@@ -43,7 +43,7 @@ auto MCD::CDD::clock() -> void {
|
||||
readSubcode();
|
||||
if(session.tracks[io.track].isAudio()) break;
|
||||
mcd.cdc.decode(io.sector);
|
||||
advance();
|
||||
if (!MegaLD()) advance();
|
||||
} break;
|
||||
|
||||
case Status::Tracking: [[fallthrough]]; // TODO: implement tracking logic
|
||||
@@ -57,30 +57,239 @@ auto MCD::CDD::clock() -> void {
|
||||
}
|
||||
|
||||
auto MCD::CDD::advance() -> void {
|
||||
if(auto track = session.inTrack(io.sector + 1)) {
|
||||
// Calculate the sector advance amount based on the MegaLD playback modes, if applicable.
|
||||
i32 sectorAdvanceOffset = 1;
|
||||
if (MegaLD()) {
|
||||
switch (mcd.ld.currentPlaybackMode) {
|
||||
case 0x00:
|
||||
// Normal playback. Nothing to do.
|
||||
sectorAdvanceOffset = 1;
|
||||
io.sectorRepeatCount = 0;
|
||||
break;
|
||||
case 0x01:
|
||||
// Frame skipping. This only affects the analog video update rate, and is handled in MCD::LD::updateCurrentVideoFrameNumber.
|
||||
sectorAdvanceOffset = 1;
|
||||
io.sectorRepeatCount = 0;
|
||||
break;
|
||||
case 0x02:
|
||||
// Frame stepping
|
||||
switch (mcd.ld.currentPlaybackSpeed) {
|
||||
case 0x00:
|
||||
//-0x0: 0 frames. This pauses playback in frame stepping mode, and performs a normal playback in frame skipping mode.
|
||||
sectorAdvanceOffset = 0;
|
||||
io.sectorRepeatCount = 0;
|
||||
break;
|
||||
case 0x01:
|
||||
//-0x1: 1 frame only. The image will not update after the initial frame. Note that under frame step mode, output register
|
||||
// 0x07 will report this step speed as 0x1 only until the single frame step has been performed, after which, the output
|
||||
// register will now state a value of 0x0. Also note that under frame skip mode, the output register will output 0x19 for
|
||||
// an input register state of 0x11, in other words, the "direction" bit is set to "reverse" when a 1-frame frame skip mode
|
||||
// is activated.
|
||||
if (io.sectorRepeatCount == 0) {
|
||||
sectorAdvanceOffset = 1;
|
||||
io.sectorRepeatCount = 1;
|
||||
} else {
|
||||
sectorAdvanceOffset = 0;
|
||||
}
|
||||
break;
|
||||
case 0x02:
|
||||
//-0x2: 15 FPS instead of 30 (12 seconds for 180 frames) - Display frame 2x
|
||||
sectorAdvanceOffset = (++io.sectorRepeatCount >= 2 ? 1 : 0);
|
||||
break;
|
||||
case 0x03:
|
||||
//-0x3: 7.5 FPS instead of 30 (24 seconds for 180 frames) - Display frame 4x
|
||||
sectorAdvanceOffset = (++io.sectorRepeatCount >= 4 ? 1 : 0);
|
||||
break;
|
||||
case 0x04:
|
||||
//-0x4: 3.75 FPS instead of 30 (24 seconds for 90 frames) - Display frame 8x
|
||||
sectorAdvanceOffset = (++io.sectorRepeatCount >= 8 ? 1 : 0);
|
||||
break;
|
||||
case 0x05:
|
||||
//-0x5: 1.875 FPS instead of 30 (48 seconds for 90 frames) - Display frame 16x
|
||||
sectorAdvanceOffset = (++io.sectorRepeatCount >= 16 ? 1 : 0);
|
||||
break;
|
||||
case 0x06:
|
||||
//-0x6: 1 FPS instead of 30 (45 seconds for 45 frames) - Display frame 30x
|
||||
sectorAdvanceOffset = (++io.sectorRepeatCount >= 30 ? 1 : 0);
|
||||
break;
|
||||
case 0x07:
|
||||
//-0x7: ~0.33r FPS instead of 30 (30 seconds for 10 frames) - Display frame 90x
|
||||
sectorAdvanceOffset = (++io.sectorRepeatCount >= 90 ? 1 : 0);
|
||||
break;
|
||||
}
|
||||
sectorAdvanceOffset = (mcd.ld.currentPlaybackDirection ? (i32)(-sectorAdvanceOffset) : sectorAdvanceOffset);
|
||||
io.sectorRepeatCount = (sectorAdvanceOffset != 0 ? (i32)0 : io.sectorRepeatCount);
|
||||
break;
|
||||
case 0x03:
|
||||
// Fast forward
|
||||
switch (mcd.ld.currentPlaybackSpeed) {
|
||||
case 0x00:
|
||||
sectorAdvanceOffset = 1;
|
||||
break;
|
||||
case 0x01:
|
||||
sectorAdvanceOffset = 2;
|
||||
break;
|
||||
case 0x02:
|
||||
sectorAdvanceOffset = 3;
|
||||
break;
|
||||
case 0x03:
|
||||
sectorAdvanceOffset = 8;
|
||||
break;
|
||||
case 0x04:
|
||||
sectorAdvanceOffset = 14;
|
||||
break;
|
||||
case 0x05:
|
||||
sectorAdvanceOffset = 20;
|
||||
break;
|
||||
case 0x06:
|
||||
case 0x07:
|
||||
//##TODO## Unimplemented
|
||||
// -0x7/0x06: Search mode. Plays 0.75 seconds of footage forwards in time, with audio, then jumps either forward or back
|
||||
// 4 seconds from the resulting point. Note that setting this register to 0x07 is the same as 0x06, but 0x06 is the value
|
||||
// that is reported back as the current mode in either case by output register 0x07.
|
||||
break;
|
||||
}
|
||||
sectorAdvanceOffset = (mcd.ld.currentPlaybackDirection ? (i32)(-sectorAdvanceOffset) : sectorAdvanceOffset);
|
||||
io.sectorRepeatCount = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(auto track = session.inTrack(io.sector + sectorAdvanceOffset)) {
|
||||
io.track = track();
|
||||
io.sector++;
|
||||
io.sector += sectorAdvanceOffset;
|
||||
io.sample = 0;
|
||||
|
||||
if (MegaLD()) {
|
||||
mcd.ld.updateCurrentVideoFrameNumber(io.sector);
|
||||
|
||||
if (stopPointEnabled && (io.sector == targetStopPoint)) {
|
||||
mcd.ld.handleStopPointReached(io.sector);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
io.status = Status::LeadOut;
|
||||
io.track = 0xaa;
|
||||
if ((io.sector + sectorAdvanceOffset) < 0) {
|
||||
io.status = Status::LeadIn;
|
||||
io.track = 0xa00;
|
||||
} else {
|
||||
io.status = Status::LeadOut;
|
||||
io.track = 0xaa;
|
||||
}
|
||||
}
|
||||
|
||||
auto MCD::CDD::sample() -> void {
|
||||
i16 left = 0;
|
||||
i16 right = 0;
|
||||
if(io.status == Status::Playing) {
|
||||
if(session.tracks[io.track].isAudio()) {
|
||||
mcd.fd->seek((abs(session.leadIn.lba) + io.sector) * 2448 + io.sample);
|
||||
left = mcd.fd->readl(2);
|
||||
right = mcd.fd->readl(2);
|
||||
i16 digitalSampleLeft = 0;
|
||||
i16 digitalSampleRight = 0;
|
||||
if (io.status == Status::Playing) {
|
||||
if (MegaLD() || session.tracks[io.track].isAudio()) {
|
||||
if (session.tracks[io.track].isAudio()) {
|
||||
mcd.fd->seek((abs(session.leadIn.lba) + io.sector) * 2448 + io.sample);
|
||||
digitalSampleLeft = mcd.fd->readl(2);
|
||||
digitalSampleRight = mcd.fd->readl(2);
|
||||
}
|
||||
io.sample += 4;
|
||||
if(io.sample >= 2352) advance();
|
||||
}
|
||||
}
|
||||
dac.sample(left, right);
|
||||
|
||||
if (MegaLD()) {
|
||||
// Determine the state of our overall audio mixing mode settings
|
||||
//##FIX## Note that we don't take the unusual "latching" behaviour of the digital audio mixing disabled state
|
||||
//persisting when switching into analog mixing mode 0 here, as described in the notes for input register 0x01. It
|
||||
//is highly, highly unlikely anything relies on this however. If there was a scenario however where only VDP
|
||||
//graphics were on the screen, and digital audio was intended to play, but was instead silent, it's possible this
|
||||
//is the culprit. Fixing this would require latching a mute state into a register, rather than evaluating the
|
||||
//input register state live like we do here.
|
||||
auto analogMixingMode = mcd.ld.inputRegs[0x01].bit(7, 6);
|
||||
auto audioMixingInputSelection = mcd.ld.inputRegs[0x0D].bit(7, 6);
|
||||
bool digitalAudioMixingDisabled = (analogMixingMode > 0) && ((audioMixingInputSelection == 2) || ((analogMixingMode == 1) && mcd.ld.inputRegs[0x0D].bit(4)));
|
||||
bool digitalAudioAttenuationDisabled = (analogMixingMode == 0) || (audioMixingInputSelection == 3);
|
||||
bool analogAudioMixingDisabled = (analogMixingMode == 0) || ((analogMixingMode == 1) && !mcd.ld.inputRegs[0x0D].bit(4));
|
||||
|
||||
// Disable digital audio if it is turned off
|
||||
if (digitalAudioMixingDisabled) {
|
||||
digitalSampleLeft = 0;
|
||||
digitalSampleRight = 0;
|
||||
}
|
||||
|
||||
// Attenuate the digital audio using the digital audio fader
|
||||
if (!digitalAudioAttenuationDisabled) {
|
||||
float digitalAudioFader = (float)mcd.ld.inputRegs[0x0F] / (float)((1 << 8) - 1);
|
||||
digitalSampleLeft = (int16_t)((float)digitalSampleLeft * digitalAudioFader);
|
||||
digitalSampleRight = (int16_t)((float)digitalSampleRight * digitalAudioFader);
|
||||
}
|
||||
|
||||
// Take digital left/right exclusive register state into account
|
||||
if ((analogMixingMode != 0) && mcd.ld.inputRegs[0x0D].bit(0) && mcd.ld.inputRegs[0x0D].bit(1)) {
|
||||
digitalSampleLeft /= 2;
|
||||
digitalSampleRight /= 2;
|
||||
} else if (mcd.ld.inputRegs[0x0D].bit(0)) {
|
||||
digitalSampleRight = digitalSampleLeft;
|
||||
} else if (mcd.ld.inputRegs[0x0D].bit(1)) {
|
||||
digitalSampleLeft = digitalSampleRight;
|
||||
}
|
||||
|
||||
// Retrieve the next analog audio sample
|
||||
i16 analogSampleLeft = 0;
|
||||
i16 analogSampleRight = 0;
|
||||
auto analogAudioSamplePos = (io.sector * 2352) + io.sample + (mcd.ld.analogAudioLeadingAudioSamples * 4);
|
||||
if ((analogAudioSamplePos + 3) < mcd.ld.analogAudioRawDataView.size()) {
|
||||
analogSampleLeft = (i16)((u16)mcd.ld.analogAudioRawDataView[analogAudioSamplePos + 0] | (u16)(mcd.ld.analogAudioRawDataView[analogAudioSamplePos + 1] << 8));
|
||||
analogSampleRight = (i16)((u16)mcd.ld.analogAudioRawDataView[analogAudioSamplePos + 2] | (u16)(mcd.ld.analogAudioRawDataView[analogAudioSamplePos + 3] << 8));
|
||||
}
|
||||
|
||||
// Disable analog audio if it is turned off
|
||||
if (analogAudioMixingDisabled) {
|
||||
analogSampleLeft = 0;
|
||||
analogSampleRight = 0;
|
||||
}
|
||||
|
||||
// Take analog mute and left/right exclusive register state into account
|
||||
if (mcd.ld.inputRegs[0x0E].bit(7) || (mcd.ld.inputRegs[0x0E].bit(0) && mcd.ld.inputRegs[0x0E].bit(1))) {
|
||||
analogSampleLeft = 0;
|
||||
analogSampleRight = 0;
|
||||
} else if (mcd.ld.inputRegs[0x0E].bit(0)) {
|
||||
analogSampleRight = analogSampleLeft;
|
||||
} else if (mcd.ld.inputRegs[0x0E].bit(1)) {
|
||||
analogSampleLeft = analogSampleRight;
|
||||
}
|
||||
|
||||
// Attenuate analog audio by the current attenuation register settings. Note that as per the hardware, the
|
||||
// maximum attenuation value of 0xFF doesn't give total silence.
|
||||
//##FIX## We currently make the "fade to mute" flags apply immediately. This is techically incorrect, however
|
||||
//since most likely nothing relies on this, and it's a bit of a pain to do, we just make them take effect
|
||||
//immediately here. If we want to do this properly, we need to take accurate measurements on the hardware for
|
||||
//the time taken for the fade, then tie a process into the clock event to reduce a secondary attenuation value
|
||||
//by the correct amount to achieve that timing as the clock advances. The fact that triggering a fade for a
|
||||
//second channel applies immediately if the other channel has already been faded to mute, shows there's in
|
||||
//fact a single secondary attenuation counter/register behind the scenes which handles this, which both
|
||||
//channels share.
|
||||
unsigned int analogAudioVolumeScaleLeft = 0x100 - (unsigned int)mcd.ld.analogAudioAttenuationLeft;
|
||||
unsigned int analogAudioVolumeScaleRight = 0x100 - (unsigned int)mcd.ld.analogAudioAttenuationRight;
|
||||
analogAudioVolumeScaleLeft = (mcd.ld.analogAudioFadeToMutedLeft ? 0 : analogAudioVolumeScaleLeft);
|
||||
analogAudioVolumeScaleRight = (mcd.ld.analogAudioFadeToMutedRight ? 0 : analogAudioVolumeScaleRight);
|
||||
analogSampleLeft = (i16)(((int)analogSampleLeft * (int)analogAudioVolumeScaleLeft) >> 8);
|
||||
analogSampleRight = (i16)(((int)analogSampleRight * (int)analogAudioVolumeScaleRight) >> 8);
|
||||
|
||||
// Audio is disabled in frame stepping mode
|
||||
if (mcd.ld.currentPlaybackMode == 0x02) {
|
||||
digitalSampleLeft = 0;
|
||||
digitalSampleRight = 0;
|
||||
analogSampleLeft = 0;
|
||||
analogSampleRight = 0;
|
||||
}
|
||||
|
||||
// Mix analog and digital audio together
|
||||
i16 combinedSampleLeft = (int16_t)std::clamp((int)digitalSampleLeft + (int)analogSampleLeft, (int)std::numeric_limits<int16_t>::min(), (int)std::numeric_limits<int16_t>::max());
|
||||
i16 combinedSampleRight = (int16_t)std::clamp((int)digitalSampleRight + (int)analogSampleRight, (int)std::numeric_limits<int16_t>::min(), (int)std::numeric_limits<int16_t>::max());
|
||||
|
||||
// Output the combined sample
|
||||
dac.sample(combinedSampleLeft, combinedSampleRight);
|
||||
} else {
|
||||
dac.sample(digitalSampleLeft, digitalSampleRight);
|
||||
}
|
||||
}
|
||||
|
||||
//convert sector# to normalized sector position on the CD-ROM surface for seek latency calculation
|
||||
@@ -143,7 +352,7 @@ auto MCD::CDD::process() -> void {
|
||||
} break;
|
||||
|
||||
case Command::Stop: {
|
||||
io.status = mcd.fd ? Status::Stopped : Status::NoDisc;
|
||||
stop();
|
||||
status[1] = 0x0;
|
||||
status[2] = 0x0; status[3] = 0x0;
|
||||
status[4] = 0x0; status[5] = 0x0;
|
||||
@@ -266,11 +475,11 @@ auto MCD::CDD::process() -> void {
|
||||
} break;
|
||||
|
||||
case Command::Pause: {
|
||||
io.status = Status::Paused;
|
||||
pause();
|
||||
} break;
|
||||
|
||||
case Command::Play: {
|
||||
io.status = Status::Playing;
|
||||
play();
|
||||
} break;
|
||||
|
||||
// TrackSkip command directs movement of the laser/sled by a specified radial track count.
|
||||
@@ -351,6 +560,11 @@ auto MCD::CDD::insert() -> void {
|
||||
io.sector = session.leadIn.lba;
|
||||
io.sample = 0;
|
||||
io.track = 0;
|
||||
|
||||
laserdiscLoaded = false;
|
||||
if ((mcd.pak->attribute("system") == "MegaLD") && (mcd.ld.mmi.media().size() > 0)) {
|
||||
laserdiscLoaded = mcd.ld.mmi.media()[0].type.match("LD");
|
||||
}
|
||||
}
|
||||
|
||||
auto MCD::CDD::eject() -> void {
|
||||
@@ -360,6 +574,142 @@ auto MCD::CDD::eject() -> void {
|
||||
io.sector = 0;
|
||||
io.sample = 0;
|
||||
io.track = 0;
|
||||
|
||||
// If this is a MegaLD system, release the analog audio stream when changing discs.
|
||||
if (MegaLD()) {
|
||||
mcd.ld.analogAudioRawDataView = {};
|
||||
}
|
||||
}
|
||||
|
||||
auto MCD::CDD::stop() -> void {
|
||||
io.status = mcd.fd ? Status::Stopped : Status::NoDisc;
|
||||
}
|
||||
|
||||
auto MCD::CDD::play() -> void {
|
||||
if (io.status == Status::Seeking) {
|
||||
io.seeking = Status::Playing;
|
||||
} else {
|
||||
io.status = Status::Playing;
|
||||
}
|
||||
}
|
||||
|
||||
auto MCD::CDD::pause() -> void {
|
||||
if (io.status == Status::Seeking) {
|
||||
io.seeking = Status::Paused;
|
||||
} else {
|
||||
io.status = Status::Paused;
|
||||
}
|
||||
}
|
||||
|
||||
auto MCD::CDD::seekToTime(u8 hour, u8 minute, u8 second, u8 frame, bool startPaused) -> void {
|
||||
s32 lba = lbaFromTime(hour, minute, second, frame);
|
||||
seekToSector(lba, startPaused);
|
||||
}
|
||||
|
||||
auto MCD::CDD::seekToRelativeTime(n7 track, u8 minute, u8 second, u8 frame, bool startPaused) -> void {
|
||||
auto firstTrack = session.firstTrack;
|
||||
auto lastTrack = session.lastTrack;
|
||||
if ((track < firstTrack) || (track > lastTrack)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto targetTrackLba = session.tracks[track].indices[1].lba;
|
||||
s32 lba = targetTrackLba + ((s32)minute * 60 * 75 + (s32)second * 75 + (s32)frame);
|
||||
seekToSector(lba, startPaused);
|
||||
}
|
||||
|
||||
// Note that despite the generic name, this implementation is currently MegaLD specific.
|
||||
auto MCD::CDD::seekToSector(s32 lba, bool startPaused) -> void {
|
||||
counter = 0;
|
||||
io.status = Status::Seeking;
|
||||
io.seeking = (startPaused ? Status::Paused : Status::Playing);
|
||||
//##TODO## Calculate correct latency for the LaserActive
|
||||
io.latency = 20.0 + 10.0 * abs(position(io.sector) - position(lba));
|
||||
io.sector = lba;
|
||||
io.sample = 0;
|
||||
if (auto track = session.inTrack(io.sector)) io.track = track();
|
||||
}
|
||||
|
||||
auto MCD::CDD::seekToTrack(n7 track, bool startPaused) -> void {
|
||||
auto lba = session.tracks[track].indices[1].lba;
|
||||
seekToSector(lba, startPaused);
|
||||
}
|
||||
|
||||
auto MCD::CDD::getTrackCount() -> n7 {
|
||||
return (session.lastTrack - session.firstTrack) + 1;
|
||||
}
|
||||
|
||||
auto MCD::CDD::getFirstTrack() -> n7 {
|
||||
return session.firstTrack;
|
||||
}
|
||||
|
||||
auto MCD::CDD::getLastTrack() -> n7 {
|
||||
return session.lastTrack;
|
||||
}
|
||||
|
||||
auto MCD::CDD::getCurrentTrack() -> n7 {
|
||||
return io.track;
|
||||
}
|
||||
|
||||
auto MCD::CDD::getCurrentSector() -> s32 {
|
||||
return io.sector;
|
||||
}
|
||||
|
||||
auto MCD::CDD::getCurrentTimecode(u8& minute, u8& second, u8& frame) -> void {
|
||||
auto [lminute, lsecond, lframe] = CD::MSF(io.sector);
|
||||
minute = lminute;
|
||||
second = lsecond;
|
||||
frame = lframe;
|
||||
}
|
||||
|
||||
auto MCD::CDD::getCurrentTrackRelativeTimecode(u8& minute, u8& second, u8& frame) -> void {
|
||||
auto [lminute, lsecond, lframe] = CD::MSF(io.sector - session.tracks[io.track].indices[1].lba);
|
||||
minute = lminute;
|
||||
second = lsecond;
|
||||
frame = lframe;
|
||||
}
|
||||
|
||||
auto MCD::CDD::getLeadOutTimecode(u8& minute, u8& second, u8& frame) -> void {
|
||||
auto [lminute, lsecond, lframe] = CD::MSF(session.leadOut.lba);
|
||||
minute = lminute;
|
||||
second = lsecond;
|
||||
frame = lframe;
|
||||
}
|
||||
|
||||
auto MCD::CDD::getTrackTocData(n7 track, u8& flags, u8& minute, u8& second, u8& frame) -> void {
|
||||
auto [lminute, lsecond, lframe] = CD::MSF(session.tracks[track].indices[1].lba);
|
||||
minute = lminute;
|
||||
second = lsecond;
|
||||
frame = lframe;
|
||||
flags = session.tracks[track].control;
|
||||
}
|
||||
|
||||
auto MCD::CDD::lbaFromTime(u8 hour, u8 minute, u8 second, u8 frame) -> s32 {
|
||||
s32 lba = ((((((s32)hour * 60) + (s32)minute) * 60) + (s32)second) * 75) + (s32)frame;
|
||||
return lba;
|
||||
}
|
||||
|
||||
auto MCD::CDD::isTrackAudio(n7 track) -> bool {
|
||||
return session.tracks[track].isAudio();
|
||||
}
|
||||
|
||||
auto MCD::CDD::isDiscLoaded() -> bool {
|
||||
return (mcd.disc && mcd.fd);
|
||||
}
|
||||
|
||||
auto MCD::CDD::isDiscLaserdisc() -> bool {
|
||||
if (!isDiscLoaded()) {
|
||||
return false;
|
||||
}
|
||||
return laserdiscLoaded;
|
||||
}
|
||||
|
||||
auto MCD::CDD::isLaserdiscClv() -> bool {
|
||||
return isDiscLaserdisc() && mcd.ld.video.isCLV;
|
||||
}
|
||||
|
||||
auto MCD::CDD::isLaserdiscDigitalAudioPresent() -> bool {
|
||||
return mcd.ld.video.hasDigitalAudio;
|
||||
}
|
||||
|
||||
auto MCD::CDD::power(bool reset) -> void {
|
||||
@@ -373,4 +723,6 @@ auto MCD::CDD::power(bool reset) -> void {
|
||||
for(auto& data : command) data = 0x0;
|
||||
insert();
|
||||
checksum();
|
||||
stopPointEnabled = false;
|
||||
targetStopPoint = 0;
|
||||
}
|
||||
+22
-4
@@ -1,4 +1,7 @@
|
||||
#include <md/md.hpp>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <optional>
|
||||
|
||||
namespace ares::MegaDrive {
|
||||
|
||||
@@ -14,6 +17,7 @@ MCD mcd;
|
||||
#include "cdc-transfer.cpp"
|
||||
#include "cdd.cpp"
|
||||
#include "cdd-dac.cpp"
|
||||
#include "megald.cpp"
|
||||
#include "timer.cpp"
|
||||
#include "gpu.cpp"
|
||||
#include "pcm.cpp"
|
||||
@@ -27,7 +31,7 @@ auto MCD::load(Node::Object parent) -> void {
|
||||
tray->setFamily("Mega CD");
|
||||
tray->setType("Compact Disc");
|
||||
tray->setHotSwappable(true);
|
||||
tray->setAllocate([&](auto name) { return allocate(tray); });
|
||||
tray->setAllocate([&](auto name) { return allocate(tray, name); });
|
||||
tray->setConnect([&] { return connect(); });
|
||||
tray->setDisconnect([&] { return disconnect(); });
|
||||
|
||||
@@ -53,6 +57,7 @@ auto MCD::unload() -> void {
|
||||
debugger = {};
|
||||
cdd.unload(node);
|
||||
pcm.unload(node);
|
||||
ld.unload();
|
||||
|
||||
bios.reset();
|
||||
pram.reset();
|
||||
@@ -64,8 +69,10 @@ auto MCD::unload() -> void {
|
||||
node.reset();
|
||||
}
|
||||
|
||||
auto MCD::allocate(Node::Port parent) -> Node::Peripheral {
|
||||
return disc = parent->append<Node::Peripheral>("Mega CD Disc");
|
||||
auto MCD::allocate(Node::Port parent, string name) -> Node::Peripheral {
|
||||
disc = parent->append<Node::Peripheral>("Mega CD Disc");
|
||||
disc->setAttribute("media", name);
|
||||
return disc;
|
||||
}
|
||||
|
||||
auto MCD::connect() -> void {
|
||||
@@ -78,7 +85,12 @@ auto MCD::connect() -> void {
|
||||
information = {};
|
||||
information.title = pak->attribute("title");
|
||||
|
||||
fd = pak->read("cd.rom");
|
||||
if(MegaLD()) {
|
||||
ld.load(pak->attribute("location"));
|
||||
} else {
|
||||
fd = pak->read("cd.rom");
|
||||
}
|
||||
|
||||
if(!fd) return disconnect();
|
||||
|
||||
cdd.insert();
|
||||
@@ -93,6 +105,9 @@ auto MCD::disconnect() -> void {
|
||||
fd.reset();
|
||||
pak.reset();
|
||||
information = {};
|
||||
if(MegaLD()) {
|
||||
ld.notifyDiscEjected();
|
||||
}
|
||||
}
|
||||
|
||||
auto MCD::save() -> void {
|
||||
@@ -192,6 +207,9 @@ auto MCD::power(bool reset) -> void {
|
||||
gpu.power(reset);
|
||||
pcm.power(reset);
|
||||
resetPeripheral(reset);
|
||||
if (MegaLD()) {
|
||||
ld.power(reset);
|
||||
}
|
||||
}
|
||||
|
||||
auto MCD::resetCpu() -> void {
|
||||
|
||||
+144
-2
@@ -33,10 +33,10 @@ struct MCD : M68000, Thread {
|
||||
auto title() const -> string { return information.title; }
|
||||
|
||||
//mcd.cpp
|
||||
auto load(Node::Object) -> void;
|
||||
auto load(Node::Object parent) -> void;
|
||||
auto unload() -> void;
|
||||
|
||||
auto allocate(Node::Port) -> Node::Peripheral;
|
||||
auto allocate(Node::Port, string name) -> Node::Peripheral;
|
||||
auto connect() -> void;
|
||||
auto disconnect() -> void;
|
||||
|
||||
@@ -312,6 +312,28 @@ struct MCD : M68000, Thread {
|
||||
auto checksum() -> void;
|
||||
auto insert() -> void;
|
||||
auto eject() -> void;
|
||||
auto stop() -> void;
|
||||
auto play() -> void;
|
||||
auto pause() -> void;
|
||||
auto seekToTime(u8 hour, u8 minute, u8 second, u8 frame, bool startPaused) -> void;
|
||||
auto seekToRelativeTime(n7 track, u8 minute, u8 second, u8 frame, bool startPaused) -> void;
|
||||
auto seekToSector(s32 lba, bool startPaused) -> void;
|
||||
auto seekToTrack(n7 track, bool startPaused) -> void;
|
||||
auto getTrackCount() -> n7;
|
||||
auto getFirstTrack() -> n7;
|
||||
auto getLastTrack() -> n7;
|
||||
auto getCurrentTrack() -> n7;
|
||||
auto getCurrentSector() -> s32;
|
||||
auto getCurrentTimecode(u8& minute, u8& second, u8& frame) -> void;
|
||||
auto getCurrentTrackRelativeTimecode(u8& minute, u8& second, u8& frame) -> void;
|
||||
auto getLeadOutTimecode(u8& minute, u8& second, u8& frame) -> void;
|
||||
auto getTrackTocData(n7 track, u8& flags, u8& minute, u8& second, u8& frame) -> void;
|
||||
auto lbaFromTime(u8 hour, u8 minute, u8 second, u8 frame) -> s32;
|
||||
auto isTrackAudio(n7 track) -> bool;
|
||||
auto isDiscLoaded() -> bool;
|
||||
auto isDiscLaserdisc() -> bool;
|
||||
auto isLaserdiscClv() -> bool;
|
||||
auto isLaserdiscDigitalAudioPresent() -> bool;
|
||||
auto power(bool reset) -> void;
|
||||
|
||||
//serialization.cpp
|
||||
@@ -343,6 +365,7 @@ struct MCD : M68000, Thread {
|
||||
n4 seeking; //status after seeking (Playing or Paused)
|
||||
n16 latency;
|
||||
i32 sector; //current frame#
|
||||
i32 sectorRepeatCount;
|
||||
n16 sample; //current audio sample# within current frame
|
||||
n7 track; //current track#
|
||||
n1 tocRead;
|
||||
@@ -354,8 +377,127 @@ struct MCD : M68000, Thread {
|
||||
n4 status [10];
|
||||
n4 command[10];
|
||||
n16 subcode[64];
|
||||
n1 stopPointEnabled;
|
||||
s32 targetStopPoint;
|
||||
bool laserdiscLoaded;
|
||||
} cdd;
|
||||
|
||||
struct LD {
|
||||
// megald.cpp
|
||||
auto load(string sourceFile) -> void;
|
||||
auto unload() -> void;
|
||||
auto notifyDiscEjected() -> void;
|
||||
auto read(n24 address) -> n8;
|
||||
auto write(n24 address, n8 data) -> void;
|
||||
auto getOutputRegisterValue(int regNum) -> n8;
|
||||
auto processInputRegisterWrite(int regNum, n8 data, n8 previousData, bool wasDeferredRegisterWrite) -> void;
|
||||
auto resetSeekTargetToDefault() -> void;
|
||||
auto liveSeekRegistersContainsLatchableTarget() const -> bool;
|
||||
auto latchSeekTargetFromCurrentState() -> bool;
|
||||
auto performSeekWithLatchedState() -> void;
|
||||
auto updateStopPointWithCurrentState() -> void;
|
||||
auto zeroBasedFrameIndexFromLba(s32 lba, bool processLeadIn = false) -> s32;
|
||||
auto lbaFromZeroBasedFrameIndex(s32 frameIndex) -> s32;
|
||||
auto VideoTimeToRedbookTime(u8& hours, u8& minutes, u8& seconds, u8& frames) -> void;
|
||||
auto handleStopPointReached(s32 lba) -> void;
|
||||
auto updateCurrentVideoFrameNumber(s32 lba) -> void;
|
||||
auto loadCurrentVideoFrameIntoBuffer(bool blankFrame) -> void;
|
||||
auto power(bool reset) -> void;
|
||||
auto scanline(u32 vdpPixelBuffer[1495], u32 vcounter) -> void;
|
||||
|
||||
//serialization.cpp
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
enum class SeekPointReg {
|
||||
Chapter,
|
||||
HoursOrFrameH,
|
||||
MinutesOrFrameM,
|
||||
SecondsOrFrameL,
|
||||
Frames,
|
||||
};
|
||||
enum class SeekMode {
|
||||
SeekToRedbookTime,
|
||||
SeekToRedbookRelativeTime,
|
||||
SeekToVideoFrame,
|
||||
SeekToVideoTime,
|
||||
};
|
||||
|
||||
struct AnalogVideoFrameIndex {
|
||||
bool isCLV;
|
||||
bool hasDigitalAudio;
|
||||
size_t leadInFrameCount;
|
||||
size_t activeVideoFrameCount;
|
||||
size_t leadOutFrameCount;
|
||||
std::vector<const unsigned char*> leadInFrames;
|
||||
std::vector<const unsigned char*> activeVideoFrames;
|
||||
std::vector<const unsigned char*> leadOutFrames;
|
||||
|
||||
s32 frameSkipBaseFrame;
|
||||
s32 frameSkipCounter;
|
||||
s32 currentVideoFrameIndex;
|
||||
n1 currentVideoFrameLeadIn;
|
||||
n1 currentVideoFrameLeadOut;
|
||||
n1 currentVideoFrameFieldSelectionEnabled[2];
|
||||
n1 currentVideoFrameFieldSelectionEvenField[2];
|
||||
n1 currentVideoFrameOnEvenField;
|
||||
qon_desc videoFileHeader;
|
||||
qoi2_desc videoFrameHeader;
|
||||
int drawIndex;
|
||||
std::vector<unsigned char> videoFrameBuffers[2];
|
||||
std::vector<unsigned char> dummyBlankLineBuffer;
|
||||
|
||||
static const size_t FrameBufferWidth = 1495;
|
||||
static const size_t FrameBufferHeight = 525;
|
||||
std::vector<u32> outputFramebuffer;
|
||||
} video;
|
||||
vector<u8> analogAudioDataBuffer;
|
||||
array_view<u8> analogAudioRawDataView;
|
||||
n32 analogAudioLeadingAudioSamples;
|
||||
Decode::MMI mmi;
|
||||
|
||||
static constexpr double videoFramesPerSecond = 30.0 / 1.001; // Roughly 29.97
|
||||
static const size_t inputRegisterCount = 0x20;
|
||||
static const size_t outputRegisterCount = 0x20;
|
||||
n8 inputRegs[inputRegisterCount];
|
||||
n8 inputFrozenRegs[inputRegisterCount];
|
||||
n8 outputRegs[outputRegisterCount];
|
||||
n8 outputFrozenRegs[outputRegisterCount];
|
||||
n1 areInputRegsFrozen;
|
||||
n1 areOutputRegsFrozen;
|
||||
n1 operationErrorFlag1;
|
||||
n1 operationErrorFlag2;
|
||||
n1 operationErrorFlag3;
|
||||
n1 seekEnabled;
|
||||
n3 currentSeekMode;
|
||||
n1 currentSeekModeTimeFormat;
|
||||
n1 currentSeekModeRepeat;
|
||||
n8 analogAudioAttenuationLeft;
|
||||
n8 analogAudioAttenuationRight;
|
||||
n1 analogAudioFadeToMutedLeft;
|
||||
n1 analogAudioFadeToMutedRight;
|
||||
|
||||
// Currently latched seek point
|
||||
u8 activeSeekMode;
|
||||
n8 seekPointRegs[5];
|
||||
|
||||
// Currently latched stop point
|
||||
n8 stopPointRegs[5];
|
||||
n1 reachedStopPoint;
|
||||
n1 reachedStopPointPreviously;
|
||||
|
||||
u4 currentPlaybackMode;
|
||||
u3 currentPlaybackSpeed;
|
||||
n8 skippedFrameCount;
|
||||
u1 currentPlaybackDirection;
|
||||
n8 targetDriveState;
|
||||
n8 currentDriveState;
|
||||
n1 targetPauseState;
|
||||
n1 currentPauseState;
|
||||
n1 seekPerformedSinceLastFrameUpdate;
|
||||
n8 driveStateChangeDelayCounter;
|
||||
n8 selectedTrackInfo;
|
||||
} ld;
|
||||
|
||||
struct Timer {
|
||||
//timer.cpp
|
||||
auto clock() -> void;
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -38,6 +38,7 @@ auto MCD::serialize(serializer& s) -> void {
|
||||
|
||||
s(cdc);
|
||||
s(cdd);
|
||||
s(ld);
|
||||
s(timer);
|
||||
s(gpu);
|
||||
s(pcm);
|
||||
@@ -145,6 +146,7 @@ auto MCD::CDD::serialize(serializer& s) -> void {
|
||||
s(io.seeking);
|
||||
s(io.latency);
|
||||
s(io.sector);
|
||||
s(io.sectorRepeatCount);
|
||||
s(io.sample);
|
||||
s(io.track);
|
||||
s(io.tocRead);
|
||||
@@ -155,6 +157,57 @@ auto MCD::CDD::serialize(serializer& s) -> void {
|
||||
s(status);
|
||||
s(command);
|
||||
s(subcode);
|
||||
s(stopPointEnabled);
|
||||
s(targetStopPoint);
|
||||
}
|
||||
|
||||
auto MCD::LD::serialize(serializer& s) -> void {
|
||||
s(inputRegs);
|
||||
s(inputFrozenRegs);
|
||||
s(outputRegs);
|
||||
s(outputFrozenRegs);
|
||||
s(areInputRegsFrozen);
|
||||
s(areOutputRegsFrozen);
|
||||
s(operationErrorFlag1);
|
||||
s(operationErrorFlag2);
|
||||
s(operationErrorFlag3);
|
||||
s(seekEnabled);
|
||||
s(currentSeekMode);
|
||||
s(currentSeekModeTimeFormat);
|
||||
s(currentSeekModeRepeat);
|
||||
s(analogAudioAttenuationLeft);
|
||||
s(analogAudioAttenuationRight);
|
||||
s(analogAudioFadeToMutedLeft);
|
||||
s(analogAudioFadeToMutedRight);
|
||||
s(stopPointRegs);
|
||||
s(reachedStopPoint);
|
||||
s(reachedStopPointPreviously);
|
||||
s(activeSeekMode);
|
||||
s(currentPlaybackMode);
|
||||
s(currentPlaybackSpeed);
|
||||
s(currentPlaybackDirection);
|
||||
s(seekPointRegs);
|
||||
s(targetDriveState);
|
||||
s(currentDriveState);
|
||||
s(targetPauseState);
|
||||
s(currentPauseState);
|
||||
s(seekPerformedSinceLastFrameUpdate);
|
||||
s(driveStateChangeDelayCounter);
|
||||
s(selectedTrackInfo);
|
||||
|
||||
s(video.frameSkipBaseFrame);
|
||||
s(video.frameSkipCounter);
|
||||
s(video.currentVideoFrameIndex);
|
||||
s(video.currentVideoFrameLeadIn);
|
||||
s(video.currentVideoFrameLeadOut);
|
||||
s(video.currentVideoFrameFieldSelectionEnabled);
|
||||
s(video.currentVideoFrameFieldSelectionEvenField);
|
||||
s(video.currentVideoFrameOnEvenField);
|
||||
|
||||
// Restore the current video frame into the display buffer
|
||||
if (MegaLD() && s.reading()) {
|
||||
loadCurrentVideoFrameIntoBuffer(inputRegs[0x0C].bit(2));
|
||||
}
|
||||
}
|
||||
|
||||
auto MCD::Timer::serialize(serializer& s) -> void {
|
||||
|
||||
@@ -2,6 +2,12 @@
|
||||
//started: 2016-07-08
|
||||
|
||||
#include <ares/ares.hpp>
|
||||
#include <nall/decode/mmi.hpp>
|
||||
#include <vector>
|
||||
#include <cmath>
|
||||
|
||||
#include <qon/qon.h>
|
||||
#include <qon/qoi2.h>
|
||||
|
||||
#include <component/processor/m68000/m68000.hpp>
|
||||
#include <component/processor/z80/z80.hpp>
|
||||
@@ -31,6 +37,7 @@ namespace ares::MegaDrive {
|
||||
|
||||
inline static auto Mega32X() -> bool;
|
||||
inline static auto MegaCD() -> bool;
|
||||
inline static auto MegaLD() -> bool;
|
||||
|
||||
#include <md/controller/controller.hpp>
|
||||
#include <md/bus/bus.hpp>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
static const string SerializerVersion = "v144";
|
||||
static const string SerializerVersion = "v145";
|
||||
|
||||
auto System::serialize(bool synchronize) -> serializer {
|
||||
if(synchronize) scheduler.enter(Scheduler::Mode::Synchronize);
|
||||
|
||||
@@ -20,6 +20,9 @@ auto enumerate() -> vector<string> {
|
||||
"[Sega] Mega CD 32X (NTSC-J)",
|
||||
"[Sega] Mega CD 32X (NTSC-U)",
|
||||
"[Sega] Mega CD 32X (PAL)",
|
||||
//Mega LD
|
||||
"[Sega] Mega LD (NTSC-J)",
|
||||
"[Sega] Mega LD (NTSC-U)",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -74,25 +77,36 @@ auto System::load(Node::System& root, string name) -> bool {
|
||||
information.name = "Mega Drive";
|
||||
information.mega32X = 0;
|
||||
information.megaCD = 0;
|
||||
information.megaLD = 0;
|
||||
cpu.minCyclesBetweenSyncs = 0; // sync every cycle
|
||||
}
|
||||
if(name.match("[Sega] Mega 32X (*)")) {
|
||||
information.name = "Mega Drive";
|
||||
information.mega32X = 1;
|
||||
information.megaCD = 0;
|
||||
information.megaLD = 0;
|
||||
cpu.minCyclesBetweenSyncs = 14; // sync approx every 24-25 pixels
|
||||
}
|
||||
if(name.match("[Sega] Mega CD (*)")) {
|
||||
information.name = "Mega Drive";
|
||||
information.mega32X = 0;
|
||||
information.megaCD = 1;
|
||||
information.megaLD = 0;
|
||||
cpu.minCyclesBetweenSyncs = 0; // sync every cycle
|
||||
}
|
||||
if(name.match("[Sega] Mega CD 32X (*)")) {
|
||||
information.name = "Mega Drive";
|
||||
information.mega32X = 1;
|
||||
information.megaCD = 1;
|
||||
cpu.minCyclesBetweenSyncs = 40; // sync approx every 70 pixels
|
||||
information.megaLD = 0;
|
||||
cpu.minCyclesBetweenSyncs = 10; // sync approx every 1 pixel
|
||||
}
|
||||
if(name.match("[Sega] Mega LD (*)")) {
|
||||
information.name = "Mega Drive";
|
||||
information.mega32X = 0;
|
||||
information.megaCD = 1;
|
||||
information.megaLD = 1;
|
||||
cpu.minCyclesBetweenSyncs = 4; // sync approx every 7 pixels
|
||||
}
|
||||
if(name.find("NTSC-J")) {
|
||||
information.region = Region::NTSCJ;
|
||||
|
||||
@@ -20,6 +20,7 @@ struct System {
|
||||
auto region() const -> Region { return information.region; }
|
||||
auto mega32X() const -> bool { return information.mega32X; }
|
||||
auto megaCD() const -> bool { return information.megaCD; }
|
||||
auto megaLD() const -> bool { return information.megaLD; }
|
||||
auto frequency() const -> double { return information.frequency; }
|
||||
|
||||
auto game() -> string;
|
||||
@@ -40,6 +41,7 @@ private:
|
||||
Region region = Region::NTSCJ;
|
||||
bool mega32X = false;
|
||||
bool megaCD = false;
|
||||
bool megaLD = false;
|
||||
double frequency = Constants::Colorburst::NTSC * 15.0;
|
||||
} information;
|
||||
|
||||
@@ -56,3 +58,4 @@ auto Region::PAL() -> bool { return system.region() == System::Region::PAL; }
|
||||
|
||||
auto Mega32X() -> bool { return system.mega32X(); }
|
||||
auto MegaCD() -> bool { return system.megaCD(); }
|
||||
auto MegaLD() -> bool { return system.megaLD(); }
|
||||
@@ -185,8 +185,10 @@ auto VDP::mainH32() -> void {
|
||||
if(dac.pixels) {
|
||||
blocks<false, true>();
|
||||
if(Mega32X()) m32x.vdp.scanline(pixels + 18, vcounter()); //approx 3 and 1/4 pixel offset
|
||||
if(MegaLD()) mcd.ld.scanline(dac.pixels, vcounter());
|
||||
} else {
|
||||
blocks<false, false>();
|
||||
if(MegaLD()) mcd.ld.scanline(dac.pixels, vcounter());
|
||||
}
|
||||
|
||||
tick<false>(); slot();
|
||||
@@ -248,8 +250,10 @@ auto VDP::mainH40() -> void {
|
||||
if(dac.pixels) {
|
||||
blocks<true, true>();
|
||||
if(Mega32X()) m32x.vdp.scanline(pixels, vcounter());
|
||||
if(MegaLD()) mcd.ld.scanline(dac.pixels, vcounter());
|
||||
} else {
|
||||
blocks<true, false>();
|
||||
if(MegaLD()) mcd.ld.scanline(dac.pixels, vcounter());
|
||||
}
|
||||
|
||||
tick<true>(); slot();
|
||||
|
||||
+15
-11
@@ -34,9 +34,9 @@ VDP vdp;
|
||||
auto VDP::load(Node::Object parent) -> void {
|
||||
node = parent->append<Node::Object>("VDP");
|
||||
|
||||
screen = node->append<Node::Video::Screen>("Screen", 1415, visibleHeight() * 2);
|
||||
screen = node->append<Node::Video::Screen>("Screen", 1495, visibleHeight() * 2);
|
||||
screen->colors(1 << 16, {&VDP::color, this});
|
||||
screen->setSize(1415, visibleHeight() * 2);
|
||||
screen->setSize(1495, visibleHeight() * 2);
|
||||
screen->setScale(0.25, 0.5);
|
||||
Region::PAL() ? screen->setAspect(41.0, 37.0) : screen->setAspect(32.0, 35.0);
|
||||
screen->refreshRateHint(system.frequency(), 3420, frameHeight());
|
||||
@@ -80,18 +80,21 @@ auto VDP::pixels() -> u32* {
|
||||
y = y % visibleHeight();
|
||||
|
||||
auto yScale = latch.interlace ? 2 : 1;
|
||||
output = screen->pixels().data() + y * yScale * 1415;
|
||||
if(latch.interlace) output += field() * 1415;
|
||||
output = screen->pixels().data() + y * yScale * 1495;
|
||||
if(latch.interlace) output += field() * 1495;
|
||||
|
||||
size_t megaLDExtraOverscanBorder = 40;
|
||||
if(h40()) {
|
||||
// H40 mode has slightly shorter lines, so sides are blanked.
|
||||
for(auto n: range(13)) output[ n] = 0b101 << 9;
|
||||
for(auto n: range(14)) output[1415-14+n] = 0b101 << 9;
|
||||
for(auto n: range(13+megaLDExtraOverscanBorder)) output[ n] = 0b101 << 9;
|
||||
for(auto n: range(14+megaLDExtraOverscanBorder)) output[1495-14-megaLDExtraOverscanBorder+n] = 0b101 << 9;
|
||||
|
||||
return output+13;
|
||||
return output+13+megaLDExtraOverscanBorder;
|
||||
}
|
||||
for(auto n: range(megaLDExtraOverscanBorder)) output[ n] = 0b101 << 9;
|
||||
for(auto n: range(megaLDExtraOverscanBorder)) output[1495-megaLDExtraOverscanBorder+n] = 0b101 << 9;
|
||||
|
||||
return output;
|
||||
return output+megaLDExtraOverscanBorder;
|
||||
}
|
||||
|
||||
auto VDP::frame() -> void {
|
||||
@@ -100,11 +103,12 @@ auto VDP::frame() -> void {
|
||||
auto yScale = latch.interlace ? 2 : 1;
|
||||
screen->setScale(0.25, 1.0 / yScale);
|
||||
|
||||
size_t megaLDExtraOverscanBorder = 40;
|
||||
if(screen->overscan()) {
|
||||
screen->setSize(1415, visibleHeight() * yScale);
|
||||
screen->setViewport(0, 0, screen->width(), screen->height());
|
||||
screen->setSize(1495 - (!MegaLD() ? megaLDExtraOverscanBorder * 2 : 0), visibleHeight() * yScale);
|
||||
screen->setViewport((!MegaLD() ? megaLDExtraOverscanBorder : 0), 0, screen->width(), screen->height());
|
||||
} else {
|
||||
int x = 13 * 5;
|
||||
int x = 13 * 5 + (!MegaLD() ? megaLDExtraOverscanBorder : 0);
|
||||
int y = Region::PAL() ? 30 + 8 * v28() : 11;
|
||||
int width = 1280;
|
||||
int height = screenHeight();
|
||||
|
||||
+1
-1
@@ -52,7 +52,7 @@ struct VDP : Thread {
|
||||
auto screenWidth() const -> u32 { return latch.displayWidth ? 320 : 256; }
|
||||
auto screenHeight() const -> u32 { return io.overscan ? 240 : 224; }
|
||||
auto frameHeight() const -> u32 { return Region::PAL() ? 313 : 262; }
|
||||
auto visibleHeight() const -> u32 { return Region::PAL() ? 294 : 243; }
|
||||
auto visibleHeight() const -> u32 { return MegaLD() ? frameHeight() : (Region::PAL() ? 294 : 243); }
|
||||
|
||||
//vdp.cpp
|
||||
auto load(Node::Object) -> void;
|
||||
|
||||
@@ -119,7 +119,6 @@ auto VDP::registerWrite(n4 address, n8 data) -> void {
|
||||
background.io.vscrollLock = data.bit(7);
|
||||
irq.line.pending &= irq.line.enable;
|
||||
irq.poll();
|
||||
updateScreenSize();
|
||||
return;
|
||||
|
||||
case 0x1: //mode control 2
|
||||
@@ -131,7 +130,6 @@ auto VDP::registerWrite(n4 address, n8 data) -> void {
|
||||
io.displayEnable = data.bit(6);
|
||||
irq.frame.pending &= irq.frame.enable;
|
||||
irq.poll();
|
||||
updateScreenSize();
|
||||
return;
|
||||
|
||||
case 0x2: //name table base address
|
||||
|
||||
+5
-14
@@ -27,15 +27,15 @@ auto VDP::load(Node::Object parent) -> void {
|
||||
screen->refreshRateHint(system.colorburst() * 15, 3420, Region::PAL() ? 313 : 262);
|
||||
|
||||
if(Display::CRT()) {
|
||||
screen->colors(1 << 6, {&VDP::colorMasterSystem, this});
|
||||
screen->setSize(284, screenHeight());
|
||||
screen->setScale(1.0, 1.0);
|
||||
Region::PAL() ? screen->setAspect(19.0, 14.0) : screen->setAspect(8.0, 7.0);
|
||||
|
||||
colorEmulation = screen->append<Node::Setting::Boolean>("Color Emulation", true, [&](auto value) {
|
||||
screen->resetPalette();
|
||||
});
|
||||
colorEmulation->setDynamic(true);
|
||||
|
||||
screen->colors(1 << 6, {&VDP::colorMasterSystem, this});
|
||||
screen->setSize(284, screenHeight());
|
||||
screen->setScale(1.0, 1.0);
|
||||
Region::PAL() ? screen->setAspect(19.0, 14.0) : screen->setAspect(8.0, 7.0);
|
||||
}
|
||||
|
||||
if(Display::LCD()) {
|
||||
@@ -149,15 +149,6 @@ auto VDP::step(u32 clocks) -> void {
|
||||
}
|
||||
}
|
||||
|
||||
auto VDP::updateScreenSize() -> void {
|
||||
if(Display::CRT()) {
|
||||
screen->setSize(284, screenHeight());
|
||||
}
|
||||
//if(Display::LCD()) {
|
||||
// screen->setSize(160, 144);
|
||||
//}
|
||||
}
|
||||
|
||||
auto VDP::vlines() -> u32 {
|
||||
if(revision->value() == 1) return 192;
|
||||
|
||||
|
||||
@@ -38,7 +38,6 @@ struct VDP : Thread {
|
||||
auto main() -> void;
|
||||
auto step(u32 clocks) -> void;
|
||||
|
||||
auto updateScreenSize() -> void;
|
||||
auto screenHeight() const -> u32 { return Region::PAL() ? 288 : 243; }
|
||||
|
||||
auto vlines() -> u32;
|
||||
|
||||
@@ -14,6 +14,8 @@ Gamepad::Gamepad(Node::Port parent) {
|
||||
|
||||
bank = 0;
|
||||
|
||||
axis = node->append<Node::Input::Axis>("Axis");
|
||||
|
||||
x = node->append<Node::Input::Axis> ("X-Axis");
|
||||
y = node->append<Node::Input::Axis> ("Y-Axis");
|
||||
up = node->append<Node::Input::Button>("Up");
|
||||
@@ -313,57 +315,37 @@ auto Gamepad::read() -> n32 {
|
||||
auto cardinalMax = 85.0;
|
||||
auto diagonalMax = 69.0;
|
||||
auto innerDeadzone = 7.0; // default should remain 7 (~8.2% of 85) as the deadzone is axial in nature and fights cardinalMax
|
||||
auto outerDeadzoneRadiusMax = 2.0 / sqrt(2.0) * (diagonalMax / cardinalMax * (cardinalMax - innerDeadzone) + innerDeadzone); //from linear scaling equation, substitute outerDeadzoneRadiusMax*sqrt(2)/2 for lengthAbsoluteX and set diagonalMax as the result then solve for outerDeadzoneRadiusMax
|
||||
auto saturationRadius = (innerDeadzone + diagonalMax + sqrt(pow(innerDeadzone + diagonalMax, 2.0) - 2.0 * sqrt(2.0) * diagonalMax * innerDeadzone)) / sqrt(2.0); //from linear response curve function within axis->processDeadzoneAndResponseCurve, substitute saturationRadius * sqrt(2) / 2 for right-hand lengthAbsolute and set diagonalMax as the result then solve for saturationRadius
|
||||
auto offset = 0.0;
|
||||
|
||||
//scale {-32768 ... +32767} to {-outerDeadzoneRadiusMax ... +outerDeadzoneRadiusMax}
|
||||
auto ax = x->value() * outerDeadzoneRadiusMax / 32767.0;
|
||||
auto ay = y->value() * outerDeadzoneRadiusMax / 32767.0;
|
||||
|
||||
//create inner axial dead-zone in range {-innerDeadzone ... +innerDeadzone} and scale from it up to outer circular dead-zone of radius outerDeadzoneRadiusMax
|
||||
auto length = sqrt(ax * ax + ay * ay);
|
||||
if(length <= outerDeadzoneRadiusMax) {
|
||||
auto lengthAbsoluteX = abs(ax);
|
||||
auto lengthAbsoluteY = abs(ay);
|
||||
if(lengthAbsoluteX <= innerDeadzone) {
|
||||
lengthAbsoluteX = 0.0;
|
||||
} else {
|
||||
lengthAbsoluteX = (lengthAbsoluteX - innerDeadzone) * cardinalMax / (cardinalMax - innerDeadzone) / lengthAbsoluteX;
|
||||
}
|
||||
ax *= lengthAbsoluteX;
|
||||
if(lengthAbsoluteY <= innerDeadzone) {
|
||||
lengthAbsoluteY = 0.0;
|
||||
} else {
|
||||
lengthAbsoluteY = (lengthAbsoluteY - innerDeadzone) * cardinalMax / (cardinalMax - innerDeadzone) / lengthAbsoluteY;
|
||||
}
|
||||
ay *= lengthAbsoluteY;
|
||||
} else {
|
||||
length = outerDeadzoneRadiusMax / length;
|
||||
ax *= length;
|
||||
ay *= length;
|
||||
}
|
||||
|
||||
//bound diagonals to an octagonal range {-diagonalMax ... +diagonalMax}
|
||||
if(ax != 0.0 && ay != 0.0) {
|
||||
auto slope = ay / ax;
|
||||
auto edgex = copysign(cardinalMax / (abs(slope) + (cardinalMax - diagonalMax) / diagonalMax), ax);
|
||||
auto edgey = copysign(min(abs(edgex * slope), cardinalMax / (1.0 / abs(slope) + (cardinalMax - diagonalMax) / diagonalMax)), ay);
|
||||
edgex = edgey / slope;
|
||||
//scale {-32767 ... +32767} to {-saturationRadius + offset ... +saturationRadius + offset}
|
||||
auto ax = axis->setOperatingRange(x->value(), saturationRadius, offset);
|
||||
auto ay = axis->setOperatingRange(y->value(), saturationRadius, offset);
|
||||
|
||||
length = sqrt(ax * ax + ay * ay);
|
||||
auto distanceToEdge = sqrt(edgex * edgex + edgey * edgey);
|
||||
if(length > distanceToEdge) {
|
||||
ax = edgex;
|
||||
ay = edgey;
|
||||
}
|
||||
//create inner axial dead-zone in range {-innerDeadzone ... +innerDeadzone} and scale from it up to saturationRadius
|
||||
ax = axis->processDeadzoneAndResponseCurve(ax, innerDeadzone, saturationRadius, offset);
|
||||
ay = axis->processDeadzoneAndResponseCurve(ay, innerDeadzone, saturationRadius, offset);
|
||||
|
||||
auto scaledLength = hypot(ax - offset, ay - offset);
|
||||
if(scaledLength > saturationRadius) {
|
||||
ax = axis->revisePosition(ax, scaledLength, saturationRadius, offset);
|
||||
ay = axis->revisePosition(ay, scaledLength, saturationRadius, offset);
|
||||
}
|
||||
|
||||
//let cardinalMax and diagonalMax define boundaries and restrict to an octagonal gate
|
||||
double axBounded = 0.0;
|
||||
double ayBounded = 0.0;
|
||||
axis->applyGateBoundaries(innerDeadzone, cardinalMax, diagonalMax, ax, ay, offset, axBounded, ayBounded);
|
||||
ax = axBounded;
|
||||
ay = ayBounded;
|
||||
|
||||
//keep cardinal input within positive and negative bounds of cardinalMax
|
||||
if(abs(ax) > cardinalMax) ax = copysign(cardinalMax, ax);
|
||||
if(abs(ay) > cardinalMax) ay = copysign(cardinalMax, ay);
|
||||
|
||||
ax = axis->clampAxisToNearestBoundary(ax, offset, cardinalMax);
|
||||
ay = axis->clampAxisToNearestBoundary(ay, offset, cardinalMax);
|
||||
|
||||
//add epsilon to counteract floating point precision error
|
||||
ax = copysign(abs(ax) + 1e-09, ax);
|
||||
ay = copysign(abs(ay) + 1e-09, ay);
|
||||
ax = axis->counteractPrecisionError(ax);
|
||||
ay = axis->counteractPrecisionError(ay);
|
||||
|
||||
n32 data;
|
||||
data.byte(0) = s8(-ay);
|
||||
@@ -398,13 +380,9 @@ auto Gamepad::read() -> n32 {
|
||||
|
||||
auto Gamepad::getInodeChecksum(u8 bank) -> u8 {
|
||||
if (bank < 62) {
|
||||
u32 checksum = 0;
|
||||
u32 i = bank == 0 ? 3 + ram.read<Byte>(0x20 + 0x1a) * 2 : 1; //first bank has 3 + bank * 2 system pages, other banks have 127.
|
||||
|
||||
for (i; i < 0x100; i++) {
|
||||
checksum += ram.read<Byte>((1 + bank) * 0x100) + ram.read<Byte>((1 + bank) * 0x100 + 0x01);
|
||||
}
|
||||
|
||||
u8 checksum = 0;
|
||||
for (i32 i=2; i<0x100; i++)
|
||||
checksum += ram.read<Byte>((1 + bank) * 0x100 + i);
|
||||
return checksum;
|
||||
}
|
||||
|
||||
@@ -442,7 +420,7 @@ auto Gamepad::formatControllerPak() -> void {
|
||||
//pages 1 thru nBanks, nBanks+1 thru (nBanks*2) (inode table, inode table copy)
|
||||
u8 nBanks = ram.read<Byte>(0x20 + 0x1a);
|
||||
u32 inodeTablePage = 1;
|
||||
u32 inodeTableCopyPage = 1 + nBanks * 2;
|
||||
u32 inodeTableCopyPage = 1 + nBanks;
|
||||
for(u32 bank : range(0,nBanks)) {
|
||||
u32 firstDataPage = bank == 0 ? (3 + nBanks * 2) : 1; //first bank has 3 + bank * 2 system pages, other banks have 127.
|
||||
for(u32 page : array<u32[2]>{inodeTablePage + bank, inodeTableCopyPage + bank}) {
|
||||
|
||||
@@ -5,6 +5,7 @@ struct Gamepad : Controller {
|
||||
u8 bank;
|
||||
Memory::Writable ram; //Toshiba TC55257DFL-85V
|
||||
Node::Input::Rumble motor;
|
||||
Node::Input::Axis axis;
|
||||
|
||||
Node::Input::Axis x;
|
||||
Node::Input::Axis y;
|
||||
|
||||
@@ -196,8 +196,8 @@ struct CPU : Thread {
|
||||
template<u32 Size> auto write(u64 vaddr, u32 paddr, u64 data) -> void;
|
||||
auto power(bool reset) -> void;
|
||||
|
||||
auto readDebug(u64 vaddr, u32 paddr) -> u8;
|
||||
auto writeDebug(u64 vaddr, u32 paddr, u8 value) -> void;
|
||||
template<u32 Size> auto readDebug(u64 vaddr, u32 paddr) -> u64;
|
||||
template<u32 Size> auto writeDebug(u64 vaddr, u32 paddr, u64 value) -> void;
|
||||
|
||||
//8KB
|
||||
struct Line {
|
||||
@@ -318,7 +318,7 @@ struct CPU : Thread {
|
||||
template<u32 Size> auto vaddrAlignedError(u64 vaddr, bool write) -> bool;
|
||||
auto addressException(u64 vaddr) -> void;
|
||||
|
||||
auto readDebug(u64 vaddr) -> u8;
|
||||
template <u32 Size> auto readDebug(u64 vaddr) -> u64;
|
||||
template <u32 Size> auto writeDebug(u64 vaddr, u64 data) -> bool;
|
||||
|
||||
//serialization.cpp
|
||||
|
||||
+15
-8
@@ -58,13 +58,16 @@ auto CPU::DataCache::read(u64 vaddr, u32 paddr) -> u64 {
|
||||
return line.read<Size>(paddr);
|
||||
}
|
||||
|
||||
auto CPU::DataCache::readDebug(u64 vaddr, u32 paddr) -> u8 {
|
||||
template<u32 Size>
|
||||
auto CPU::DataCache::readDebug(u64 vaddr, u32 paddr) -> u64 {
|
||||
// This is used by the debugger to read memory through the cache but without
|
||||
// actually causing side effects that modify the cache state (eg: no line fill)
|
||||
auto& line = this->line(vaddr);
|
||||
if(!line.hit(paddr)) {
|
||||
Thread dummyThread{};
|
||||
return bus.read<Byte>(paddr, dummyThread, "Ares Debugger");
|
||||
return bus.read<Size>(paddr, dummyThread, "Ares Debugger");
|
||||
}
|
||||
return line.read<Byte>(paddr);
|
||||
return line.read<Size>(paddr);
|
||||
}
|
||||
|
||||
template<u32 Size>
|
||||
@@ -79,13 +82,16 @@ auto CPU::DataCache::write(u64 vaddr, u32 paddr, u64 data) -> void {
|
||||
line.write<Size>(paddr, data);
|
||||
}
|
||||
|
||||
auto CPU::DataCache::writeDebug(u64 vaddr, u32 paddr, u8 data) -> void {
|
||||
template<u32 Size>
|
||||
auto CPU::DataCache::writeDebug(u64 vaddr, u32 paddr, u64 data) -> void {
|
||||
// This is used by the debugger to write memory through the cache but without
|
||||
// actually causing side effects that modify the cache state (eg: no line fill)
|
||||
auto& line = this->line(vaddr);
|
||||
if(!line.hit(paddr)) {
|
||||
Thread dummyThread{};
|
||||
return bus.write<Byte>(paddr, data, dummyThread, "Ares Debugger");
|
||||
return bus.write<Size>(paddr, data, dummyThread, "Ares Debugger");
|
||||
}
|
||||
line.write<Byte>(paddr, data);
|
||||
line.write<Size>(paddr, data);
|
||||
}
|
||||
|
||||
auto CPU::DataCache::power(bool reset) -> void {
|
||||
@@ -99,5 +105,6 @@ auto CPU::DataCache::power(bool reset) -> void {
|
||||
}
|
||||
}
|
||||
|
||||
template
|
||||
auto CPU::DataCache::Line::write<Byte>(u32 paddr, u64 data) -> void;
|
||||
template auto CPU::DataCache::Line::write<Byte>(u32 paddr, u64 data) -> void;
|
||||
template auto CPU::DataCache::writeDebug<Byte>(u64 vaddr, u32 paddr, u64 data) -> void;
|
||||
template auto CPU::DataCache::readDebug<Byte>(u64 vaddr, u32 paddr) -> u64;
|
||||
+12
-6
@@ -161,12 +161,13 @@ auto CPU::read(PhysAccess access) -> maybe<u64> {
|
||||
return busRead<Size>(access.paddr);
|
||||
}
|
||||
|
||||
auto CPU::readDebug(u64 vaddr) -> u8 {
|
||||
template<u32 Size>
|
||||
auto CPU::readDebug(u64 vaddr) -> u64 {
|
||||
Thread dummyThread{};
|
||||
auto access = devirtualize<Read, Byte>(vaddr, false, false);
|
||||
auto access = devirtualize<Read, Size>(vaddr, false, false);
|
||||
if(!access) return 0;
|
||||
if(access.cache) return dcache.readDebug(access.vaddr, access.paddr);
|
||||
return bus.read<Byte>(access.paddr, dummyThread, "Ares Debugger");
|
||||
if(access.cache) return dcache.readDebug<Size>(access.vaddr, access.paddr);
|
||||
return bus.read<Size>(access.paddr, dummyThread, "Ares Debugger");
|
||||
}
|
||||
|
||||
|
||||
@@ -180,11 +181,12 @@ auto CPU::write(PhysAccess access, u64 data) -> bool {
|
||||
|
||||
template<u32 Size>
|
||||
auto CPU::writeDebug(u64 vaddr, u64 data) -> bool {
|
||||
Thread dummyThread{};
|
||||
auto access = devirtualize<Write, Size>(vaddr, false, false);
|
||||
if(!access) return false;
|
||||
GDB::server.reportMemWrite(access.vaddr, Size);
|
||||
if(access.cache) return dcache.write<Size>(access.vaddr, access.paddr, data), true;
|
||||
return busWrite<Size>(access.paddr, data), true;
|
||||
if(access.cache) return dcache.writeDebug<Size>(access.vaddr, access.paddr, data), true;
|
||||
return bus.write<Size>(access.paddr, data, dummyThread, "Ares Debugger"), true;
|
||||
}
|
||||
|
||||
template<u32 Size>
|
||||
@@ -221,3 +223,7 @@ template auto CPU::writeDebug<Byte>(u64, u64) -> bool;
|
||||
template auto CPU::writeDebug<Half>(u64, u64) -> bool;
|
||||
template auto CPU::writeDebug<Word>(u64, u64) -> bool;
|
||||
template auto CPU::writeDebug<Dual>(u64, u64) -> bool;
|
||||
template auto CPU::readDebug<Byte>(u64) -> u64;
|
||||
template auto CPU::readDebug<Half>(u64) -> u64;
|
||||
template auto CPU::readDebug<Word>(u64) -> u64;
|
||||
template auto CPU::readDebug<Dual>(u64) -> u64;
|
||||
@@ -8,6 +8,7 @@ inline auto PI::readWord(u32 address, Thread& thread) -> u32 {
|
||||
}
|
||||
thread.step(250 * 2);
|
||||
io.busLatch = busRead<Word>(address);
|
||||
io.pbusAddress = (address + 4) & ~1;
|
||||
return io.busLatch;
|
||||
}
|
||||
|
||||
@@ -59,6 +60,7 @@ inline auto PI::writeWord(u32 address, u32 data, Thread& thread) -> void {
|
||||
if(io.ioBusy) return;
|
||||
io.ioBusy = 1;
|
||||
io.busLatch = data;
|
||||
io.pbusAddress = (address + 4) & ~1;
|
||||
queue.insert(Queue::PI_BUS_Write, 400);
|
||||
return busWrite<Word>(address, data);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ auto RDRAM::Debugger::load(Node::Object parent) -> void {
|
||||
memory.dcache->setSize(4_MiB + 4_MiB);
|
||||
memory.dcache->setRead([&](u32 address) -> u8 {
|
||||
u32 vaddr = address | 0x8000'0000;
|
||||
return cpu.dcache.readDebug(vaddr, address);
|
||||
return cpu.dcache.readDebug<Byte>(vaddr, address);
|
||||
});
|
||||
memory.dcache->setWrite([&](u32 address, u8 data) -> void {
|
||||
u32 vaddr = address | 0x8000'0000;
|
||||
|
||||
+107
-41
@@ -81,9 +81,6 @@ auto System::run() -> void {
|
||||
vulkan.load(node);
|
||||
_vulkanNeedsLoad = false;
|
||||
}
|
||||
if(_needsPower) {
|
||||
_power(_reset);
|
||||
}
|
||||
cpu.main();
|
||||
}
|
||||
|
||||
@@ -170,57 +167,130 @@ auto System::initDebugHooks() -> void {
|
||||
res.resize(byteCount * 2);
|
||||
char* resPtr = res.begin();
|
||||
|
||||
for(u32 i : range(byteCount)) {
|
||||
auto val = cpu.readDebug(address++);
|
||||
hexByte(resPtr, val);
|
||||
resPtr += 2;
|
||||
// For 8/16/32/64-bit reads, perform an actual read like the CPU would do.
|
||||
// This makes sure we return the same value the CPU would get wrt to masking.
|
||||
u64 value;
|
||||
switch(byteCount) {
|
||||
case Byte:
|
||||
value = cpu.readDebug<Byte>(address);
|
||||
hexByte(resPtr, value);
|
||||
return res;
|
||||
case Half:
|
||||
value = cpu.readDebug<Half>(address);
|
||||
hexByte(resPtr + 0, (value >> 8) & 0xff);
|
||||
hexByte(resPtr + 2, value & 0xff);
|
||||
return res;
|
||||
case Word:
|
||||
value = cpu.readDebug<Word>(address);
|
||||
hexByte(resPtr + 0, (value >> 24) & 0xff);
|
||||
hexByte(resPtr + 2, (value >> 16) & 0xff);
|
||||
hexByte(resPtr + 4, (value >> 8) & 0xff);
|
||||
hexByte(resPtr + 6, value & 0xff);
|
||||
return res;
|
||||
case Dual:
|
||||
// Don't allow 64-bit access to RCP to prevent freezing
|
||||
if (address < 0xffff'ffff'a400'0000ull || address + byteCount > 0xffff'ffff'bfff'ffffull) {
|
||||
value = cpu.readDebug<Dual>(address);
|
||||
hexByte(resPtr + 0, (value >> 56) & 0xff);
|
||||
hexByte(resPtr + 2, (value >> 48) & 0xff);
|
||||
hexByte(resPtr + 4, (value >> 40) & 0xff);
|
||||
hexByte(resPtr + 6, (value >> 32) & 0xff);
|
||||
hexByte(resPtr + 8, (value >> 24) & 0xff);
|
||||
hexByte(resPtr + 10, (value >> 16) & 0xff);
|
||||
hexByte(resPtr + 12, (value >> 8) & 0xff);
|
||||
hexByte(resPtr + 14, value & 0xff);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
// Handle reads of different/unaligned sizes only within the RDRAM area,
|
||||
// where we are sure that the write size does not really matter
|
||||
if(address >= 0xffff'ffff'8000'0000ull && address + byteCount <= 0xffff'ffff'83ef'ffffull) {
|
||||
for(u32 i : range(byteCount)) {
|
||||
auto val = cpu.readDebug<Byte>(address++);
|
||||
hexByte(resPtr, val);
|
||||
resPtr += 2;
|
||||
}
|
||||
return res;
|
||||
} else if (address >= 0xffff'ffff'a000'0000ull && address + byteCount <= 0xffff'ffff'a3ef'ffffull) {
|
||||
Thread dummyThread{};
|
||||
for(u32 i : range(byteCount)) {
|
||||
auto val = bus.read<Byte>(address & 0x1fff'ffff, dummyThread, "Ares Debugger");
|
||||
hexByte(resPtr, val);
|
||||
resPtr += 2;
|
||||
address++;
|
||||
}
|
||||
return res;
|
||||
} else if (address >= 0xffff'ffff'a400'0000ull && address + byteCount <= 0xffff'ffff'bfff'ffffull) {
|
||||
// Otherwise, use 32-bit reads to read the data. This is the expected
|
||||
// read for RCP, so it's the one that's most likely to return the data
|
||||
// as the user would expect.
|
||||
Thread dummyThread{};
|
||||
for(u32 i : range(byteCount / 4)) {
|
||||
u64 value = bus.read<Word>(address & 0x1fff'ffff, dummyThread, "Ares Debugger");
|
||||
hexByte(resPtr + 0, (value >> 24) & 0xff);
|
||||
hexByte(resPtr + 2, (value >> 16) & 0xff);
|
||||
hexByte(resPtr + 4, (value >> 8) & 0xff);
|
||||
hexByte(resPtr + 6, value & 0xff);
|
||||
resPtr += 8;
|
||||
address += 4;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
GDB::server.hooks.write = [](u64 address, vector<u8> data) {
|
||||
address = (s32)address;
|
||||
|
||||
// For 8/16/32/64-bit writes, perform an actual write like the CPU would do.
|
||||
// This makes sure we write the same value the CPU would write wrt to masking.
|
||||
u64 value;
|
||||
switch(data.size()) {
|
||||
case Byte:
|
||||
value = (u64)data[0];
|
||||
cpu.writeDebug<Byte>(address, value);
|
||||
break;
|
||||
case Half:
|
||||
value = ((u64)data[0]<<8) | ((u64)data[1]<<0);
|
||||
cpu.writeDebug<Half>(address, value);
|
||||
break;
|
||||
case Word:
|
||||
value = ((u64)data[0]<<24) | ((u64)data[1]<<16) | ((u64)data[2]<<8) | ((u64)data[3]<<0);
|
||||
cpu.writeDebug<Word>(address, value);
|
||||
break;
|
||||
case Dual:
|
||||
value = ((u64)data[0]<<56) | ((u64)data[1]<<48) | ((u64)data[2]<<40) | ((u64)data[3]<<32);
|
||||
value |= ((u64)data[4]<<24) | ((u64)data[5]<<16) | ((u64)data[6]<< 8) | ((u64)data[7]<< 0);
|
||||
cpu.writeDebug<Dual>(address, value);
|
||||
break;
|
||||
}
|
||||
|
||||
// Handle writes of different/unaligned sizes only within the RDRAM area,
|
||||
// where we are sure that the write size does not really matter
|
||||
if(address >= 0xffff'ffff'8000'0000ull && address <= 0xffff'ffff'83ef'ffffull) {
|
||||
if(address >= 0xffff'ffff'8000'0000ull && address + data.size() <= 0xffff'ffff'83ef'ffffull) {
|
||||
for(auto b : data) {
|
||||
cpu.dcache.writeDebug(address, address & 0x1fff'ffff, b);
|
||||
cpu.dcache.writeDebug<Byte>(address, address & 0x1fff'ffff, b);
|
||||
address++;
|
||||
}
|
||||
} else if (address >= 0xffff'ffff'a000'0000ull && address <= 0xffff'ffff'a3ef'ffffull) {
|
||||
} else if (address >= 0xffff'ffff'a000'0000ull && address + data.size() <= 0xffff'ffff'a3ef'ffffull) {
|
||||
Thread dummyThread{};
|
||||
for(auto b : data) {
|
||||
bus.write<Byte>(address & 0x1fff'ffff, b, dummyThread, "Ares Debugger");
|
||||
address++;
|
||||
}
|
||||
} else {
|
||||
// Otherwise, the write is expected to be of a set size to an aligned address.
|
||||
u64 value;
|
||||
switch(data.size()) {
|
||||
case Byte:
|
||||
value = (u64)data[0];
|
||||
cpu.writeDebug<Byte>(address, value);
|
||||
break;
|
||||
case Half:
|
||||
value = ((u64)data[0]<<8) | ((u64)data[1]<<0);
|
||||
cpu.writeDebug<Half>(address, value);
|
||||
break;
|
||||
case Word:
|
||||
value = ((u64)data[0]<<24) | ((u64)data[1]<<16) | ((u64)data[2]<<8) | ((u64)data[3]<<0);
|
||||
cpu.writeDebug<Word>(address, value);
|
||||
break;
|
||||
case Dual:
|
||||
value = ((u64)data[0]<<56) | ((u64)data[1]<<48) | ((u64)data[2]<<40) | ((u64)data[3]<<32);
|
||||
value |= ((u64)data[4]<<24) | ((u64)data[5]<<16) | ((u64)data[6]<< 8) | ((u64)data[7]<< 0);
|
||||
cpu.writeDebug<Dual>(address, value);
|
||||
break;
|
||||
} else if (address >= 0xffff'ffff'a400'0000ull && address + data.size() <= 0xffff'ffff'bfff'ffffull) {
|
||||
// Otherwise, use 32-bit writes to write the data. This is the expected
|
||||
// write for RCP, so it's the one that's most likely to write the data
|
||||
// as the user would expect.
|
||||
Thread dummyThread{};
|
||||
for(u32 i = 0; i < data.size() / 4; i++) {
|
||||
u64 value = ((u64)data[i*4+0]<<24) | ((u64)data[i*4+1]<<16) | ((u64)data[i*4+2]<<8) | ((u64)data[i*4+3]<<0);
|
||||
bus.write<Word>(address & 0x1fff'ffff, value, dummyThread, "Ares Debugger");
|
||||
address += 4;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
GDB::server.hooks.regRead = [](u32 regIdx) {
|
||||
if(regIdx < 32) {
|
||||
return hex(cpu.ipu.r[regIdx].u64, 16, '0');
|
||||
@@ -352,13 +422,6 @@ auto System::save() -> void {
|
||||
}
|
||||
|
||||
auto System::power(bool reset) -> void {
|
||||
_reset = reset;
|
||||
// Re-initializing ParaLLEl-RDP from a thread that did not initialize it seems to occasionally cause a deadlock.
|
||||
// Delay reset until the next iteration of the system's run loop on the worker thread.
|
||||
_needsPower = true;
|
||||
}
|
||||
|
||||
auto System::_power(bool reset) -> void {
|
||||
for(auto& setting : node->find<Node::Setting::Setting>()) setting->setLatch();
|
||||
|
||||
if constexpr(Accuracy::CPU::Recompiler || Accuracy::RSP::Recompiler) {
|
||||
@@ -370,6 +433,10 @@ auto System::_power(bool reset) -> void {
|
||||
if(_DD()) dd.power(reset);
|
||||
mi.power(reset);
|
||||
vi.power(reset);
|
||||
#if defined(VULKAN)
|
||||
vulkan.unload();
|
||||
_vulkanNeedsLoad = true;
|
||||
#endif
|
||||
ai.power(reset);
|
||||
pi.power(reset);
|
||||
pif.power(reset);
|
||||
@@ -380,7 +447,6 @@ auto System::_power(bool reset) -> void {
|
||||
rsp.power(reset);
|
||||
rdp.power(reset);
|
||||
if(model() == Model::Aleck64) aleck64.power(reset);
|
||||
_needsPower = false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -39,8 +39,6 @@ private:
|
||||
} information;
|
||||
|
||||
atomic<bool> _vulkanNeedsLoad = false;
|
||||
atomic<bool> _needsPower = false;
|
||||
bool _reset = false;
|
||||
|
||||
auto initDebugHooks() -> void;
|
||||
auto _power(bool reset) -> void;
|
||||
|
||||
+10
-3
@@ -44,19 +44,26 @@ auto VI::load(Node::Object parent) -> void {
|
||||
}
|
||||
});
|
||||
|
||||
int videoHeight = Region::PAL() ? 576 : 480;
|
||||
|
||||
#if defined(VULKAN)
|
||||
if(vulkan.enable) {
|
||||
screen->setSize(vulkan.outputUpscale * 640, vulkan.outputUpscale * 480);
|
||||
screen->setSize(vulkan.outputUpscale * 640, vulkan.outputUpscale * videoHeight);
|
||||
if(!vulkan.supersampleScanout) {
|
||||
screen->setScale(1.0 / vulkan.outputUpscale, 1.0 / vulkan.outputUpscale);
|
||||
}
|
||||
} else {
|
||||
screen->setSize(640, 480);
|
||||
screen->setSize(640, videoHeight);
|
||||
}
|
||||
#else
|
||||
screen->setSize(640, 480);
|
||||
screen->setSize(640, videoHeight);
|
||||
#endif
|
||||
|
||||
// Pedantic N64 NTSC aspect ratio is 120:119, but let's keep 120:120 to avoid slight scaling.
|
||||
// Pedantic N64 PAL aspect ratio is 5900000:4965653, but let's use 12:10 to achieve the
|
||||
// same aspect ratio as NTSC.
|
||||
Region::PAL() ? screen->setAspect(12, 10) : screen->setAspect(120, 120);
|
||||
|
||||
debugger.load(node);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ auto load(Node::System& node, string name) -> bool {
|
||||
}
|
||||
|
||||
auto option(string name, string value) -> bool {
|
||||
if(name == "Pixel Accuracy") vdp.setAccurate(value.boolean());
|
||||
if(name == "Pixel Accuracy") vdp.setAccurate(true); // Forced: scanline renderer is too buggy
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,17 +24,18 @@ auto VDP::load(Node::Object parent) -> void {
|
||||
node = parent->append<Node::Object>("VDP");
|
||||
|
||||
screen = node->append<Node::Video::Screen>("Screen", 1365, 263);
|
||||
screen->colors(1 << 10, {&VDP::color, this});
|
||||
screen->setSize(1128, 263);
|
||||
screen->setScale(0.25, 1.0);
|
||||
screen->setAspect(8.0, 7.0);
|
||||
screen->refreshRateHint(60); // TODO: More accurate refresh rate hint
|
||||
|
||||
colorEmulation = screen->append<Node::Setting::Boolean>("Color Emulation", true, [&](auto value) {
|
||||
screen->resetPalette();
|
||||
});
|
||||
colorEmulation->setDynamic(true);
|
||||
|
||||
screen->colors(1 << 10, {&VDP::color, this});
|
||||
screen->setSize(1128, 263);
|
||||
screen->setScale(0.25, 1.0);
|
||||
screen->setAspect(8.0, 7.0);
|
||||
screen->refreshRateHint(60); // TODO: More accurate refresh rate hint
|
||||
|
||||
vce.debugger.load(vce, parent);
|
||||
vdc0.debugger.load(vdc0, parent); if(Model::SuperGrafx())
|
||||
vdc1.debugger.load(vdc1, parent);
|
||||
|
||||
@@ -39,17 +39,16 @@ auto VDP::load(Node::Object parent) -> void {
|
||||
node = parent->append<Node::Object>("VDP");
|
||||
|
||||
screen = node->append<Node::Video::Screen>("Screen", 1365, 263);
|
||||
colorEmulation = screen->append<Node::Setting::Boolean>("Color Emulation", true, [&](auto value) {
|
||||
screen->resetPalette();
|
||||
});
|
||||
colorEmulation->setDynamic(true);
|
||||
screen->colors(1 << 10, {&VDP::color, this});
|
||||
screen->setSize(1128, 263);
|
||||
screen->setScale(0.25, 1.0);
|
||||
screen->setAspect(8.0, 7.0);
|
||||
screen->refreshRateHint(60); // TODO: More accurate refresh rate hint
|
||||
|
||||
colorEmulation = screen->append<Node::Setting::Boolean>("Color Emulation", true, [&](auto value) {
|
||||
screen->resetPalette();
|
||||
});
|
||||
colorEmulation->setDynamic(true);
|
||||
|
||||
vce.debugger.load(vce, node);
|
||||
vdc0.debugger.load(vdc0, node); if(Model::SuperGrafx())
|
||||
vdc1.debugger.load(vdc1, node);
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
DualShock::DualShock(Node::Port parent) {
|
||||
node = parent->append<Node::Peripheral>("DualShock");
|
||||
|
||||
axis = node->append<Node::Input::Axis>("Axis");
|
||||
|
||||
lx = node->append<Node::Input::Axis >("L-Stick X");
|
||||
ly = node->append<Node::Input::Axis >("L-Stick Y");
|
||||
rx = node->append<Node::Input::Axis >("R-Stick X");
|
||||
@@ -213,15 +215,76 @@ auto DualShock::readPad() -> vector<u8> {
|
||||
|
||||
if(!analogMode && !configMode) return result;
|
||||
|
||||
auto cardinalMax = 127.5;
|
||||
auto diagonalMax = 127.5;
|
||||
auto innerDeadzone = 6.0; //missing information on deadzone for DualShock potentiometers; use arbitrary number roughly 4.7% of cardinalMax instead (common software default in gaming industry seems to be 5.0%)
|
||||
auto saturationRadius = (innerDeadzone + diagonalMax + sqrt(pow(innerDeadzone + diagonalMax, 2.0) - 2.0 * sqrt(2.0) * diagonalMax * innerDeadzone)) / sqrt(2.0); //from linear response curve function within axis->processDeadzoneAndResponseCurve, substitute saturationRadius * sqrt(2) / 2 for right-hand lengthAbsolute and set diagonalMax as the result then solve for saturationRadius
|
||||
auto offset = -0.5;
|
||||
|
||||
platform->input(rx);
|
||||
platform->input(ry);
|
||||
result.append((rx->value() + 32768) * 255 / 65535);
|
||||
result.append((ry->value() + 32768) * 255 / 65535);
|
||||
|
||||
//scale {-32767 ... +32767} to {-saturationRadius + offset ... +saturationRadius + offset}
|
||||
auto arx = axis->setOperatingRange(rx->value(), saturationRadius, offset);
|
||||
auto ary = axis->setOperatingRange(ry->value(), saturationRadius, offset);
|
||||
|
||||
//create inner axial dead-zone in range {-innerDeadzone ... +innerDeadzone} and scale from it up to saturationRadius
|
||||
arx = axis->processDeadzoneAndResponseCurve(arx, innerDeadzone, saturationRadius, offset);
|
||||
ary = axis->processDeadzoneAndResponseCurve(ary, innerDeadzone, saturationRadius, offset);
|
||||
|
||||
auto scaledLengthRightStick = hypot(arx - offset, ary - offset);
|
||||
if(scaledLengthRightStick > saturationRadius) {
|
||||
arx = axis->revisePosition(arx, scaledLengthRightStick, saturationRadius, offset);
|
||||
ary = axis->revisePosition(ary, scaledLengthRightStick, saturationRadius, offset);
|
||||
}
|
||||
|
||||
//let cardinalMax and diagonalMax define boundaries and restrict to a square gate
|
||||
double arxBounded = 0.0;
|
||||
double aryBounded = 0.0;
|
||||
axis->applyGateBoundaries(innerDeadzone, cardinalMax, diagonalMax, arx, ary, offset, arxBounded, aryBounded);
|
||||
arx = arxBounded;
|
||||
ary = aryBounded;
|
||||
|
||||
//keep cardinal input within positive and negative bounds of cardinalMax
|
||||
arx = axis->clampAxisToNearestBoundary(arx, offset, cardinalMax);
|
||||
ary = axis->clampAxisToNearestBoundary(ary, offset, cardinalMax);
|
||||
|
||||
//add epsilon to counteract floating point precision error
|
||||
arx = axis->counteractPrecisionError(arx);
|
||||
ary = axis->counteractPrecisionError(ary);
|
||||
|
||||
result.append(u8(arx + 128.0));
|
||||
result.append(u8(ary + 128.0));
|
||||
|
||||
platform->input(lx);
|
||||
platform->input(ly);
|
||||
result.append((lx->value() + 32768) * 255 / 65535);
|
||||
result.append((ly->value() + 32768) * 255 / 65535);
|
||||
|
||||
auto alx = axis->setOperatingRange(lx->value(), saturationRadius, offset);
|
||||
auto aly = axis->setOperatingRange(ly->value(), saturationRadius, offset);
|
||||
|
||||
alx = axis->processDeadzoneAndResponseCurve(alx, innerDeadzone, saturationRadius, offset);
|
||||
aly = axis->processDeadzoneAndResponseCurve(aly, innerDeadzone, saturationRadius, offset);
|
||||
|
||||
auto scaledLengthLeftStick = hypot(alx - offset, aly - offset);
|
||||
if(scaledLengthLeftStick > saturationRadius) {
|
||||
alx = axis->revisePosition(alx, scaledLengthLeftStick, saturationRadius, offset);
|
||||
aly = axis->revisePosition(aly, scaledLengthLeftStick, saturationRadius, offset);
|
||||
}
|
||||
|
||||
double alxBounded = 0.0;
|
||||
double alyBounded = 0.0;
|
||||
axis->applyGateBoundaries(innerDeadzone, cardinalMax, diagonalMax, alx , aly, offset, alxBounded, alyBounded);
|
||||
alx = alxBounded;
|
||||
aly = alyBounded;
|
||||
|
||||
alx = axis->clampAxisToNearestBoundary(alx, offset, cardinalMax);
|
||||
aly = axis->clampAxisToNearestBoundary(aly, offset, cardinalMax);
|
||||
|
||||
alx = axis->counteractPrecisionError(alx);
|
||||
aly = axis->counteractPrecisionError(aly);
|
||||
|
||||
result.append(u8(alx + 128.0));
|
||||
result.append(u8(aly + 128.0));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
struct DualShock : PeripheralDevice {
|
||||
Node::Input::Axis axis;
|
||||
Node::Input::Axis lx;
|
||||
Node::Input::Axis ly;
|
||||
Node::Input::Axis rx;
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace ares::SuperFamicom {
|
||||
|
||||
#include "port.cpp"
|
||||
#include "gamepad/gamepad.cpp"
|
||||
#include "rumble-gamepad/rumble-gamepad.cpp"
|
||||
#include "justifier/justifier.cpp"
|
||||
#include "justifiers/justifiers.cpp"
|
||||
#include "mouse/mouse.cpp"
|
||||
|
||||
@@ -24,6 +24,7 @@ struct Controller {
|
||||
|
||||
#include "port.hpp"
|
||||
#include "gamepad/gamepad.hpp"
|
||||
#include "rumble-gamepad/rumble-gamepad.hpp"
|
||||
#include "justifier/justifier.hpp"
|
||||
#include "justifiers/justifiers.hpp"
|
||||
#include "mouse/mouse.hpp"
|
||||
|
||||
@@ -13,6 +13,7 @@ auto ControllerPort::load(Node::Object parent) -> void {
|
||||
port->setDisconnect([&] { device.reset(); });
|
||||
port->setSupported({
|
||||
"Gamepad",
|
||||
"Rumble Gamepad",
|
||||
"Justifier",
|
||||
"Justifiers",
|
||||
"Mouse",
|
||||
@@ -30,6 +31,7 @@ auto ControllerPort::unload() -> void {
|
||||
|
||||
auto ControllerPort::allocate(string name) -> Node::Peripheral {
|
||||
if(name == "Gamepad" ) device = new Gamepad(port);
|
||||
if(name == "Rumble Gamepad" ) device = new RumbleGamepad(port);
|
||||
if(name == "Justifier" ) device = new Justifier(port);
|
||||
if(name == "Justifiers" ) device = new Justifiers(port);
|
||||
if(name == "Mouse" ) device = new Mouse(port);
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
RumbleGamepad::RumbleGamepad(Node::Port parent) {
|
||||
node = parent->append<Node::Peripheral>("Rumble Gamepad");
|
||||
|
||||
up = node->append<Node::Input::Button>("Up");
|
||||
down = node->append<Node::Input::Button>("Down");
|
||||
left = node->append<Node::Input::Button>("Left");
|
||||
right = node->append<Node::Input::Button>("Right");
|
||||
b = node->append<Node::Input::Button>("B");
|
||||
a = node->append<Node::Input::Button>("A");
|
||||
y = node->append<Node::Input::Button>("Y");
|
||||
x = node->append<Node::Input::Button>("X");
|
||||
l = node->append<Node::Input::Button>("L");
|
||||
r = node->append<Node::Input::Button>("R");
|
||||
select = node->append<Node::Input::Button>("Select");
|
||||
start = node->append<Node::Input::Button>("Start");
|
||||
rumble = node->append<Node::Input::Rumble>("Rumble");
|
||||
}
|
||||
|
||||
auto RumbleGamepad::data() -> n2 {
|
||||
rumbleState = (rumbleState << 1) | iobit();
|
||||
if(rumbleState.bit(8, 15) == 0x72) {
|
||||
rumble->setValues((rumbleState.bit(0,3) * 65535) / 15, (rumbleState.bit(4,7) * 65535) / 15);
|
||||
platform->input(rumble);
|
||||
rumbleState = 0;
|
||||
}
|
||||
|
||||
if(latched == 1) {
|
||||
platform->input(b);
|
||||
return b->value();
|
||||
}
|
||||
|
||||
//note: D-pad physically prevents up+down and left+right from being pressed at the same time
|
||||
switch(counter++) {
|
||||
case 0: return b->value();
|
||||
case 1: return y->value();
|
||||
case 2: return select->value();
|
||||
case 3: return start->value();
|
||||
case 4: return upLatch;
|
||||
case 5: return downLatch;
|
||||
case 6: return leftLatch;
|
||||
case 7: return rightLatch;
|
||||
case 8: return a->value();
|
||||
case 9: return x->value();
|
||||
case 10: return l->value();
|
||||
case 11: return r->value();
|
||||
|
||||
case 12: return 0; //4-bit device signature
|
||||
case 13: return 0;
|
||||
case 14: return 0;
|
||||
case 15: return 0;
|
||||
}
|
||||
|
||||
counter = 16;
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto RumbleGamepad::latch(n1 data) -> void {
|
||||
if(latched == data) return;
|
||||
latched = data;
|
||||
counter = 0;
|
||||
|
||||
if(latched == 0) {
|
||||
platform->input(b);
|
||||
platform->input(y);
|
||||
platform->input(select);
|
||||
platform->input(start);
|
||||
platform->input(up);
|
||||
platform->input(down);
|
||||
platform->input(left);
|
||||
platform->input(right);
|
||||
platform->input(a);
|
||||
platform->input(x);
|
||||
platform->input(l);
|
||||
platform->input(r);
|
||||
|
||||
if(!(up->value() & down->value())) {
|
||||
yHold = 0, upLatch = up->value(), downLatch = down->value();
|
||||
} else if(!yHold) {
|
||||
yHold = 1, swap(upLatch, downLatch);
|
||||
}
|
||||
|
||||
if(!(left->value() & right->value())) {
|
||||
xHold = 0, leftLatch = left->value(), rightLatch = right->value();
|
||||
} else if(!xHold) {
|
||||
xHold = 1, swap(leftLatch, rightLatch);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
struct RumbleGamepad : Controller {
|
||||
Node::Input::Button up;
|
||||
Node::Input::Button down;
|
||||
Node::Input::Button left;
|
||||
Node::Input::Button right;
|
||||
Node::Input::Button b;
|
||||
Node::Input::Button a;
|
||||
Node::Input::Button y;
|
||||
Node::Input::Button x;
|
||||
Node::Input::Button l;
|
||||
Node::Input::Button r;
|
||||
Node::Input::Button select;
|
||||
Node::Input::Button start;
|
||||
Node::Input::Rumble rumble;
|
||||
|
||||
RumbleGamepad(Node::Port);
|
||||
|
||||
auto data() -> n2;
|
||||
auto latch(n1 data) -> void;
|
||||
|
||||
private:
|
||||
n1 latched;
|
||||
n8 counter;
|
||||
|
||||
n1 yHold;
|
||||
n1 upLatch;
|
||||
n1 downLatch;
|
||||
n1 xHold;
|
||||
n1 leftLatch;
|
||||
n1 rightLatch;
|
||||
|
||||
n16 rumbleState;
|
||||
};
|
||||
@@ -23,6 +23,10 @@ auto PPU::load(Node::Object parent) -> void {
|
||||
node = parent->append<Node::Object>("PPU");
|
||||
|
||||
screen = node->append<Node::Video::Screen>("Screen", 564, height() * 2);
|
||||
deepBlackBoost = screen->append<Node::Setting::Boolean>("Deep Black Boost", true, [&](auto value) {
|
||||
screen->resetPalette();
|
||||
});
|
||||
deepBlackBoost->setDynamic(true);
|
||||
screen->colors(1 << 19, {&PPU::color, this});
|
||||
screen->setSize(564, height() * 2);
|
||||
screen->setScale(0.5, 0.5);
|
||||
@@ -32,10 +36,6 @@ auto PPU::load(Node::Object parent) -> void {
|
||||
vramSize = node->append<Node::Setting::Natural>("VRAM", 64_KiB);
|
||||
vramSize->setAllowedValues({64_KiB, 128_KiB});
|
||||
|
||||
deepBlackBoost = screen->append<Node::Setting::Boolean>("Deep Black Boost", true, [&](auto value) {
|
||||
screen->resetPalette();
|
||||
});
|
||||
deepBlackBoost->setDynamic(true);
|
||||
debugger.load(node);
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,12 @@ auto PPU::load(Node::Object parent) -> void {
|
||||
node = parent->append<Node::Object>("PPU");
|
||||
|
||||
screen = node->append<Node::Video::Screen>("Screen", 564, height() * 2);
|
||||
|
||||
deepBlackBoost = screen->append<Node::Setting::Boolean>("Deep Black Boost", true, [&](auto value) {
|
||||
screen->resetPalette();
|
||||
});
|
||||
deepBlackBoost->setDynamic(true);
|
||||
|
||||
screen->colors(1 << 19, {&PPU::color, this});
|
||||
screen->setSize(564, height() * 2);
|
||||
screen->setScale(0.5, 0.5);
|
||||
@@ -49,11 +55,6 @@ auto PPU::load(Node::Object parent) -> void {
|
||||
vramSize = node->append<Node::Setting::Natural>("VRAM", 64_KiB);
|
||||
vramSize->setAllowedValues({64_KiB, 128_KiB});
|
||||
|
||||
deepBlackBoost = screen->append<Node::Setting::Boolean>("Deep Black Boost", true, [&](auto value) {
|
||||
screen->resetPalette();
|
||||
});
|
||||
deepBlackBoost->setDynamic(true);
|
||||
|
||||
debugger.load(node);
|
||||
}
|
||||
|
||||
|
||||
@@ -62,9 +62,9 @@ ares_add_sources(
|
||||
)
|
||||
|
||||
ares_add_sources(
|
||||
CORE #
|
||||
CORE #
|
||||
sg
|
||||
INCLUDED #
|
||||
INCLUDED #
|
||||
keyboard/keyboard.hpp
|
||||
)
|
||||
|
||||
|
||||
+6
-5
@@ -28,17 +28,18 @@ auto PPU::load(Node::Object parent) -> void {
|
||||
const u32 height = 144 + (Model::WonderSwan() ? 13 : 0);
|
||||
|
||||
screen = node->append<Node::Video::Screen>("Screen", width, height);
|
||||
screen->colors(1 << 12, {&PPU::color, this});
|
||||
screen->setSize(width, height);
|
||||
screen->setScale(1.0, 1.0);
|
||||
screen->setAspect(1.0, 1.0);
|
||||
screen->setFillColor(SoC::ASWAN() ? 0xfff : 0);
|
||||
|
||||
colorEmulation = screen->append<Node::Setting::Boolean>("Color Emulation", true, [&](auto value) {
|
||||
screen->resetPalette();
|
||||
});
|
||||
colorEmulation->setDynamic(true);
|
||||
|
||||
screen->colors(1 << 12, {&PPU::color, this});
|
||||
screen->setSize(width, height);
|
||||
screen->setScale(1.0, 1.0);
|
||||
screen->setAspect(1.0, 1.0);
|
||||
screen->setFillColor(SoC::ASWAN() ? 0xfff : 0);
|
||||
|
||||
interframeBlending = screen->append<Node::Setting::Boolean>("Interframe Blending", true, [&](auto value) {
|
||||
screen->setInterframeBlending(value);
|
||||
});
|
||||
|
||||
@@ -77,14 +77,14 @@ function(ares_populate_version_info)
|
||||
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
endif()
|
||||
if(modified)
|
||||
set(ares_git_shorthash "${ares_git_shorthash}-modified")
|
||||
endif()
|
||||
if(NOT ares_git_ref)
|
||||
set(ARES_VERSION "${ares_git_shorthash} - ${ares_git_commit_date}")
|
||||
else()
|
||||
set(ARES_VERSION "${ares_git_ref} - (${ares_git_shorthash} - ${ares_git_commit_date})")
|
||||
if(modified)
|
||||
set(ares_git_shorthash "${ares_git_shorthash}-modified")
|
||||
endif()
|
||||
if(NOT ares_git_ref)
|
||||
set(ARES_VERSION "${ares_git_shorthash} - ${ares_git_commit_date}")
|
||||
else()
|
||||
set(ARES_VERSION "${ares_git_ref} - (${ares_git_shorthash} - ${ares_git_commit_date})")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ macro(librashader_set_soname)
|
||||
unset(_result)
|
||||
endmacro()
|
||||
|
||||
if(OS_LINUX OR OS_FREEBSD OR OS_OPENBSD)
|
||||
if(OS_LINUX OR OS_FREEBSD OR OS_OPENBSD OR ARES_SKIP_DEPS OR (NOT ARES_ENABLE_LIBRASHADER))
|
||||
set(_librashader_path_hint "${CMAKE_SOURCE_DIR}/thirdparty/librashader/include")
|
||||
endif()
|
||||
|
||||
|
||||
@@ -55,11 +55,7 @@ set(
|
||||
/Zc:__cplusplus
|
||||
/utf-8
|
||||
/permissive-
|
||||
$<$<NOT:$<CONFIG:Debug>>:/GL>
|
||||
$<$<NOT:$<CONFIG:Debug>>:/GS->
|
||||
$<$<NOT:$<CONFIG:Debug>>:/Oi>
|
||||
$<$<NOT:$<CONFIG:Debug>>:/Ob2>
|
||||
$<$<NOT:$<CONFIG:Debug>>:/Ot>
|
||||
)
|
||||
|
||||
set(
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"prebuilt": {
|
||||
"version": "2025-05-22",
|
||||
"version": "2025-07-14",
|
||||
"baseUrl": "https://github.com/ares-emulator/ares-deps/releases/download",
|
||||
"label": "Pre-Built ares-deps",
|
||||
"hashes": {
|
||||
"linux-universal": "d24162136d2a310ade86223e8cf099c6e6b93003b35c136e8cbe30ffc2390845",
|
||||
"macos-universal": "2b7bc99370e360c9086ba863ac9cd810aa67928206b799af74830ba6e0a9ffc4",
|
||||
"windows-arm64": "07181e8a56f2f17b26b70ef852a44e4193e5196adc5013fcd1d691d155a49f42",
|
||||
"windows-x64": "a7086281f98d9f0fcad0a833a6410f90b4776a5d274c125aef99fe3a81d2eed5"
|
||||
"linux-universal": "c4679098d2a9cd51c1cacb70555e302e78501e481dcaabc060ca16d6f620128f",
|
||||
"macos-universal": "a93cdd3b82b9fcdc7cc0047480a3dcc93f5ca198f48593f52138bba1f5ba6d77",
|
||||
"windows-arm64": "c7d2e1d8f72f5b6add4e68bbbc684248cdc9556f9a27dbdf62a961b890eb52ea",
|
||||
"windows-x64": "5fe3200331dc5d397b4a3080d3d2c86e2ace48e14b51a6e304c5ca75328726b3"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -73,6 +73,7 @@ target_sources(
|
||||
emulator/mega-32x.cpp
|
||||
emulator/mega-cd-32x.cpp
|
||||
emulator/mega-cd.cpp
|
||||
emulator/mega-ld.cpp
|
||||
emulator/mega-drive.cpp
|
||||
emulator/msx.cpp
|
||||
emulator/msx2.cpp
|
||||
|
||||
@@ -35,10 +35,10 @@ public:
|
||||
if(program._isRunning && !program._programThread) {
|
||||
program._interruptDepth += 1;
|
||||
if(program._interruptDepth == 1) {
|
||||
lock.lock();
|
||||
program.lock.lock();
|
||||
program._interruptWaiting = true;
|
||||
program._programConditionVariable.notify_one();
|
||||
program._programConditionVariable.wait(lock, [] { return program._interruptWorking; });
|
||||
program._programConditionVariable.wait(program.lock, [] { return program._interruptWorking; });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,14 +49,11 @@ public:
|
||||
if(program._interruptDepth == 0) {
|
||||
program._interruptWorking = false;
|
||||
program._interruptWaiting = false;
|
||||
lock.unlock();
|
||||
program.lock.unlock();
|
||||
program._programConditionVariable.notify_one();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_lock<std::mutex> lock{program._programMutex, std::defer_lock};
|
||||
};
|
||||
|
||||
auto locate(const string& name) -> string;
|
||||
@@ -53,6 +53,7 @@ namespace ares::Atari2600 {
|
||||
#include "mega-32x.cpp"
|
||||
#include "mega-cd.cpp"
|
||||
#include "mega-cd-32x.cpp"
|
||||
#include "mega-ld.cpp"
|
||||
#endif
|
||||
|
||||
#ifdef CORE_MS
|
||||
@@ -230,6 +231,7 @@ auto Emulator::construct() -> void {
|
||||
emulators.append(new Mega32X);
|
||||
emulators.append(new MegaCD);
|
||||
emulators.append(new MegaCD32X);
|
||||
emulators.append(new MegaLD);
|
||||
#endif
|
||||
|
||||
#ifdef CORE_SATURN
|
||||
|
||||
@@ -70,6 +70,7 @@ auto FamicomDiskSystem::load(Menu menu) -> void {
|
||||
|
||||
auto FamicomDiskSystem::changeDiskState(string state) -> void
|
||||
{
|
||||
Program::Guard guard;
|
||||
print("Changing disk state to: ", state, "\n");
|
||||
//Eject the disk and give the emulator time to react before re-inserting
|
||||
print("Ejecting disk\n");
|
||||
@@ -77,6 +78,7 @@ auto FamicomDiskSystem::changeDiskState(string state) -> void
|
||||
if(state != "Ejected") {
|
||||
print("Setting disk change timer\n");
|
||||
diskChangeTimer->onActivate([=] {
|
||||
Program::Guard guard;
|
||||
print("Disk change timer activated, setting disk state to: ", state, "\n");
|
||||
diskChangeTimer->setEnabled(false);
|
||||
emulator->notify(state);
|
||||
|
||||
@@ -42,6 +42,7 @@ auto GameBoyAdvance::load(Menu menu) -> void {
|
||||
MenuRadioItem item{&orientationMenu};
|
||||
item.setText(orientation);
|
||||
item.onActivate([=] {
|
||||
Program::Guard guard;
|
||||
if(auto orientations = root->find<ares::Node::Setting::String>("PPU/Screen/Orientation")) {
|
||||
orientations->setValue(orientation);
|
||||
}
|
||||
|
||||
@@ -62,6 +62,7 @@ auto GameBoy::load(Menu menu) -> void {
|
||||
MenuRadioItem item{&colorEmulationMenu};
|
||||
item.setText(setting);
|
||||
item.onActivate([=] {
|
||||
Program::Guard guard;
|
||||
if(auto settings = root->find<ares::Node::Setting::String>("PPU/Screen/Color Emulation")) {
|
||||
settings->setValue(setting);
|
||||
}
|
||||
|
||||
@@ -116,6 +116,7 @@ auto MegaCD32X::load(Menu menu) -> void {
|
||||
MenuItem changeDisc{&menu};
|
||||
changeDisc.setIcon(Icon::Device::Optical);
|
||||
changeDisc.setText("Change Disc").onActivate([&] {
|
||||
Program::Guard guard;
|
||||
save();
|
||||
auto tray = root->find<ares::Node::Port>("Mega CD/Disc Tray");
|
||||
tray->disconnect();
|
||||
@@ -126,6 +127,7 @@ auto MegaCD32X::load(Menu menu) -> void {
|
||||
|
||||
//give the emulator core a few seconds to notice an empty drive state before reconnecting
|
||||
discTrayTimer->onActivate([&] {
|
||||
Program::Guard guard;
|
||||
discTrayTimer->setEnabled(false);
|
||||
auto tray = root->find<ares::Node::Port>("Mega CD/Disc Tray");
|
||||
tray->allocate();
|
||||
|
||||
@@ -121,6 +121,7 @@ auto MegaCD::load(Menu menu) -> void {
|
||||
MenuItem changeDisc{&menu};
|
||||
changeDisc.setIcon(Icon::Device::Optical);
|
||||
changeDisc.setText("Change Disc").onActivate([&] {
|
||||
Program::Guard guard;
|
||||
save();
|
||||
auto tray = root->find<ares::Node::Port>("Mega CD/Disc Tray");
|
||||
tray->disconnect();
|
||||
@@ -131,6 +132,7 @@ auto MegaCD::load(Menu menu) -> void {
|
||||
|
||||
//give the emulator core a few seconds to notice an empty drive state before reconnecting
|
||||
discTrayTimer->onActivate([&] {
|
||||
Program::Guard guard;
|
||||
discTrayTimer->setEnabled(false);
|
||||
auto tray = root->find<ares::Node::Port>("Mega CD/Disc Tray");
|
||||
tray->allocate();
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
struct MegaLD : Emulator {
|
||||
MegaLD();
|
||||
auto load() -> LoadResult override;
|
||||
auto load(Menu) -> void override;
|
||||
auto unload() -> void override;
|
||||
auto save() -> bool override;
|
||||
auto pak(ares::Node::Object) -> shared_pointer<vfs::directory> override;
|
||||
auto changeDiskState(const string state) -> void;
|
||||
|
||||
u32 regionID = 0;
|
||||
sTimer discTrayTimer;
|
||||
};
|
||||
|
||||
MegaLD::MegaLD() {
|
||||
manufacturer = "Sega";
|
||||
name = "Mega LD";
|
||||
|
||||
firmware.append({"BIOS", "US"}); //NTSC-U
|
||||
firmware.append({"BIOS", "Japan"}); //NTSC-J
|
||||
|
||||
for(auto id : range(2)) {
|
||||
InputPort port{string{"Controller Port ", 1 + id}};
|
||||
|
||||
{ InputDevice device{"Fighting Pad"};
|
||||
device.digital("Up", virtualPorts[id].pad.up);
|
||||
device.digital("Down", virtualPorts[id].pad.down);
|
||||
device.digital("Left", virtualPorts[id].pad.left);
|
||||
device.digital("Right", virtualPorts[id].pad.right);
|
||||
device.digital("A", virtualPorts[id].pad.west);
|
||||
device.digital("B", virtualPorts[id].pad.south);
|
||||
device.digital("C", virtualPorts[id].pad.east);
|
||||
device.digital("X", virtualPorts[id].pad.l_bumper);
|
||||
device.digital("Y", virtualPorts[id].pad.north);
|
||||
device.digital("Z", virtualPorts[id].pad.r_bumper);
|
||||
device.digital("Mode", virtualPorts[id].pad.select);
|
||||
device.digital("Start", virtualPorts[id].pad.start);
|
||||
port.append(device); }
|
||||
|
||||
ports.append(port);
|
||||
}
|
||||
}
|
||||
|
||||
auto MegaLD::load() -> LoadResult {
|
||||
game = mia::Medium::create("Mega LD");
|
||||
string location = Emulator::load(game, configuration.game);
|
||||
if(!location) return noFileSelected;
|
||||
LoadResult result = game->load(location);
|
||||
if(result != successful) return result;
|
||||
|
||||
auto region = Emulator::region();
|
||||
//if statements below are ordered by lowest to highest priority
|
||||
if(region == "NTSC-J") regionID = 1;
|
||||
if(region == "NTSC-U") regionID = 0;
|
||||
|
||||
system = mia::System::create("Mega LD");
|
||||
result = system->load(firmware[regionID].location);
|
||||
if(result != successful) {
|
||||
result.firmwareSystemName = "Mega LD";
|
||||
result.firmwareType = firmware[regionID].type;
|
||||
result.firmwareRegion = firmware[regionID].region;
|
||||
result.result = noFirmware;
|
||||
return result;
|
||||
}
|
||||
|
||||
if(!ares::MegaDrive::load(root, {"[Sega] Mega LD (", region, ")"})) return otherError;
|
||||
|
||||
if(auto port = root->find<ares::Node::Port>("Mega CD/Disc Tray")) {
|
||||
port->allocate();
|
||||
port->connect();
|
||||
}
|
||||
|
||||
if(auto port = root->find<ares::Node::Port>("Controller Port 1")) {
|
||||
port->allocate("Fighting Pad");
|
||||
port->connect();
|
||||
}
|
||||
|
||||
if(auto port = root->find<ares::Node::Port>("Controller Port 2")) {
|
||||
port->allocate("Fighting Pad");
|
||||
port->connect();
|
||||
}
|
||||
|
||||
discTrayTimer = Timer{};
|
||||
return successful;
|
||||
}
|
||||
|
||||
auto MegaLD::load(Menu menu) -> void {
|
||||
Group group;
|
||||
Menu changeSideMenu{&menu};
|
||||
changeSideMenu.setIcon(Icon::Device::Optical);
|
||||
changeSideMenu.setText("Change Side");
|
||||
auto sides = game->pak->attribute("medium").split(",").strip();
|
||||
|
||||
MenuRadioItem noDiscItem{&changeSideMenu};
|
||||
noDiscItem.setText("No Disc").onActivate([&] {
|
||||
changeDiskState("No Disc");
|
||||
});
|
||||
group.append(noDiscItem);
|
||||
|
||||
auto checkedItemIndex = (sides.size() > 0 ? group.objectCount() : 0);
|
||||
for(auto side : sides) {
|
||||
MenuRadioItem item{ &changeSideMenu };
|
||||
group.append(item);
|
||||
item.setText(side).onActivate([this, side] { changeDiskState(side); });
|
||||
}
|
||||
group.objects<MenuRadioItem>()[checkedItemIndex].setChecked();
|
||||
}
|
||||
|
||||
auto MegaLD::changeDiskState(const string state) -> void {
|
||||
Program::Guard guard;
|
||||
discTrayTimer->setEnabled(false);
|
||||
save();
|
||||
auto tray = root->find<ares::Node::Port>("Mega CD/Disc Tray");
|
||||
tray->disconnect();
|
||||
|
||||
if(state == "No Disc") return;
|
||||
|
||||
discTrayTimer->onActivate([&, state] {
|
||||
Program::Guard guard;
|
||||
discTrayTimer->setEnabled(false);
|
||||
auto tray = root->find<ares::Node::Port>("Mega CD/Disc Tray");
|
||||
tray->allocate(state);
|
||||
tray->connect();
|
||||
}).setInterval(3000).setEnabled();
|
||||
}
|
||||
|
||||
auto MegaLD::unload() -> void {
|
||||
Emulator::unload();
|
||||
discTrayTimer.reset();
|
||||
}
|
||||
|
||||
auto MegaLD::save() -> bool {
|
||||
root->save();
|
||||
system->save(game->location);
|
||||
game->save(game->location);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto MegaLD::pak(ares::Node::Object node) -> shared_pointer<vfs::directory> {
|
||||
if(node->name() == "Mega Drive") return system->pak;
|
||||
if(node->name() == "Mega CD Disc") return game->pak;
|
||||
return {};
|
||||
}
|
||||
@@ -140,7 +140,7 @@ auto Nintendo64::load() -> LoadResult {
|
||||
port->connect();
|
||||
bool transferPakConnected = false;
|
||||
if(auto port = peripheral->find<ares::Node::Port>("Pak")) {
|
||||
if(id == 0 && game->pak->attribute("tpak").boolean()) {
|
||||
if(game->pak->attribute({"port", id+1, "/tpak"}).boolean()) {
|
||||
#if defined(CORE_GB)
|
||||
auto transferPak = port->allocate("Transfer Pak");
|
||||
port->connect();
|
||||
@@ -161,7 +161,7 @@ auto Nintendo64::load() -> LoadResult {
|
||||
}
|
||||
|
||||
if(!transferPakConnected) {
|
||||
if(id == 0 && game->pak->attribute("cpak").boolean()) {
|
||||
if(game->pak->attribute({"port", id+1, "/cpak"}).boolean()) {
|
||||
gamepad = mia::Pak::create("Nintendo 64");
|
||||
|
||||
//create maximum sized controller pak, file is resized later
|
||||
@@ -169,7 +169,7 @@ auto Nintendo64::load() -> LoadResult {
|
||||
gamepad->load("save.pak", ".pak", game->location);
|
||||
port->allocate("Controller Pak");
|
||||
port->connect();
|
||||
} else if(game->pak->attribute("rpak").boolean()) {
|
||||
} else if(game->pak->attribute({"port", id+1, "/rpak"}).boolean()) {
|
||||
port->allocate("Rumble Pak");
|
||||
port->connect();
|
||||
}
|
||||
@@ -188,6 +188,7 @@ auto Nintendo64::load(Menu menu) -> void {
|
||||
MenuItem changeDisk{&menu};
|
||||
changeDisk.setIcon(Icon::Device::Optical);
|
||||
changeDisk.setText("Change Disk").onActivate([&] {
|
||||
Program::Guard guard;
|
||||
save();
|
||||
auto drive = root->find<ares::Node::Port>("Nintendo 64DD/Disk Drive");
|
||||
drive->disconnect();
|
||||
@@ -198,6 +199,7 @@ auto Nintendo64::load(Menu menu) -> void {
|
||||
|
||||
//give the emulator core a few seconds to notice an empty drive state before reconnecting
|
||||
diskInsertTimer->onActivate([&] {
|
||||
Program::Guard guard;
|
||||
diskInsertTimer->setEnabled(false);
|
||||
auto drive = root->find<ares::Node::Port>("Nintendo 64DD/Disk Drive");
|
||||
drive->allocate();
|
||||
@@ -213,107 +215,107 @@ auto Nintendo64::portMenu(Menu& portMenu, ares::Node::Port port) -> void {
|
||||
const string portNum = port->name()[port->name().length() - 1];
|
||||
|
||||
// remove this check to enable pak menu option for all 4 controllers
|
||||
if(portNum == "1") {
|
||||
if(portMenu.actionCount() > 0) portMenu.append(MenuSeparator());
|
||||
Menu pakMenu{&portMenu};
|
||||
pakMenu.setText("Pak");
|
||||
Group pakGroup;
|
||||
MenuRadioItem nothing{&pakMenu};;
|
||||
nothing.setText("Nothing");
|
||||
nothing.setAttribute<ares::Node::Port>("port", port);
|
||||
nothing.onActivate([=] {
|
||||
Program::Guard guard;
|
||||
auto port = nothing.attribute<ares::Node::Port>("port");
|
||||
const string portName = port->name();
|
||||
if(auto port = emulator->root->find<ares::Node::Port>(portName)) {
|
||||
port->disconnect();
|
||||
auto peripheral = port->allocate("Gamepad");
|
||||
port->connect();
|
||||
}
|
||||
});
|
||||
pakGroup.append(nothing);
|
||||
|
||||
if(portMenu.actionCount() > 0) portMenu.append(MenuSeparator());
|
||||
Menu pakMenu{&portMenu};
|
||||
pakMenu.setText("Pak");
|
||||
Group pakGroup;
|
||||
MenuRadioItem nothing{&pakMenu};;
|
||||
nothing.setText("Nothing");
|
||||
nothing.setAttribute<ares::Node::Port>("port", port);
|
||||
nothing.onActivate([=] {
|
||||
auto port = nothing.attribute<ares::Node::Port>("port");
|
||||
const string portName = port->name();
|
||||
if(auto port = emulator->root->find<ares::Node::Port>(portName)) {
|
||||
port->disconnect();
|
||||
auto peripheral = port->allocate("Gamepad");
|
||||
MenuRadioItem cpak{&pakMenu};
|
||||
cpak.setAttribute<ares::Node::Port>("port", port);
|
||||
cpak.setText("Controller Pak");
|
||||
cpak.onActivate([=] {
|
||||
Program::Guard guard;
|
||||
auto port = cpak.attribute<ares::Node::Port>("port");
|
||||
const string portName = port->name();
|
||||
if(auto port = emulator->root->find<ares::Node::Port>(portName)) {
|
||||
port->disconnect();
|
||||
auto peripheral = port->allocate("Gamepad");
|
||||
port->connect();
|
||||
if(auto port = peripheral->find<ares::Node::Port>("Pak")) {
|
||||
emulator->gamepad = mia::Pak::create("Nintendo 64");
|
||||
emulator->gamepad->pak->append("save.pak", 32_KiB);
|
||||
string pakExt = ".pak";
|
||||
if(portNum != "1") { pakExt = string(".", portNum, ".pak");}
|
||||
emulator->gamepad->load("save.pak", pakExt, emulator->game->location);
|
||||
port->allocate("Controller Pak");
|
||||
port->connect();
|
||||
}
|
||||
});
|
||||
pakGroup.append(nothing);
|
||||
}
|
||||
});
|
||||
pakGroup.append(cpak);
|
||||
|
||||
MenuRadioItem cpak{&pakMenu};
|
||||
cpak.setAttribute<ares::Node::Port>("port", port);
|
||||
cpak.setText("Controller Pak");
|
||||
cpak.onActivate([=] {
|
||||
auto port = cpak.attribute<ares::Node::Port>("port");
|
||||
const string portName = port->name();
|
||||
if(auto port = emulator->root->find<ares::Node::Port>(portName)) {
|
||||
port->disconnect();
|
||||
auto peripheral = port->allocate("Gamepad");
|
||||
MenuRadioItem rpak{&pakMenu};
|
||||
rpak.setAttribute<ares::Node::Port>("port", port);
|
||||
rpak.setText("Rumble Pak");
|
||||
rpak.onActivate([=] {
|
||||
Program::Guard guard;
|
||||
auto port = rpak.attribute<ares::Node::Port>("port");
|
||||
const string portName = port->name();
|
||||
if(auto port = emulator->root->find<ares::Node::Port>(portName)) {
|
||||
port->disconnect();
|
||||
auto peripheral = port->allocate("Gamepad");
|
||||
port->connect();
|
||||
if(auto port = peripheral->find<ares::Node::Port>("Pak")) {
|
||||
port->allocate("Rumble Pak");
|
||||
port->connect();
|
||||
if(auto port = peripheral->find<ares::Node::Port>("Pak")) {
|
||||
emulator->gamepad = mia::Pak::create("Nintendo 64");
|
||||
emulator->gamepad->pak->append("save.pak", 32_KiB);
|
||||
string pakExt = ".pak";
|
||||
if(portNum != "1") { pakExt = string(".", portNum, ".pak");}
|
||||
emulator->gamepad->load("save.pak", pakExt, emulator->game->location);
|
||||
port->allocate("Controller Pak");
|
||||
port->connect();
|
||||
}
|
||||
}
|
||||
});
|
||||
pakGroup.append(cpak);
|
||||
}
|
||||
});
|
||||
pakGroup.append(rpak);
|
||||
|
||||
MenuRadioItem rpak{&pakMenu};
|
||||
rpak.setAttribute<ares::Node::Port>("port", port);
|
||||
rpak.setText("Rumble Pak");
|
||||
rpak.onActivate([=] {
|
||||
auto port = rpak.attribute<ares::Node::Port>("port");
|
||||
const string portName = port->name();
|
||||
if(auto port = emulator->root->find<ares::Node::Port>(portName)) {
|
||||
port->disconnect();
|
||||
auto peripheral = port->allocate("Gamepad");
|
||||
port->connect();
|
||||
if(auto port = peripheral->find<ares::Node::Port>("Pak")) {
|
||||
port->allocate("Rumble Pak");
|
||||
port->connect();
|
||||
}
|
||||
}
|
||||
});
|
||||
pakGroup.append(rpak);
|
||||
|
||||
MenuRadioItem tpak{&pakMenu};
|
||||
tpak.setAttribute<ares::Node::Port>("port", port);
|
||||
tpak.setText("Transfer Pak");
|
||||
tpak.onActivate([=] {
|
||||
auto port = tpak.attribute<ares::Node::Port>("port");
|
||||
const string portName = port->name();
|
||||
if(auto port = emulator->root->find<ares::Node::Port>(portName)) {
|
||||
port->disconnect();
|
||||
auto peripheral = port->allocate("Gamepad");
|
||||
port->connect();
|
||||
if(auto port = peripheral->find<ares::Node::Port>("Pak")) {
|
||||
MenuRadioItem tpak{&pakMenu};
|
||||
tpak.setAttribute<ares::Node::Port>("port", port);
|
||||
tpak.setText("Transfer Pak");
|
||||
tpak.onActivate([=] {
|
||||
Program::Guard guard;
|
||||
auto port = tpak.attribute<ares::Node::Port>("port");
|
||||
const string portName = port->name();
|
||||
if(auto port = emulator->root->find<ares::Node::Port>(portName)) {
|
||||
port->disconnect();
|
||||
auto peripheral = port->allocate("Gamepad");
|
||||
port->connect();
|
||||
if(auto port = peripheral->find<ares::Node::Port>("Pak")) {
|
||||
#if defined(CORE_GB)
|
||||
emulator->gb.reset();
|
||||
auto transferPak = port->allocate("Transfer Pak");
|
||||
port->connect();
|
||||
emulator->gb.reset();
|
||||
auto transferPak = port->allocate("Transfer Pak");
|
||||
port->connect();
|
||||
|
||||
if(auto slot = transferPak->find<ares::Node::Port>("Cartridge Slot")) {
|
||||
emulator->gb = mia::Medium::create("Game Boy Color");
|
||||
string tmpPath;
|
||||
if(emulator->gb->load(emulator->load(emulator->gb, tmpPath)) == successful) {
|
||||
slot->allocate();
|
||||
slot->connect();
|
||||
} else {
|
||||
port->disconnect();
|
||||
emulator->gb.reset();
|
||||
}
|
||||
if(auto slot = transferPak->find<ares::Node::Port>("Cartridge Slot")) {
|
||||
emulator->gb = mia::Medium::create("Game Boy Color");
|
||||
string tmpPath;
|
||||
if(emulator->gb->load(emulator->load(emulator->gb, tmpPath)) == successful) {
|
||||
slot->allocate();
|
||||
slot->connect();
|
||||
} else {
|
||||
port->disconnect();
|
||||
emulator->gb.reset();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
}
|
||||
});
|
||||
pakGroup.append(tpak);
|
||||
}
|
||||
});
|
||||
pakGroup.append(tpak);
|
||||
|
||||
// set currently enabled pak
|
||||
// Note: based on initialization routine in desktop-ui/emulator/nintendo-64.cpp & nintedo-64dd.cpp
|
||||
if(emulator->game->pak->attribute("tpak").boolean() && portNum == "1") tpak.setChecked();
|
||||
else if(emulator->game->pak->attribute("cpak").boolean() && portNum == "1") cpak.setChecked();
|
||||
else if(emulator->game->pak->attribute("rpak").boolean()) rpak.setChecked();
|
||||
else nothing.setChecked();
|
||||
}
|
||||
// set currently enabled pak
|
||||
if(emulator->game->pak->attribute({"port", portNum, "/tpak"}).boolean()) tpak.setChecked();
|
||||
else if(emulator->game->pak->attribute({"port", portNum, "/cpak"}).boolean()) cpak.setChecked();
|
||||
else if(emulator->game->pak->attribute({"port", portNum, "/rpak"}).boolean()) rpak.setChecked();
|
||||
else nothing.setChecked();
|
||||
}
|
||||
|
||||
auto Nintendo64::unload() -> void {
|
||||
|
||||
@@ -164,6 +164,7 @@ auto Nintendo64DD::load(Menu menu) -> void {
|
||||
MenuItem changeDisk{&menu};
|
||||
changeDisk.setIcon(Icon::Device::Optical);
|
||||
changeDisk.setText("Change Disk").onActivate([&] {
|
||||
Program::Guard guard;
|
||||
save();
|
||||
auto drive = root->find<ares::Node::Port>("Nintendo 64DD/Disk Drive");
|
||||
drive->disconnect();
|
||||
@@ -174,6 +175,7 @@ auto Nintendo64DD::load(Menu menu) -> void {
|
||||
|
||||
//give the emulator core a few seconds to notice an empty drive state before reconnecting
|
||||
diskInsertTimer->onActivate([&] {
|
||||
Program::Guard guard;
|
||||
diskInsertTimer->setEnabled(false);
|
||||
auto drive = root->find<ares::Node::Port>("Nintendo 64DD/Disk Drive");
|
||||
drive->allocate();
|
||||
|
||||
@@ -138,6 +138,7 @@ auto PlayStation::load(Menu menu) -> void {
|
||||
MenuItem changeDisc{&menu};
|
||||
changeDisc.setIcon(Icon::Device::Optical);
|
||||
changeDisc.setText("Change Disc").onActivate([&] {
|
||||
Program::Guard guard;
|
||||
save();
|
||||
auto tray = root->find<ares::Node::Port>("PlayStation/Disc Tray");
|
||||
tray->disconnect();
|
||||
@@ -148,6 +149,7 @@ auto PlayStation::load(Menu menu) -> void {
|
||||
|
||||
//give the emulator core a few seconds to notice an empty drive state before reconnecting
|
||||
discTrayTimer->onActivate([&] {
|
||||
Program::Guard guard;
|
||||
discTrayTimer->setEnabled(false);
|
||||
auto tray = root->find<ares::Node::Port>("PlayStation/Disc Tray");
|
||||
tray->allocate();
|
||||
|
||||
@@ -29,6 +29,22 @@ SuperFamicom::SuperFamicom() {
|
||||
device.digital("Start", virtualPorts[id].pad.start);
|
||||
port.append(device); }
|
||||
|
||||
{ InputDevice device{"Rumble Gamepad"};
|
||||
device.digital("Up", virtualPorts[id].pad.up);
|
||||
device.digital("Down", virtualPorts[id].pad.down);
|
||||
device.digital("Left", virtualPorts[id].pad.left);
|
||||
device.digital("Right", virtualPorts[id].pad.right);
|
||||
device.digital("B", virtualPorts[id].pad.south);
|
||||
device.digital("A", virtualPorts[id].pad.east);
|
||||
device.digital("Y", virtualPorts[id].pad.west);
|
||||
device.digital("X", virtualPorts[id].pad.north);
|
||||
device.digital("L", virtualPorts[id].pad.l_bumper);
|
||||
device.digital("R", virtualPorts[id].pad.r_bumper);
|
||||
device.digital("Select", virtualPorts[id].pad.select);
|
||||
device.digital("Start", virtualPorts[id].pad.start);
|
||||
device.rumble("Rumble", virtualPorts[id].pad.rumble);
|
||||
port.append(device); }
|
||||
|
||||
{ InputDevice device{"Justifier"};
|
||||
device.relative("X", virtualPorts[id].mouse.x);
|
||||
device.relative("Y", virtualPorts[id].mouse.y);
|
||||
|
||||
@@ -39,6 +39,7 @@ auto WonderSwanColor::load(Menu menu) -> void {
|
||||
MenuRadioItem item{&orientationMenu};
|
||||
item.setText(orientation);
|
||||
item.onActivate([=] {
|
||||
Program::Guard guard;
|
||||
if(auto orientations = root->find<ares::Node::Setting::String>("PPU/Screen/Orientation")) {
|
||||
orientations->setValue(orientation);
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ auto WonderSwan::load(Menu menu) -> void {
|
||||
MenuRadioItem item{&orientationMenu};
|
||||
item.setText(orientation);
|
||||
item.onActivate([=] {
|
||||
Program::Guard guard;
|
||||
if(auto orientations = root->find<ares::Node::Setting::String>("PPU/Screen/Orientation")) {
|
||||
orientations->setValue(orientation);
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ auto InputManager::createHotkeys() -> void {
|
||||
auto InputManager::pollHotkeys() -> void {
|
||||
if(Application::modal()) return;
|
||||
|
||||
if(!driverSettings.inputDefocusAllow.checked()) {
|
||||
if(settings.input.defocus != "Allow") {
|
||||
if (!presentation.focused() && !ruby::video.fullScreen()) return;
|
||||
}
|
||||
|
||||
|
||||
@@ -517,8 +517,10 @@ auto InputManager::poll(bool force) -> void {
|
||||
if(changed) {
|
||||
this->devices = devices;
|
||||
bind();
|
||||
inputSettings.refresh();
|
||||
hotkeySettings.refresh();
|
||||
if(settingsWindow.initialized) {
|
||||
inputSettings.refresh();
|
||||
hotkeySettings.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,36 +35,43 @@ Presentation::Presentation() {
|
||||
setAlignment(Alignment::Center);
|
||||
});
|
||||
videoOutputMenu.setText("Output").setIcon(Icon::Emblem::Image);
|
||||
videoOutputPixelPerfect.setText("Pixel Perfect").onActivate([&] {
|
||||
settings.video.output = "Perfect";
|
||||
});
|
||||
videoOutputFixedScale.setText("Scale (Fixed)").onActivate([&] {
|
||||
settings.video.output = "Fixed";
|
||||
});
|
||||
videoOutputIntegerScale.setText("Scale (Integer)").onActivate([&] {
|
||||
settings.video.output = "Integer";
|
||||
});
|
||||
videoOutputScale.setText("Scale (Best Fit)").onActivate([&] {
|
||||
videoOutputScale.setText("Scale: Best Fit").onActivate([&] {
|
||||
settings.video.output = "Scale";
|
||||
});
|
||||
videoOutputStretch.setText("Stretch").onActivate([&] {
|
||||
videoOutputIntegerScale.setText("Scale: Integer").onActivate([&] {
|
||||
settings.video.output = "Integer";
|
||||
});
|
||||
videoOutputStretch.setText("Scale: Stretch to Fill").onActivate([&] {
|
||||
settings.video.output = "Stretch";
|
||||
});
|
||||
|
||||
if(settings.video.output == "Perfect" ) videoOutputPixelPerfect.setChecked();
|
||||
if(settings.video.output == "Fixed" ) videoOutputFixedScale.setChecked();
|
||||
if(settings.video.output == "Integer" ) videoOutputIntegerScale.setChecked();
|
||||
if(settings.video.output == "Scale" ) videoOutputScale.setChecked();
|
||||
if(settings.video.output == "Stretch" ) videoOutputStretch.setChecked();
|
||||
|
||||
videoAspectCorrection.setText("Aspect Correction").setChecked(settings.video.aspectCorrection).onToggle([&] {
|
||||
settings.video.aspectCorrection = videoAspectCorrection.checked();
|
||||
videoAspectCorrectionNone.setText("Aspect: No correction").onActivate([&] {
|
||||
settings.video.aspectCorrection = "None";
|
||||
if(settings.video.adaptiveSizing) resizeWindow();
|
||||
});
|
||||
videoAdaptiveSizing.setText("Adaptive Sizing").setChecked(settings.video.adaptiveSizing).onToggle([&] {
|
||||
|
||||
videoAspectCorrectionStandard.setText("Aspect: Standard").onActivate([&] {
|
||||
settings.video.aspectCorrection = "Standard";
|
||||
if(settings.video.adaptiveSizing) resizeWindow();
|
||||
});
|
||||
|
||||
videoAspectCorrectionAnamorphic.setText("Aspect: Anamorphic (16:9)").onActivate([&] {
|
||||
settings.video.aspectCorrection = "Anamorphic";
|
||||
if(settings.video.adaptiveSizing) resizeWindow();
|
||||
});
|
||||
|
||||
if(settings.video.aspectCorrection == "None") videoAspectCorrectionNone.setChecked();
|
||||
if(settings.video.aspectCorrection == "Standard") videoAspectCorrectionStandard.setChecked();
|
||||
if(settings.video.aspectCorrection == "Anamorphic") videoAspectCorrectionAnamorphic.setChecked();
|
||||
|
||||
videoAdaptiveSizing.setText("Window: Auto resize").setChecked(settings.video.adaptiveSizing).onToggle([&] {
|
||||
if(settings.video.adaptiveSizing = videoAdaptiveSizing.checked()) resizeWindow();
|
||||
});
|
||||
videoAutoCentering.setText("Auto Centering").setChecked(settings.video.autoCentering).onToggle([&] {
|
||||
videoAutoCentering.setText("Window: Auto center").setChecked(settings.video.autoCentering).onToggle([&] {
|
||||
if(settings.video.autoCentering = videoAutoCentering.checked()) resizeWindow();
|
||||
});
|
||||
videoShaderMenu.setText("Shader").setIcon(Icon::Emblem::Image);
|
||||
@@ -295,7 +302,6 @@ Presentation::Presentation() {
|
||||
auto Presentation::resizeWindow() -> void {
|
||||
if(fullScreen()) setFullScreen(false);
|
||||
if(maximized()) return;
|
||||
if(settings.video.output == "Fixed") return;
|
||||
|
||||
u32 multiplier = settings.video.multiplier;
|
||||
u32 viewportWidth = 320 * multiplier;
|
||||
@@ -305,7 +311,8 @@ auto Presentation::resizeWindow() -> void {
|
||||
auto& node = program.screens.first();
|
||||
u32 videoWidth = node->width() * node->scaleX();
|
||||
u32 videoHeight = node->height() * node->scaleY();
|
||||
if(settings.video.aspectCorrection) videoWidth = videoWidth * node->aspectX() / node->aspectY();
|
||||
if(settings.video.aspectCorrection != "None") videoWidth = videoWidth * node->aspectX() / node->aspectY();
|
||||
if(settings.video.aspectCorrection == "Anamorphic") videoWidth = videoWidth * 4 / 3;
|
||||
if(node->rotation() == 90 || node->rotation() == 270) swap(videoWidth, videoHeight);
|
||||
|
||||
viewportWidth = videoWidth * multiplier;
|
||||
@@ -556,6 +563,7 @@ auto Presentation::refreshSystemMenu() -> void {
|
||||
{ MenuRadioItem peripheralItem{&portMenu};
|
||||
peripheralItem.setAttribute<ares::Node::Port>("port", port);
|
||||
peripheralItem.setText("Nothing");
|
||||
if(!port->connected()) peripheralItem.setChecked();
|
||||
peripheralItem.onActivate([=] {
|
||||
auto port = peripheralItem.attribute<ares::Node::Port>("port");
|
||||
port->disconnect();
|
||||
|
||||
@@ -19,15 +19,17 @@ struct Presentation : Window {
|
||||
Menu videoSizeMenu{&settingsMenu};
|
||||
Group videoSizeGroup;
|
||||
Menu videoOutputMenu{&settingsMenu};
|
||||
MenuRadioItem videoOutputPixelPerfect{&videoOutputMenu};
|
||||
MenuRadioItem videoOutputFixedScale{&videoOutputMenu};
|
||||
MenuRadioItem videoOutputIntegerScale{&videoOutputMenu};
|
||||
MenuRadioItem videoOutputScale{&videoOutputMenu};
|
||||
MenuRadioItem videoOutputIntegerScale{&videoOutputMenu};
|
||||
MenuRadioItem videoOutputStretch{&videoOutputMenu};
|
||||
Group videoOutputGroup{&videoOutputPixelPerfect, &videoOutputFixedScale, &videoOutputIntegerScale,
|
||||
&videoOutputScale, &videoOutputStretch};
|
||||
Group videoOutputGroup{&videoOutputIntegerScale, &videoOutputScale, &videoOutputStretch};
|
||||
MenuSeparator videoOutputSeparator{&videoOutputMenu};
|
||||
MenuCheckItem videoAspectCorrection{&videoOutputMenu};
|
||||
MenuRadioItem videoAspectCorrectionNone{&videoOutputMenu};
|
||||
MenuRadioItem videoAspectCorrectionStandard{&videoOutputMenu};
|
||||
MenuRadioItem videoAspectCorrectionAnamorphic{&videoOutputMenu};
|
||||
Group videoAspectCorrectionGroup{&videoAspectCorrectionNone, &videoAspectCorrectionStandard,
|
||||
&videoAspectCorrectionAnamorphic};
|
||||
MenuSeparator videoOutputSeparator2{&videoOutputMenu};
|
||||
MenuCheckItem videoAdaptiveSizing{&videoOutputMenu};
|
||||
MenuCheckItem videoAutoCentering{&videoOutputMenu};
|
||||
Menu videoShaderMenu{&settingsMenu};
|
||||
|
||||
@@ -74,7 +74,8 @@ auto Program::video(ares::Node::Video::Screen node, const u32* data, u32 pitch,
|
||||
|
||||
u32 videoWidth = node->width() * node->scaleX();
|
||||
u32 videoHeight = node->height() * node->scaleY();
|
||||
if(settings.video.aspectCorrection) videoWidth = videoWidth * node->aspectX() / node->aspectY();
|
||||
if(settings.video.aspectCorrection != "None") videoWidth = videoWidth * node->aspectX() / node->aspectY();
|
||||
if(settings.video.aspectCorrection == "Anamorphic") videoWidth = videoWidth * 4 / 3;
|
||||
if(node->rotation() == 90 || node->rotation() == 270) swap(videoWidth, videoHeight);
|
||||
|
||||
ruby::video.lock();
|
||||
@@ -86,16 +87,6 @@ auto Program::video(ares::Node::Video::Screen node, const u32* data, u32 pitch,
|
||||
u32 outputWidth = videoWidth * multiplier;
|
||||
u32 outputHeight = videoHeight * multiplier;
|
||||
|
||||
if(settings.video.output == "Perfect") {
|
||||
outputWidth = videoWidth;
|
||||
outputHeight = videoHeight;
|
||||
}
|
||||
|
||||
if(settings.video.output == "Fixed") {
|
||||
outputWidth = videoWidth * settings.video.multiplier;
|
||||
outputHeight = videoHeight * settings.video.multiplier;
|
||||
}
|
||||
|
||||
if(multiplier == 0 || settings.video.output == "Scale") {
|
||||
f32 multiplierX = (f32)viewportWidth / (f32)videoWidth;
|
||||
f32 multiplierY = (f32)viewportHeight / (f32)videoHeight;
|
||||
|
||||
@@ -17,10 +17,6 @@ auto Program::create() -> void {
|
||||
audioDriverUpdate();
|
||||
inputDriverUpdate();
|
||||
|
||||
driverSettings.videoRefresh();
|
||||
driverSettings.audioRefresh();
|
||||
driverSettings.inputRefresh();
|
||||
|
||||
_isRunning = true;
|
||||
worker = thread::create({&Program::emulatorRunLoop, this});
|
||||
|
||||
@@ -50,7 +46,7 @@ auto Program::waitForInterrupts() -> void {
|
||||
std::unique_lock<std::mutex> lock(_programMutex);
|
||||
_interruptWorking = true;
|
||||
_programConditionVariable.notify_one();
|
||||
_programConditionVariable.wait(lock, [this] { return !_interruptWorking; });
|
||||
_programConditionVariable.wait(lock, [this] { return !_interruptWorking || _quitting; });
|
||||
}
|
||||
|
||||
auto Program::emulatorRunLoop(uintptr_t) -> void {
|
||||
@@ -60,6 +56,7 @@ auto Program::emulatorRunLoop(uintptr_t) -> void {
|
||||
// Allow other threads to carry out tasks between emulator run loop iterations
|
||||
if(_interruptWaiting) {
|
||||
waitForInterrupts();
|
||||
continue;
|
||||
}
|
||||
if(!emulator) {
|
||||
usleep(20 * 1000);
|
||||
@@ -141,7 +138,10 @@ auto Program::main() -> void {
|
||||
}
|
||||
|
||||
auto Program::quit() -> void {
|
||||
Program::Guard guard;
|
||||
_quitting = true;
|
||||
lock.unlock();
|
||||
_programConditionVariable.notify_all();
|
||||
worker.join();
|
||||
program._isRunning = false;
|
||||
unload();
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
struct Program : ares::Platform {
|
||||
auto create() -> void;
|
||||
auto main() -> void;
|
||||
auto emulatorRunLoop(uintptr_t) -> void;
|
||||
auto quit() -> void;
|
||||
auto waitForInterrupts() -> void;
|
||||
|
||||
//platform.cpp
|
||||
auto attach(ares::Node::Object) -> void override;
|
||||
@@ -110,9 +108,14 @@ struct Program : ares::Platform {
|
||||
std::recursive_mutex inputMutex;
|
||||
|
||||
private:
|
||||
/// Routine used while the emulator thread is waiting for work to complete that modifies its state.
|
||||
auto waitForInterrupts() -> void;
|
||||
/// The main run loop for the emulator thread spawned on startup.
|
||||
auto emulatorRunLoop(uintptr_t) -> void;
|
||||
|
||||
atomic<bool> _quitting = false;
|
||||
atomic<bool> _needsResize = false;
|
||||
|
||||
|
||||
/// Mutex used to manage access to the status message queue.
|
||||
std::recursive_mutex _messageMutex;
|
||||
|
||||
@@ -132,6 +135,8 @@ private:
|
||||
NALL_USED auto getProgramThreadValue() -> bool {
|
||||
return _programThread;
|
||||
}
|
||||
/// Unique lock used by `Program::Guard` instances to synchronize interrupts. Generally this should not be used directly, with synchronization happening via `Program::Guard` construction. However, it is still exposed for exceptions such as the exit handler in `Program::quit()`.
|
||||
std::unique_lock<std::mutex> lock{_programMutex, std::defer_lock};
|
||||
};
|
||||
|
||||
extern Program program;
|
||||
@@ -17,7 +17,7 @@ auto Program::updateMessage() -> void {
|
||||
presentation.statusLeft.setText();
|
||||
}
|
||||
|
||||
if(vblanksPerSecond > 0) {
|
||||
if(vblanksPerSecond > 0 && !paused) {
|
||||
presentation.statusRight.setText({ vblanksPerSecond.load(), " VPS" });
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ auto Program::updateMessage() -> void {
|
||||
}
|
||||
|
||||
|
||||
bool defocused = driverSettings.inputDefocusPause.checked() && !ruby::video.fullScreen() && !presentation.focused();
|
||||
bool defocused = settings.input.defocus == "Pause" && !ruby::video.fullScreen() && !presentation.focused();
|
||||
if(emulator && defocused) message.text = "Paused";
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ auto Program::mute() -> void {
|
||||
auto Program::paletteUpdate() -> void {
|
||||
Program::Guard guard;
|
||||
if(!emulator) return;
|
||||
if(!emulator->root) return;
|
||||
for(auto& screen : emulator->root->find<ares::Node::Video::Screen>()) {
|
||||
screen->setLuminance(settings.video.luminance);
|
||||
screen->setSaturation(settings.video.saturation);
|
||||
|
||||
@@ -4,17 +4,16 @@ auto DriverSettings::construct() -> void {
|
||||
|
||||
videoLabel.setText("Video").setFont(Font().setBold());
|
||||
videoDriverList.onChange([&] {
|
||||
bool enabled = false;
|
||||
if(videoDriverList.selected().text() != settings.video.driver) { enabled = true; }
|
||||
videoDriverAssign.setEnabled(enabled);
|
||||
});
|
||||
videoDriverLabel.setText("Driver:");
|
||||
videoDriverAssign.setText("Apply").setEnabled(false).onActivate([&] {
|
||||
settings.video.driver = videoDriverList.selected().text();
|
||||
if (videoDriverUpdate()) {
|
||||
videoDriverAssign.setEnabled(false);
|
||||
if(videoDriverList.selected().text() != settings.video.driver) {
|
||||
auto previous = settings.video.driver;
|
||||
settings.video.driver = videoDriverList.selected().text();
|
||||
if (!videoDriverUpdate()) {
|
||||
settings.video.driver = previous;
|
||||
videoRefresh();
|
||||
}
|
||||
}
|
||||
});
|
||||
videoDriverLabel.setText("Driver:");
|
||||
videoMonitorLabel.setText("Fullscreen monitor:");
|
||||
videoMonitorList.onChange([&] {
|
||||
settings.video.monitor = videoMonitorList.selected().text();
|
||||
@@ -59,17 +58,16 @@ auto DriverSettings::construct() -> void {
|
||||
|
||||
audioLabel.setText("Audio").setFont(Font().setBold());
|
||||
audioDriverList.onChange([&] {
|
||||
bool enabled = false;
|
||||
if(audioDriverList.selected().text() != settings.audio.driver) { enabled = true; }
|
||||
audioDriverAssign.setEnabled(enabled);
|
||||
});
|
||||
audioDriverLabel.setText("Driver:");
|
||||
audioDriverAssign.setText("Apply").setEnabled(false).onActivate([&] {
|
||||
settings.audio.driver = audioDriverList.selected().text();
|
||||
if (audioDriverUpdate()) {
|
||||
audioDriverAssign.setEnabled(false);
|
||||
if(audioDriverList.selected().text() != settings.audio.driver) {
|
||||
auto previous = settings.audio.driver;
|
||||
settings.audio.driver = audioDriverList.selected().text();
|
||||
if (!audioDriverUpdate()) {
|
||||
settings.audio.driver = previous;
|
||||
audioRefresh();
|
||||
}
|
||||
}
|
||||
});
|
||||
audioDriverLabel.setText("Driver:");
|
||||
audioDeviceLabel.setText("Output device:");
|
||||
audioDeviceList.onChange([&] {
|
||||
settings.audio.device = audioDeviceList.selected().text();
|
||||
@@ -106,17 +104,16 @@ auto DriverSettings::construct() -> void {
|
||||
|
||||
inputLabel.setText("Input").setFont(Font().setBold());
|
||||
inputDriverList.onChange([&] {
|
||||
bool enabled = false;
|
||||
if(inputDriverList.selected().text() != settings.input.driver) { enabled = true; }
|
||||
inputDriverAssign.setEnabled(enabled);
|
||||
});
|
||||
inputDriverLabel.setText("Driver:");
|
||||
inputDriverAssign.setText("Apply").setEnabled(false).onActivate([&] {
|
||||
settings.input.driver = inputDriverList.selected().text();
|
||||
if (inputDriverUpdate()) {
|
||||
inputDriverAssign.setEnabled(false);
|
||||
if(inputDriverList.selected().text() != settings.input.driver) {
|
||||
auto previous = settings.input.driver;
|
||||
settings.input.driver = inputDriverList.selected().text();
|
||||
if (!inputDriverUpdate()) {
|
||||
settings.input.driver = previous;
|
||||
inputRefresh();
|
||||
}
|
||||
}
|
||||
});
|
||||
inputDriverLabel.setText("Driver:");
|
||||
inputDefocusLabel.setText("When focus is lost:");
|
||||
inputDefocusPause.setText("Pause emulation").onActivate([&] {
|
||||
settings.input.defocus = "Pause";
|
||||
@@ -130,8 +127,6 @@ auto DriverSettings::construct() -> void {
|
||||
if(settings.input.defocus == "Pause") inputDefocusPause.setChecked();
|
||||
if(settings.input.defocus == "Block") inputDefocusBlock.setChecked();
|
||||
if(settings.input.defocus == "Allow") inputDefocusAllow.setChecked();
|
||||
|
||||
driverApplyHint.setText("Note: You must click on the 'Apply' button to update and save changes to driver selection.").setFont(Font().setSize(8.0)).setForegroundColor(SystemColor::Sublabel);
|
||||
|
||||
videoDriverLayout.setPadding(12_sx, 0);
|
||||
videoPropertyLayout.setPadding(12_sx, 0);
|
||||
@@ -150,9 +145,6 @@ auto DriverSettings::videoRefresh() -> void {
|
||||
ComboButtonItem item{&videoDriverList};
|
||||
item.setText(driver);
|
||||
if(driver == ruby::video.driver()) item.setSelected();
|
||||
if(settings.video.driver == ruby::video.driver()) {
|
||||
videoDriverAssign.setEnabled(false);
|
||||
}
|
||||
}
|
||||
videoMonitorList.reset();
|
||||
for(auto& monitor : ruby::video.hasMonitors()) {
|
||||
@@ -198,9 +190,6 @@ auto DriverSettings::audioRefresh() -> void {
|
||||
ComboButtonItem item{&audioDriverList};
|
||||
item.setText(driver);
|
||||
if(driver == ruby::audio.driver()) item.setSelected();
|
||||
if(settings.audio.driver == ruby::audio.driver()) {
|
||||
audioDriverAssign.setEnabled(false);
|
||||
}
|
||||
}
|
||||
audioDeviceList.reset();
|
||||
for(auto& device : ruby::audio.hasDevices()) {
|
||||
@@ -244,9 +233,6 @@ auto DriverSettings::inputRefresh() -> void {
|
||||
ComboButtonItem item{&inputDriverList};
|
||||
item.setText(driver);
|
||||
if(driver == ruby::input.driver()) item.setSelected();
|
||||
if(settings.input.driver == ruby::input.driver()) {
|
||||
inputDriverAssign.setEnabled(false);
|
||||
}
|
||||
}
|
||||
VerticalLayout::resize();
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ auto Settings::process(bool load) -> void {
|
||||
bind(string, "Video/Shader", video.shader);
|
||||
bind(natural, "Video/Multiplier", video.multiplier);
|
||||
bind(string, "Video/Output", video.output);
|
||||
bind(boolean, "Video/AspectCorrection", video.aspectCorrection);
|
||||
bind(string, "Video/AspectCorrectionMode", video.aspectCorrection);
|
||||
bind(boolean, "Video/AdaptiveSizing", video.adaptiveSizing);
|
||||
bind(boolean, "Video/AutoCentering", video.autoCentering);
|
||||
bind(real, "Video/Luminance", video.luminance);
|
||||
@@ -178,7 +178,7 @@ auto Settings::process(bool load) -> void {
|
||||
|
||||
//
|
||||
|
||||
SettingsWindow::SettingsWindow() {
|
||||
auto SettingsWindow::initialize() -> void {
|
||||
onClose([&] {
|
||||
settings.save();
|
||||
setVisible(false);
|
||||
@@ -231,9 +231,15 @@ SettingsWindow::SettingsWindow() {
|
||||
setSize({700_sx, 425_sy});
|
||||
setAlignment({0.0, 1.0});
|
||||
setResizable(false);
|
||||
|
||||
driverSettings.videoRefresh();
|
||||
driverSettings.audioRefresh();
|
||||
driverSettings.inputRefresh();
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
auto SettingsWindow::show(const string& panel) -> void {
|
||||
if(!initialized) initialize();
|
||||
for(auto item : panelList.items()) {
|
||||
if(item.text() == panel) {
|
||||
item.setSelected();
|
||||
|
||||
@@ -18,7 +18,7 @@ struct Settings : Markup::Node {
|
||||
string shader = "None";
|
||||
u32 multiplier = 2;
|
||||
string output = "Scale";
|
||||
bool aspectCorrection = true;
|
||||
string aspectCorrection = "Standard";
|
||||
bool adaptiveSizing = true;
|
||||
bool autoCentering = false;
|
||||
|
||||
@@ -355,7 +355,6 @@ struct DriverSettings : VerticalLayout {
|
||||
HorizontalLayout videoDriverLayout{this, Size{~0, 0}};
|
||||
Label videoDriverLabel{&videoDriverLayout, Size{0, 0}};
|
||||
ComboButton videoDriverList{&videoDriverLayout, Size{0, 0}};
|
||||
Button videoDriverAssign{&videoDriverLayout, Size{0, 0}};
|
||||
Label videoDriverActive{&videoDriverLayout, Size{0, 0}};
|
||||
HorizontalLayout videoPropertyLayout{this, Size{~0, 0}};
|
||||
Label videoMonitorLabel{&videoPropertyLayout, Size{0, 0}};
|
||||
@@ -378,7 +377,6 @@ struct DriverSettings : VerticalLayout {
|
||||
HorizontalLayout audioDriverLayout{this, Size{~0, 0}};
|
||||
Label audioDriverLabel{&audioDriverLayout, Size{0, 0}};
|
||||
ComboButton audioDriverList{&audioDriverLayout, Size{0, 0}};
|
||||
Button audioDriverAssign{&audioDriverLayout, Size{0, 0}};
|
||||
Label audioDriverActive{&audioDriverLayout, Size{0, 0}};
|
||||
HorizontalLayout audioDeviceLayout{this, Size{~0, 0}};
|
||||
Label audioDeviceLabel{&audioDeviceLayout, Size{0, 0}};
|
||||
@@ -397,7 +395,6 @@ struct DriverSettings : VerticalLayout {
|
||||
HorizontalLayout inputDriverLayout{this, Size{~0, 0}};
|
||||
Label inputDriverLabel{&inputDriverLayout, Size{0, 0}};
|
||||
ComboButton inputDriverList{&inputDriverLayout, Size{0, 0}};
|
||||
Button inputDriverAssign{&inputDriverLayout, Size{0, 0}};
|
||||
Label inputDriverActive{&inputDriverLayout, Size{0, 0}};
|
||||
HorizontalLayout inputDefocusLayout{this, Size{~0, 0}};
|
||||
Label inputDefocusLabel{&inputDefocusLayout, Size{0, 0}};
|
||||
@@ -405,8 +402,6 @@ struct DriverSettings : VerticalLayout {
|
||||
RadioLabel inputDefocusBlock{&inputDefocusLayout, Size{0, 0}};
|
||||
RadioLabel inputDefocusAllow{&inputDefocusLayout, Size{0, 0}};
|
||||
Group inputDefocusGroup{&inputDefocusPause, &inputDefocusBlock, &inputDefocusAllow};
|
||||
//
|
||||
Label driverApplyHint{this, Size{0, 35}};
|
||||
};
|
||||
|
||||
struct DebugSettings : VerticalLayout {
|
||||
@@ -439,7 +434,6 @@ struct HomePanel : VerticalLayout {
|
||||
};
|
||||
|
||||
struct SettingsWindow : Window {
|
||||
SettingsWindow();
|
||||
auto show(const string& panel) -> void;
|
||||
auto eventChange() -> void;
|
||||
|
||||
@@ -457,6 +451,11 @@ struct SettingsWindow : Window {
|
||||
DriverSettings driverSettings;
|
||||
DebugSettings debugSettings;
|
||||
HomePanel homePanel;
|
||||
|
||||
bool initialized = false;
|
||||
|
||||
private:
|
||||
auto initialize() -> void;
|
||||
};
|
||||
|
||||
extern Settings settings;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
cocoaMenu = [[NSMenu alloc] initWithTitle:@""];
|
||||
[self setSubmenu:cocoaMenu];
|
||||
[cocoaMenu setAutoenablesItems:NO];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ target_sources(
|
||||
medium/medium.hpp
|
||||
medium/mega-32x.cpp
|
||||
medium/mega-cd.cpp
|
||||
medium/mega-ld.cpp
|
||||
medium/mega-drive.cpp
|
||||
medium/msx.cpp
|
||||
medium/msx2.cpp
|
||||
@@ -89,6 +90,7 @@ target_sources(
|
||||
system/mega-32x.cpp
|
||||
system/mega-cd-32x.cpp
|
||||
system/mega-cd.cpp
|
||||
system/mega-ld.cpp
|
||||
system/mega-drive.cpp
|
||||
system/msx.cpp
|
||||
system/msx2.cpp
|
||||
|
||||
Loaded 100 of 132 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user