Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
128 kB · 2747 lines
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748#include "pch.h"#include "QuickJsEngine.hpp"#include "PhotoDiscService.hpp"#include "OskiewarLivePublisher.hpp"#include "../runtime/include/ac/image_effects.hpp"#include "render/ac_surface.hpp"
using Microsoft::WRL::ComPtr;using namespace Platform;using namespace Windows::ApplicationModel::Core;using namespace Windows::ApplicationModel::Activation;using namespace Windows::Gaming::Input;using namespace Windows::Networking::Connectivity;using namespace Windows::Networking::Sockets;using namespace Windows::Data::Json;using namespace Windows::Devices::Enumeration;using namespace Windows::Devices::Midi;using namespace Windows::Graphics::Imaging;using namespace Windows::Graphics::Display;using namespace Windows::Graphics::Display::Core;using namespace Windows::Security::ExchangeActiveSyncProvisioning;using namespace Windows::Storage;using namespace Windows::Storage::Streams;using namespace Windows::System;using namespace Windows::System::Profile;using namespace Windows::UI::Core;using namespace Windows::Foundation;using namespace Windows::Web::Http;using namespace concurrency;
namespace NativeBios {
using namespace ac::xbox;
using PaintingImage = PhotoDiscImage;
struct GpuTriangleVertex { float x, y, z; float r, g, b, a;};
struct GpuSpriteVertex { float x, y, z; float u, v; float r, g, b, a;};
struct PostConstants { float texelX, texelY, timeSeconds, stencilPass;};
static constexpr char kSmokePiece[] = R"JS(let color=[12,8,24];function boot(){color=[12,8,24]}function sim(){}function paint(){wipe(color[0],color[1],color[2])}function act(button){ if(button==='A')color=[20,180,70]; else if(button==='B')color=[220,45,35]; else if(button==='X')color=[35,100,230]; else if(button==='Y')color=[235,190,20]; synth(button==='Y'?990:660,.04)}function leave(){})JS";
static void LogTelemetry(const std::string& line) { const auto folder = ApplicationData::Current->LocalFolder->Path; std::wstring path(folder->Data()); path += L"\\ac-native-bios.log"; FILE* file = nullptr; if (_wfopen_s(&file, path.c_str(), L"a") != 0 || !file) return; std::fwrite(line.data(), 1, line.size(), file); std::fwrite("\n", 1, 1, file); std::fclose(file);}
static std::string Utf8(String^ value) { if (!value || value->IsEmpty()) return {}; const int size = WideCharToMultiByte(CP_UTF8, 0, value->Data(), value->Length(), nullptr, 0, nullptr, nullptr); std::string result(static_cast<std::size_t>(size), '\0'); WideCharToMultiByte(CP_UTF8, 0, value->Data(), value->Length(), result.data(), size, nullptr, nullptr); return result;}
static std::wstring Wide(const std::string& value) { if (value.empty()) return {}; const int size = MultiByteToWideChar(CP_UTF8, 0, value.data(), static_cast<int>(value.size()), nullptr, 0); std::wstring result(static_cast<std::size_t>(size), L'\0'); MultiByteToWideChar(CP_UTF8, 0, value.data(), static_cast<int>(value.size()), result.data(), size); return result;}
static std::int64_t SystemUnixMs() { FILETIME fileTime{}; GetSystemTimeAsFileTime(&fileTime); ULARGE_INTEGER ticks{}; ticks.LowPart = fileTime.dwLowDateTime; ticks.HighPart = fileTime.dwHighDateTime; return static_cast<std::int64_t>( (ticks.QuadPart - 116444736000000000ULL) / 10000ULL);}
static void OscString(std::vector<uint8_t>& packet, const std::string& value) { packet.insert(packet.end(), value.begin(), value.end()); packet.push_back(0); while (packet.size() % 4) packet.push_back(0);}
static void OscInt(std::vector<uint8_t>& packet, std::int32_t value) { const auto bits = static_cast<std::uint32_t>(value); packet.push_back(static_cast<uint8_t>(bits >> 24)); packet.push_back(static_cast<uint8_t>(bits >> 16)); packet.push_back(static_cast<uint8_t>(bits >> 8)); packet.push_back(static_cast<uint8_t>(bits));}
static void OscFloat(std::vector<uint8_t>& packet, float value) { std::uint32_t bits = 0; static_assert(sizeof(bits) == sizeof(value), "OSC float size"); std::memcpy(&bits, &value, sizeof(bits)); OscInt(packet, static_cast<std::int32_t>(bits));}
static std::vector<uint8_t> GameSignalPacket(std::string_view event, int player, float value, float value2, std::uint32_t sequence) { std::vector<uint8_t> packet; packet.reserve(80); OscString(packet, "/oskiewar/" + std::string(event)); OscString(packet, ",iffi"); OscInt(packet, player); OscFloat(packet, value); OscFloat(packet, value2); OscInt(packet, static_cast<std::int32_t>(sequence)); return packet;}
// Howard Hinnant's civil-date transform, used here to parse the fixed ISO-8601// response from /api/clock without depending on locale-sensitive date parsing.static std::int64_t ParseIsoUnixMs(const std::string& value) { int year = 0, month = 0, day = 0, hour = 0, minute = 0, second = 0, millis = 0; if (sscanf_s(value.c_str(), "%d-%d-%dT%d:%d:%d.%dZ", &year, &month, &day, &hour, &minute, &second, &millis) != 7) throw std::runtime_error("invalid clock ISO"); year -= month <= 2; const auto era = (year >= 0 ? year : year - 399) / 400; const auto yoe = static_cast<unsigned>(year - era * 400); const auto shiftedMonth = static_cast<unsigned>(month + (month > 2 ? -3 : 9)); const auto doy = (153 * shiftedMonth + 2) / 5 + static_cast<unsigned>(day) - 1; const auto doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; const auto days = static_cast<std::int64_t>(era) * 146097 + doe - 719468; return (((days * 24 + hour) * 60 + minute) * 60 + second) * 1000 + millis;}
static std::vector<uint8_t> ReadPackageBytes(const wchar_t* name) { const auto folder = Windows::ApplicationModel::Package::Current->InstalledLocation->Path; std::wstring path(folder->Data()); path += L"\\"; path += name; FILE* file = nullptr; if (_wfopen_s(&file, path.c_str(), L"rb") != 0 || !file) return {}; std::fseek(file, 0, SEEK_END); const long length = std::ftell(file); std::rewind(file); if (length <= 0) { std::fclose(file); return {}; } std::vector<uint8_t> result(static_cast<std::size_t>(length)); const auto read = std::fread(result.data(), 1, result.size(), file); std::fclose(file); result.resize(read); return result;}
static std::array<unsigned char, 7> BlockGlyph(char value) { if (value >= 'a' && value <= 'z') value -= 'a' - 'A'; switch (value) { case 'A': return {14,17,17,31,17,17,17}; case 'B': return {30,17,17,30,17,17,30}; case 'C': return {14,17,16,16,16,17,14}; case 'D': return {30,17,17,17,17,17,30}; case 'E': return {31,16,16,30,16,16,31}; case 'F': return {31,16,16,30,16,16,16}; case 'G': return {14,17,16,23,17,17,15}; case 'H': return {17,17,17,31,17,17,17}; case 'I': return {31,4,4,4,4,4,31}; case 'J': return {7,2,2,2,18,18,12}; case 'K': return {17,18,20,24,20,18,17}; case 'L': return {16,16,16,16,16,16,31}; case 'M': return {17,27,21,21,17,17,17}; case 'N': return {17,25,21,19,17,17,17}; case 'O': return {14,17,17,17,17,17,14}; case 'P': return {30,17,17,30,16,16,16}; case 'Q': return {14,17,17,17,21,18,13}; case 'R': return {30,17,17,30,20,18,17}; case 'S': return {15,16,16,14,1,1,30}; case 'T': return {31,4,4,4,4,4,4}; case 'U': return {17,17,17,17,17,17,14}; case 'V': return {17,17,17,17,17,10,4}; case 'W': return {17,17,17,21,21,21,10}; case 'X': return {17,17,10,4,10,17,17}; case 'Y': return {17,17,10,4,4,4,4}; case 'Z': return {31,1,2,4,8,16,31}; case '0': return {14,17,19,21,25,17,14}; case '1': return {4,12,4,4,4,4,14}; case '2': return {14,17,1,2,4,8,31}; case '3': return {30,1,1,14,1,1,30}; case '4': return {2,6,10,18,31,2,2}; case '5': return {31,16,16,30,1,1,30}; case '6': return {14,16,16,30,17,17,14}; case '7': return {31,1,2,4,8,8,8}; case '8': return {14,17,17,14,17,17,14}; case '9': return {14,17,17,15,1,1,14}; case '.': return {0,0,0,0,0,12,12}; case ':': return {0,4,4,0,4,4,0}; case '-': return {0,0,0,31,0,0,0}; case '/': return {1,2,2,4,8,8,16}; case '!': return {4,4,4,4,4,0,4}; case ' ': return {0,0,0,0,0,0,0}; default: return {14,17,1,2,4,0,4}; }}
static wchar_t Mdl2Glyph(const std::string& name) { static const std::unordered_map<std::string, wchar_t> glyphs = { {"Wifi", 0xE701}, {"GameConsole", 0xE967}, {"XboxOneConsole", 0xE990}, {"ButtonA", 0xF093}, {"ButtonB", 0xF094}, {"ButtonY", 0xF095}, {"ButtonX", 0xF096}, {"LeftStick", 0xF108}, {"RightStick", 0xF109}, {"TriggerLeft", 0xF10A}, {"TriggerRight", 0xF10B}, {"BumperLeft", 0xF10C}, {"BumperRight", 0xF10D}, {"Dpad", 0xF10E}, {"ButtonView", 0xEECA}, }; const auto found = glyphs.find(name); return found == glyphs.end() ? L'?' : found->second;}
static std::string GamingDeviceName() { GAMING_DEVICE_MODEL_INFORMATION information{}; if (FAILED(GetGamingDeviceModelInformation(&information))) return {}; switch (static_cast<unsigned>(information.deviceId)) { case 0x768BAE26: return "Xbox One"; case 0x2A7361D9: return "Xbox One S"; case 0x5AD617C7: return "Xbox One X"; case 0x10F7CDE3: return "Xbox One X Dev Kit"; case 0x1D27FABB: return "Xbox Series S"; case 0x2F7A3DFF: return "Xbox Series X"; case 0xDE8A5661: return "Xbox Series X Dev Kit"; default: return {}; }}
class HostGraphics final : public Graphics { public: std::function<void(Color)> on_wipe; std::function<void(const ac::xbox::Rect&)> on_box; std::function<void(const ac::xbox::Line&)> on_line; std::function<void(const ac::xbox::Triangle&)> on_triangle; std::function<void(const ac::xbox::TexturedTriangle&)> on_textured_triangle; std::function<void(const ac::xbox::Sprite&)> on_sprite; std::function<void(const ac::xbox::Text&)> on_write; std::function<void(const ac::xbox::SystemText&)> on_system_write; std::function<void(const ac::xbox::SystemGlyph&)> on_system_glyph; std::function<void(const ac::xbox::ImageDraw&)> on_image; std::function<void(unsigned)> on_blur; void wipe(Color color) override { if (on_wipe) on_wipe(color); } void box(const ac::xbox::Rect& rect) override { if (on_box) on_box(rect); } void line(const ac::xbox::Line& line) override { if (on_line) on_line(line); } void triangle(const ac::xbox::Triangle& triangle) override { if (on_triangle) on_triangle(triangle); } void textured_triangle(const ac::xbox::TexturedTriangle& triangle) override { if (on_textured_triangle) on_textured_triangle(triangle); } void sprite(const ac::xbox::Sprite& sprite) override { if (on_sprite) on_sprite(sprite); } void write(const ac::xbox::Text& text) override { if (on_write) on_write(text); } void system_write(const ac::xbox::SystemText& text) override { if (on_system_write) on_system_write(text); } void system_glyph(const ac::xbox::SystemGlyph& glyph) override { if (on_system_glyph) on_system_glyph(glyph); } void image(const ac::xbox::ImageDraw& draw) override { if (on_image) on_image(draw); } void blur(unsigned radius) override { if (on_blur) on_blur(radius); }};class HostSound final : public Sound { public: std::function<void(const SynthVoice&)> on_synth; std::function<void()> on_stop; std::function<void(float, float)> on_oscillator; std::function<void()> on_oscillator_stop; std::function<void(std::string_view, float, float)> on_drum; std::function<int()> get_rate; void synth(const SynthVoice& voice) override { if (on_synth) on_synth(voice); } void stop_all() override { if (on_stop) on_stop(); } int sample_rate() const override { return get_rate ? get_rate() : 0; } void oscillator(float frequency, float volume) override { if (on_oscillator) on_oscillator(frequency, volume); } void oscillator_stop() override { if (on_oscillator_stop) on_oscillator_stop(); } void drum(std::string_view name, float velocity, float pan) override { if (on_drum) on_drum(name, velocity, pan); }};
static void Check(HRESULT hr) { if (FAILED(hr)) throw Exception::CreateException(hr);}
// The WinRT half of the render seam. A native class cannot hold a ref type, so// the CoreWindow arrives as the IUnknown the swap-chain call wants anyway.class CoreWindowSurface final : public ac::xbox::render::SurfaceHost { public: explicit CoreWindowSurface(IUnknown* window) : window_(window) {}
void preferred_size(unsigned& width, unsigned& height) override { try { const auto hdmi = HdmiDisplayInformation::GetForCurrentView(); const auto mode = hdmi ? hdmi->GetCurrentDisplayMode() : nullptr; if (mode) { width = (std::min)(3840u, mode->ResolutionWidthInRawPixels); height = (std::min)(2160u, mode->ResolutionHeightInRawPixels); } const auto display = DisplayInformation::GetForCurrentView(); const auto rawWidth = display->ScreenWidthInRawPixels; const auto rawHeight = display->ScreenHeightInRawPixels; if (width <= 1920 && rawWidth >= 1920 && rawHeight >= 1080) { width = (std::min)(3840u, rawWidth); height = (std::min)(2160u, rawHeight); } } catch (...) { // Early Xbox activation can withhold display information. The caller's // seeded values stand rather than DXGI's 8x8 placeholder. } }
HRESULT create_swap_chain(IDXGIFactory2* factory, ID3D11Device* device, const DXGI_SWAP_CHAIN_DESC1& desc, IDXGISwapChain1** out) override { return factory->CreateSwapChainForCoreWindow(device, window_, &desc, nullptr, out); }
private: IUnknown* window_;};
ref class App sealed : public IFrameworkView {public: virtual void Initialize(CoreApplicationView^ view) { view->Activated += ref new TypedEventHandler<CoreApplicationView^, IActivatedEventArgs^>( this, &App::OnActivated); }
virtual void SetWindow(CoreWindow^ window) { m_window = window; window->Closed += ref new TypedEventHandler<CoreWindow^, CoreWindowEventArgs^>( this, &App::OnClosed); SystemNavigationManager::GetForCurrentView()->BackRequested += ref new EventHandler<BackRequestedEventArgs^>(this, &App::OnBackRequested); CreateGraphics(); CreateAudio(48000); m_graphics = std::make_unique<HostGraphics>(); m_sound = std::make_unique<HostSound>(); m_graphics->on_wipe = [this](Color color) { m_frameColor = {color.r / 255.f, color.g / 255.f, color.b / 255.f, color.a / 255.f}; }; m_graphics->on_box = [this](const ac::xbox::Rect& rect) { m_frameRects.push_back(rect); }; m_graphics->on_line = [this](const ac::xbox::Line& line) { m_frameLines.push_back(line); }; m_graphics->on_triangle = [this](const ac::xbox::Triangle& triangle) { if (m_frameTriangles.size() < kMaxTriangles) m_frameTriangles.push_back(triangle); else ++m_frameTrianglesDropped; }; m_graphics->on_textured_triangle = [this](const ac::xbox::TexturedTriangle& triangle) { if (m_frameTexturedTriangles.size() < kMaxTexturedTriangles) m_frameTexturedTriangles.push_back(triangle); else ++m_frameTexturedTrianglesDropped; }; m_graphics->on_sprite = [this](const ac::xbox::Sprite& sprite) { if (m_frameSprites.size() < kMaxSprites) m_frameSprites.push_back(sprite); else ++m_frameSpritesDropped; }; m_graphics->on_write = [this](const ac::xbox::Text& text) { m_frameTexts.push_back(text); }; m_graphics->on_system_write = [this](const ac::xbox::SystemText& text) { if (m_frameSystemTexts.size() + m_frameSystemGlyphs.size() < kMaxSystemDraws) m_frameSystemTexts.push_back(text); else ++m_frameSystemDrawsDropped; }; m_graphics->on_system_glyph = [this](const ac::xbox::SystemGlyph& glyph) { if (m_frameSystemTexts.size() + m_frameSystemGlyphs.size() < kMaxSystemDraws) m_frameSystemGlyphs.push_back(glyph); else ++m_frameSystemDrawsDropped; }; m_graphics->on_image = [this](const ac::xbox::ImageDraw& draw) { m_frameImages.push_back(draw); RequestFrameImage(draw.source); }; m_graphics->on_blur = [this](unsigned radius) { m_frameBlurRadius = (std::max)(m_frameBlurRadius, (std::min)(16u, radius)); }; m_sound->on_synth = [this](const SynthVoice& voice) { PlaySynth(voice); }; m_sound->on_stop = [this]() { if (m_voice) { m_voice->Stop(0); m_voice->FlushSourceBuffers(); } }; m_sound->on_oscillator = [this](float frequency, float volume) { SetOscillator(frequency, volume); }; m_sound->on_oscillator_stop = [this]() { StopOscillator(); }; m_sound->on_drum = [this](std::string_view name, float velocity, float pan) { PlayDrum(name, velocity, pan); }; m_sound->get_rate = [this]() { return static_cast<int>(m_sampleRate); }; m_api = std::make_unique<Api>(Api{{1920, 1080, 1}, {}, {}, {}, *m_graphics, *m_sound, {}}); m_api->system.render_width = m_frameWidth; m_api->system.render_height = m_frameHeight; m_api->system.version = "1.0.0.41"; m_api->telemetry = [this](std::string_view line) { std::string safe(line); for (auto& character : safe) if (character == '\n' || character == '\r') character = ' '; constexpr std::string_view prefix = "JS CLIENT_ERROR "; const bool clientError = safe.rfind(prefix, 0) == 0; if (safe.size() > (clientError ? 8192u : 1024u)) safe.resize(clientError ? 8192u : 1024u); LogTelemetry("AC_NATIVE_" + safe); if (clientError) QueueClientErrorUpload(safe.substr(prefix.size())); }; m_api->client_error_report_status = [this]() { std::lock_guard<std::mutex> lock(m_clientErrorMutex); return m_clientErrorStatus; }; m_api->game_signal = [this](std::string_view event, int player, float value, float value2) { constexpr std::size_t copies = 3; LARGE_INTEGER queuedAt{}; QueryPerformanceCounter(&queuedAt); { std::lock_guard<std::mutex> lock(m_gameSignalMutex); if (m_gameSignalQueue.size() + copies > 192) { ++m_gameSignalsDropped; return; } const auto packet = GameSignalPacket(event, player, value, value2, ++m_gameSignalSequence); for (std::size_t copy = 0; copy < copies; ++copy) { m_gameSignalQueue.push_back({packet, copy == 0, queuedAt.QuadPart}); } } // gameSignal is called after the render-loop flush point. Start the // first datagram now instead of adding a full video frame of latency. FlushGameSignals(); }; m_api->replay_save = [this](std::string_view payload) { std::lock_guard<std::mutex> lock(m_replayMutex); if (m_replayQueue.size() >= 4) { ++m_replaysDropped; return; } m_replayQueue.emplace_back(payload); LogTelemetry("AC_NATIVE_REPLAY queued=1 bytes=" + std::to_string(payload.size())); }; m_oskiewarLive = std::make_unique<OskiewarLivePublisher>( [](const std::string& line) { LogTelemetry(line); }); m_api->live_publish = [this](std::string_view matchId, std::string_view payload) { m_oskiewarLive->publish(matchId, payload); }; m_photoDisc = std::make_unique<PhotoDiscService>(*m_api, [this](std::shared_ptr<const PhotoDiscImage> image) { std::lock_guard<std::mutex> lock(m_imageMutex); m_paintingImages["disc-photo"] = std::move(image); }, [](const std::string& line) { LogTelemetry(line); }); InitializeMidi(); InitializeNetworkMidi(); InitializeGameSignals(); RefreshCapabilities(true); RefreshAcData(true); RefreshNetworkClock(true); m_engine = std::make_unique<QuickJsEngine>(); m_supervisor = std::make_unique<PieceSupervisor>(*m_engine); // The game ships inside the signed package, next to the shaders and the // fonts. kSmokePiece is the fallback for a package built without it, not // the thing players are meant to get. const auto packaged = ReadPackageBytes(L"oskiewar.js"); const bool haveGame = !packaged.empty(); const std::string source = haveGame ? std::string(packaged.begin(), packaged.end()) : std::string(kSmokePiece); const char* slug = haveGame ? "hello" : "smoke";
std::string error; if (!m_supervisor->stage({slug, "packaged-v1", source, "packaged"}, *m_api, error) || !m_supervisor->activate(*m_api)) { OutputDebugStringA(("AC_NATIVE_BIOS_BOOT_ERROR " + error + "\n").c_str()); QueueClientErrorUpload("native boot: " + error); } else { const std::string ready = std::string("AC_NATIVE_BIOS_READY engine=quickjs-ng piece=") + slug + " bytes=" + std::to_string(source.size()); OutputDebugStringA((ready + "\n").c_str()); LogTelemetry(ready); } m_photoDisc->scan(); }
virtual void Load(String^) {} virtual void Uninitialize() { if (m_api) m_api->live_publish = {}; if (m_oskiewarLive) m_oskiewarLive->shutdown(); DestroyClientErrorUploads(); DestroyReplayUploads(); DestroyGameSignals(); DestroyNetworkMidi(); DestroyMidi(); DestroyAudio(); }
virtual void Run() { Render({0.025f, 0.02f, 0.04f, 1.0f}); LARGE_INTEGER profileFrequency{}; QueryPerformanceFrequency(&profileFrequency); double profileHostJsMs = 0, profileRenderCpuMs = 0, profilePresentMs = 0; unsigned profileFrames = 0; while (!m_closed) { LARGE_INTEGER profileStart{}, profileAfterJs{}; QueryPerformanceCounter(&profileStart); m_window->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent); RefreshCapabilities(false); RefreshAcData(false); RefreshNetworkClock(false); PollController(); PollMidi();#if AC_DEV_LIVE_PIECE PollLivePiece();#endif FlushGameSignals(); FlushReplayUploads(); FlushClientErrorUploads(); RefreshClock(); RefreshAudioPerformance(); m_frameRects.clear(); m_frameLines.clear(); m_frameTriangles.clear(); m_frameTexturedTriangles.clear(); m_frameSprites.clear(); m_frameTexts.clear(); m_frameSystemTexts.clear(); m_frameSystemGlyphs.clear(); m_frameImages.clear(); m_frameBlurRadius = 0; m_frameSystemDrawsDropped = 0; m_frameTrianglesDropped = 0; m_frameTexturedTrianglesDropped = 0; m_frameSpritesDropped = 0; if (m_supervisor && m_supervisor->active()) { try { m_supervisor->active()->sim(*m_api); ++m_api->sim_count; m_supervisor->active()->paint(*m_api); ++m_api->paint_count; } catch (const std::exception& error) { OutputDebugStringA((std::string("AC_NATIVE_BIOS_JS_ERROR ") + error.what() + "\n").c_str()); LogTelemetry(std::string("AC_NATIVE_BIOS_JS_ERROR ") + error.what()); QueueClientErrorUpload(std::string("native lifecycle: ") + error.what()); m_supervisor->rollback(*m_api); } } QueryPerformanceCounter(&profileAfterJs); Render(m_frameColor); profileHostJsMs += (profileAfterJs.QuadPart - profileStart.QuadPart) * 1000.0 / profileFrequency.QuadPart; profileRenderCpuMs += m_lastRenderCpuMs; profilePresentMs += m_lastPresentMs; if (++profileFrames >= 120) { const auto frameMs = (profileHostJsMs + profileRenderCpuMs + profilePresentMs) / profileFrames; m_api->system.frame_ms = frameMs; m_api->system.refresh_hz = frameMs > 0 ? 1000.0 / frameMs : 0; m_api->system.render_cpu_ms = profileRenderCpuMs / profileFrames; m_api->system.present_ms = profilePresentMs / profileFrames; LogTelemetry("AC_NATIVE_PROFILE hostJsMs=" + std::to_string(profileHostJsMs / profileFrames) + " renderCpuMs=" + std::to_string(profileRenderCpuMs / profileFrames) + " presentMs=" + std::to_string(profilePresentMs / profileFrames) + " totalMs=" + std::to_string(frameMs) + " measuredHz=" + std::to_string(m_api->system.refresh_hz) + " surface=" + std::to_string(m_api->screen.width) + "x" + std::to_string(m_api->screen.height) + " aa=" + std::to_string(m_api->system.antialiasing_samples) + "x mode=" + m_api->system.antialiasing_mode); profileFrames = 0; profileHostJsMs = 0; profileRenderCpuMs = 0; profilePresentMs = 0; } SwitchToThread(); } }
private: void OnActivated(CoreApplicationView^, IActivatedEventArgs^) { m_window->Activate(); }
void OnClosed(CoreWindow^, CoreWindowEventArgs^) { m_closed = true; }
void OnBackRequested(Object^, BackRequestedEventArgs^ args) { // Xbox promotes the B button to platform Back. Keep the app alive so the // same physical press remains available to the piece through Gamepad. args->Handled = true; }
void CreateGraphics() { if (!m_surface) m_surface = std::make_unique<CoreWindowSurface>(reinterpret_cast<IUnknown*>(m_window)); UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;#if defined(_DEBUG) flags |= D3D11_CREATE_DEVICE_DEBUG;#endif const D3D_FEATURE_LEVEL levels[] = { D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, D3D_FEATURE_LEVEL_10_0, }; D3D_FEATURE_LEVEL actual{}; ComPtr<ID3D11Device> baseDevice; ComPtr<ID3D11DeviceContext> baseContext; Check(D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, flags, levels, ARRAYSIZE(levels), D3D11_SDK_VERSION, &baseDevice, &actual, &baseContext)); Check(baseDevice.As(&m_device)); Check(baseContext.As(&m_context));
ComPtr<IDXGIDevice1> dxgiDevice; ComPtr<IDXGIAdapter> adapter; ComPtr<IDXGIFactory2> factory; Check(m_device.As(&dxgiDevice)); const auto latencyResult = dxgiDevice->SetMaximumFrameLatency(1); LogTelemetry("AC_NATIVE_FRAME_LATENCY max=1 status=" + std::to_string(static_cast<long>(latencyResult))); Check(dxgiDevice->GetAdapter(&adapter)); Check(adapter->GetParent(IID_PPV_ARGS(&factory)));
DXGI_SWAP_CHAIN_DESC1 desc{}; // Xbox may choose its 8x8 placeholder surface when width/height are left at // zero for a CoreWindow swap chain. Solid clears still stretch fullscreen, // hiding the mistake while every useful drawing coordinate gets clipped. unsigned requestedWidth = 1920; unsigned requestedHeight = 1080; m_surface->preferred_size(requestedWidth, requestedHeight); desc.Width = requestedWidth; desc.Height = requestedHeight; desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; desc.SampleDesc.Count = 1; desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; desc.BufferCount = 2; desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; desc.Scaling = DXGI_SCALING_STRETCH; desc.AlphaMode = DXGI_ALPHA_MODE_IGNORE; Check(m_surface->create_swap_chain(factory.Get(), m_device.Get(), desc, &m_swapChain)); Check(m_swapChain->GetBuffer(0, IID_PPV_ARGS(&m_backBuffer))); D3D11_TEXTURE2D_DESC backBufferDesc{}; m_backBuffer->GetDesc(&backBufferDesc); m_frameWidth = backBufferDesc.Width; m_frameHeight = backBufferDesc.Height; LogTelemetry("AC_NATIVE_SURFACE requested=" + std::to_string(requestedWidth) + "x" + std::to_string(requestedHeight) + " actual=" + std::to_string(m_frameWidth) + "x" + std::to_string(m_frameHeight)); Check(m_device->CreateRenderTargetView(m_backBuffer.Get(), nullptr, &m_target));
D3D11_TEXTURE2D_DESC sceneDesc = backBufferDesc; sceneDesc.Usage = D3D11_USAGE_DEFAULT; sceneDesc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE; sceneDesc.CPUAccessFlags = 0; sceneDesc.MiscFlags = 0; Check(m_device->CreateTexture2D(&sceneDesc, nullptr, &m_sceneTexture)); Check(m_device->CreateRenderTargetView(m_sceneTexture.Get(), nullptr, &m_sceneTarget)); Check(m_device->CreateShaderResourceView(m_sceneTexture.Get(), nullptr, &m_sceneView));
Check(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, IID_PPV_ARGS(&m_d2dFactory))); ComPtr<IDXGIDevice> d2dDxgiDevice; Check(m_device.As(&d2dDxgiDevice)); Check(m_d2dFactory->CreateDevice(d2dDxgiDevice.Get(), &m_d2dDevice)); Check(m_d2dDevice->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE, &m_d2dContext)); ComPtr<IDXGISurface> surface; Check(m_sceneTexture.As(&surface)); const auto bitmapProperties = D2D1::BitmapProperties1( D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW, D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE)); Check(m_d2dContext->CreateBitmapFromDxgiSurface(surface.Get(), &bitmapProperties, &m_d2dTarget)); m_d2dContext->SetTarget(m_d2dTarget.Get()); Check(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), reinterpret_cast<IUnknown**>(m_dwriteFactory.GetAddressOf()))); const auto package = Windows::ApplicationModel::Package::Current->InstalledLocation->Path; std::wstring ywftPath(package->Data()); ywftPath += L"\\Assets\\ywft-processing-regular.ttf"; BOOL supported = FALSE; DWRITE_FONT_FILE_TYPE fileType = DWRITE_FONT_FILE_TYPE_UNKNOWN; DWRITE_FONT_FACE_TYPE faceType = DWRITE_FONT_FACE_TYPE_UNKNOWN; UINT32 faceCount = 0; if (SUCCEEDED(m_dwriteFactory->CreateFontFileReference(ywftPath.c_str(), nullptr, &m_ywftFontFile)) && SUCCEEDED(m_ywftFontFile->Analyze(&supported, &fileType, &faceType, &faceCount)) && supported && faceCount > 0) { IDWriteFontFile* files[] = {m_ywftFontFile.Get()}; const auto fontResult = m_dwriteFactory->CreateFontFace(faceType, 1, files, 0, DWRITE_FONT_SIMULATIONS_NONE, &m_ywftFontFace); LogTelemetry("AC_NATIVE_YWFT status=" + std::to_string(static_cast<long>(fontResult))); } else LogTelemetry("AC_NATIVE_YWFT status=unavailable"); std::wstring comicPath(package->Data()); comicPath += L"\\Assets\\ComicRelief-Regular.ttf"; supported = FALSE; fileType = DWRITE_FONT_FILE_TYPE_UNKNOWN; faceType = DWRITE_FONT_FACE_TYPE_UNKNOWN; faceCount = 0; if (SUCCEEDED(m_dwriteFactory->CreateFontFileReference(comicPath.c_str(), nullptr, &m_comicFontFile)) && SUCCEEDED(m_comicFontFile->Analyze(&supported, &fileType, &faceType, &faceCount)) && supported && faceCount > 0) { IDWriteFontFile* files[] = {m_comicFontFile.Get()}; const auto fontResult = m_dwriteFactory->CreateFontFace(faceType, 1, files, 0, DWRITE_FONT_SIMULATIONS_NONE, &m_comicFontFace); LogTelemetry("AC_NATIVE_COMIC_RELIEF status=" + std::to_string(static_cast<long>(fontResult))); } else LogTelemetry("AC_NATIVE_COMIC_RELIEF status=unavailable"); Check(m_d2dContext->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::White), &m_textBrush)); CreateTrianglePipeline(); CreateSpritePipeline(); CreatePostPipeline(); }
void CreateTrianglePipeline() { const auto vertexBytes = ReadPackageBytes(L"TriangleVertexShader.cso"); const auto pixelBytes = ReadPackageBytes(L"TrianglePixelShader.cso"); if (vertexBytes.empty() || pixelBytes.empty()) { LogTelemetry("AC_NATIVE_GPU_TRIANGLES unavailable=shader-assets"); return; } Check(m_device->CreateVertexShader(vertexBytes.data(), vertexBytes.size(), nullptr, &m_triangleVertexShader)); Check(m_device->CreatePixelShader(pixelBytes.data(), pixelBytes.size(), nullptr, &m_trianglePixelShader)); const D3D11_INPUT_ELEMENT_DESC elements[] = { {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0}, {"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0}, }; Check(m_device->CreateInputLayout(elements, ARRAYSIZE(elements), vertexBytes.data(), vertexBytes.size(), &m_triangleInputLayout));
D3D11_BUFFER_DESC buffer{}; buffer.ByteWidth = static_cast<UINT>(kMaxTriangles * 3 * sizeof(GpuTriangleVertex)); buffer.Usage = D3D11_USAGE_DYNAMIC; buffer.BindFlags = D3D11_BIND_VERTEX_BUFFER; buffer.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; Check(m_device->CreateBuffer(&buffer, nullptr, &m_triangleVertexBuffer));
D3D11_TEXTURE2D_DESC depth{}; depth.Width = m_frameWidth; depth.Height = m_frameHeight; depth.MipLevels = 1; depth.ArraySize = 1; depth.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; depth.SampleDesc.Count = 1; depth.BindFlags = D3D11_BIND_DEPTH_STENCIL; ComPtr<ID3D11Texture2D> depthTexture; Check(m_device->CreateTexture2D(&depth, nullptr, &depthTexture)); Check(m_device->CreateDepthStencilView(depthTexture.Get(), nullptr, &m_triangleDepthView));
D3D11_DEPTH_STENCIL_DESC depthState{}; depthState.DepthEnable = TRUE; depthState.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL; depthState.DepthFunc = D3D11_COMPARISON_LESS_EQUAL; depthState.StencilEnable = TRUE; depthState.StencilReadMask = D3D11_DEFAULT_STENCIL_READ_MASK; depthState.StencilWriteMask = D3D11_DEFAULT_STENCIL_WRITE_MASK; depthState.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP; depthState.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_KEEP; depthState.FrontFace.StencilPassOp = D3D11_STENCIL_OP_REPLACE; depthState.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS; depthState.BackFace = depthState.FrontFace; Check(m_device->CreateDepthStencilState(&depthState, &m_triangleDepthState));
D3D11_RASTERIZER_DESC raster{}; raster.FillMode = D3D11_FILL_SOLID; raster.CullMode = D3D11_CULL_NONE; raster.DepthClipEnable = TRUE; Check(m_device->CreateRasterizerState(&raster, &m_triangleRasterState)); LogTelemetry("AC_NATIVE_GPU_TRIANGLES ready=1 max=" + std::to_string(kMaxTriangles) + " depth=d24s8 stencil=write"); }
bool DrawGpuTriangles() { if (m_frameTriangles.empty()) return true; if (!m_triangleVertexBuffer || !m_triangleVertexShader || !m_trianglePixelShader || !m_triangleDepthView) return false; D3D11_MAPPED_SUBRESOURCE mapped{}; if (FAILED(m_context->Map(m_triangleVertexBuffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped))) return false; auto* output = static_cast<GpuTriangleVertex*>(mapped.pData); std::size_t count = 0; const auto append = [this, &output, &count](float x, float y, float z, Color color) { output[count++] = { x / 960.0f - 1.0f, 1.0f - y / 540.0f, (std::max)(0.0f, (std::min)(1.0f, (z + 1.5f) / 3.0f)), color.r / 255.0f, color.g / 255.0f, color.b / 255.0f, 1.0f, }; }; for (const auto& triangle : m_frameTriangles) { append(triangle.x1, triangle.y1, triangle.z1, triangle.color); append(triangle.x2, triangle.y2, triangle.z2, triangle.color); append(triangle.x3, triangle.y3, triangle.z3, triangle.color); } m_context->Unmap(m_triangleVertexBuffer.Get(), 0);
const UINT stride = sizeof(GpuTriangleVertex), offset = 0; m_context->IASetInputLayout(m_triangleInputLayout.Get()); m_context->IASetVertexBuffers(0, 1, m_triangleVertexBuffer.GetAddressOf(), &stride, &offset); m_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); m_context->VSSetShader(m_triangleVertexShader.Get(), nullptr, 0); m_context->PSSetShader(m_trianglePixelShader.Get(), nullptr, 0); m_context->RSSetState(m_triangleRasterState.Get()); const D3D11_VIEWPORT viewport{0, 0, static_cast<float>(m_frameWidth), static_cast<float>(m_frameHeight), 0, 1}; m_context->RSSetViewports(1, &viewport); m_context->OMSetDepthStencilState(m_triangleDepthState.Get(), 1); m_context->OMSetRenderTargets(1, m_sceneTarget.GetAddressOf(), m_triangleDepthView.Get()); m_context->Draw(static_cast<UINT>(count), 0); return true; }
void CreateSpritePipeline() { const auto vertexBytes = ReadPackageBytes(L"SpriteVertexShader.cso"); const auto pixelBytes = ReadPackageBytes(L"SpritePixelShader.cso"); if (vertexBytes.empty() || pixelBytes.empty()) { LogTelemetry("AC_NATIVE_GPU_SPRITES unavailable=shader-assets"); return; } Check(m_device->CreateVertexShader(vertexBytes.data(), vertexBytes.size(), nullptr, &m_spriteVertexShader)); Check(m_device->CreatePixelShader(pixelBytes.data(), pixelBytes.size(), nullptr, &m_spritePixelShader)); const D3D11_INPUT_ELEMENT_DESC elements[] = { {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0}, {"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0}, {"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 20, D3D11_INPUT_PER_VERTEX_DATA, 0}, }; Check(m_device->CreateInputLayout(elements, ARRAYSIZE(elements), vertexBytes.data(), vertexBytes.size(), &m_spriteInputLayout)); D3D11_BUFFER_DESC buffer{}; buffer.ByteWidth = static_cast<UINT>((std::max)(kMaxSprites * 6, kMaxTexturedTriangles * 3) * sizeof(GpuSpriteVertex)); buffer.Usage = D3D11_USAGE_DYNAMIC; buffer.BindFlags = D3D11_BIND_VERTEX_BUFFER; buffer.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; Check(m_device->CreateBuffer(&buffer, nullptr, &m_spriteVertexBuffer));
std::array<uint32_t, 16 * 8> atlas{}; for (int y = 0; y < 8; ++y) for (int x = 0; x < 16; ++x) { const int localX = x % 8; const bool diamond = std::abs(localX - 3) + std::abs(y - 3) <= 3; const bool spark = localX == 3 || localX == 4 || y == 3 || y == 4 || ((localX == 2 || localX == 5) && (y == 2 || y == 5)); const bool visible = x < 8 ? diamond : spark; atlas[static_cast<std::size_t>(y) * 16 + x] = visible ? 0xffffffffu : 0u; } D3D11_TEXTURE2D_DESC texture{}; texture.Width = 16; texture.Height = 8; texture.MipLevels = 1; texture.ArraySize = 1; texture.Format = DXGI_FORMAT_R8G8B8A8_UNORM; texture.SampleDesc.Count = 1; texture.Usage = D3D11_USAGE_IMMUTABLE; texture.BindFlags = D3D11_BIND_SHADER_RESOURCE; D3D11_SUBRESOURCE_DATA pixels{}; pixels.pSysMem = atlas.data(); pixels.SysMemPitch = 16 * sizeof(uint32_t); ComPtr<ID3D11Texture2D> atlasTexture; Check(m_device->CreateTexture2D(&texture, &pixels, &atlasTexture)); Check(m_device->CreateShaderResourceView(atlasTexture.Get(), nullptr, &m_spriteAtlasView));
const auto jeffreyPixels = ReadPackageBytes(L"Assets\\JeffreyTexture.rgba"); const auto jeffreySide = static_cast<UINT>(std::sqrt(jeffreyPixels.size() / 4.0)); if (jeffreySide >= 256 && jeffreySide <= 2048 && static_cast<std::size_t>(jeffreySide) * jeffreySide * 4 == jeffreyPixels.size()) { texture.Width = jeffreySide; texture.Height = jeffreySide; pixels.pSysMem = jeffreyPixels.data(); pixels.SysMemPitch = jeffreySide * 4; ComPtr<ID3D11Texture2D> jeffreyTexture; Check(m_device->CreateTexture2D(&texture, &pixels, &jeffreyTexture)); Check(m_device->CreateShaderResourceView(jeffreyTexture.Get(), nullptr, &m_jeffreyTextureView)); m_jeffreyTextureSize = jeffreySide; }
D3D11_SAMPLER_DESC sampler{}; sampler.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT; sampler.AddressU = sampler.AddressV = sampler.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP; sampler.MaxLOD = D3D11_FLOAT32_MAX; Check(m_device->CreateSamplerState(&sampler, &m_pointSampler)); sampler.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; Check(m_device->CreateSamplerState(&sampler, &m_linearSampler)); LogTelemetry("AC_NATIVE_GPU_SPRITES ready=1 max=512 atlas=16x8 filter=point jeffreyTexture=" + (m_jeffreyTextureView ? std::to_string(m_jeffreyTextureSize) + "x" + std::to_string(m_jeffreyTextureSize) + " filter=linear" : "missing")); }
bool DrawGpuTexturedTriangles() { if (m_frameTexturedTriangles.empty()) return true; if (!m_spriteVertexBuffer || !m_spriteVertexShader || !m_spritePixelShader || !m_jeffreyTextureView || !m_linearSampler || !m_triangleDepthView) return false; D3D11_MAPPED_SUBRESOURCE mapped{}; if (FAILED(m_context->Map(m_spriteVertexBuffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped))) return false; auto* output = static_cast<GpuSpriteVertex*>(mapped.pData); std::size_t count = 0; const auto append = [&output, &count](float x, float y, float z, float u, float v, Color color) { output[count++] = {x / 960.f - 1.f, 1.f - y / 540.f, (std::max)(0.f, (std::min)(1.f, (z + 1.5f) / 3.f)), u, v, color.r / 255.f, color.g / 255.f, color.b / 255.f, color.a / 255.f}; }; for (const auto& triangle : m_frameTexturedTriangles) { append(triangle.x1, triangle.y1, triangle.z1, triangle.u1, triangle.v1, triangle.color); append(triangle.x2, triangle.y2, triangle.z2, triangle.u2, triangle.v2, triangle.color); append(triangle.x3, triangle.y3, triangle.z3, triangle.u3, triangle.v3, triangle.color); } m_context->Unmap(m_spriteVertexBuffer.Get(), 0); const UINT stride = sizeof(GpuSpriteVertex), offset = 0; m_context->IASetInputLayout(m_spriteInputLayout.Get()); m_context->IASetVertexBuffers(0, 1, m_spriteVertexBuffer.GetAddressOf(), &stride, &offset); m_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); m_context->VSSetShader(m_spriteVertexShader.Get(), nullptr, 0); m_context->PSSetShader(m_spritePixelShader.Get(), nullptr, 0); m_context->PSSetShaderResources(0, 1, m_jeffreyTextureView.GetAddressOf()); m_context->PSSetSamplers(0, 1, m_linearSampler.GetAddressOf()); m_context->RSSetState(m_triangleRasterState.Get()); m_context->OMSetDepthStencilState(m_triangleDepthState.Get(), 1); m_context->OMSetRenderTargets(1, m_sceneTarget.GetAddressOf(), m_triangleDepthView.Get()); m_context->Draw(static_cast<UINT>(count), 0); ID3D11ShaderResourceView* nullView = nullptr; m_context->PSSetShaderResources(0, 1, &nullView); return true; }
bool DrawGpuSprites() { if (m_frameSprites.empty()) return true; if (!m_spriteVertexBuffer || !m_spriteVertexShader || !m_spritePixelShader || !m_spriteAtlasView || !m_triangleDepthView) return false; D3D11_MAPPED_SUBRESOURCE mapped{}; if (FAILED(m_context->Map(m_spriteVertexBuffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped))) return false; auto* output = static_cast<GpuSpriteVertex*>(mapped.pData); std::size_t count = 0; const auto append = [&output, &count](float x, float y, float z, float u, float v, Color color) { output[count++] = {x / 960.f - 1.f, 1.f - y / 540.f, (std::max)(0.f, (std::min)(1.f, (z + 1.5f) / 3.f)), u, v, color.r / 255.f, color.g / 255.f, color.b / 255.f, color.a / 255.f}; }; for (const auto& sprite : m_frameSprites) { const float half = sprite.size * .5f; const float left = sprite.x - half, right = sprite.x + half; const float top = sprite.y - half, bottom = sprite.y + half; const float u0 = sprite.frame ? .5f : 0.f, u1 = u0 + .5f; append(left, top, sprite.z, u0, 0, sprite.color); append(right, top, sprite.z, u1, 0, sprite.color); append(right, bottom, sprite.z, u1, 1, sprite.color); append(left, top, sprite.z, u0, 0, sprite.color); append(right, bottom, sprite.z, u1, 1, sprite.color); append(left, bottom, sprite.z, u0, 1, sprite.color); } m_context->Unmap(m_spriteVertexBuffer.Get(), 0); const UINT stride = sizeof(GpuSpriteVertex), offset = 0; m_context->IASetInputLayout(m_spriteInputLayout.Get()); m_context->IASetVertexBuffers(0, 1, m_spriteVertexBuffer.GetAddressOf(), &stride, &offset); m_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); m_context->VSSetShader(m_spriteVertexShader.Get(), nullptr, 0); m_context->PSSetShader(m_spritePixelShader.Get(), nullptr, 0); m_context->PSSetShaderResources(0, 1, m_spriteAtlasView.GetAddressOf()); m_context->PSSetSamplers(0, 1, m_pointSampler.GetAddressOf()); m_context->RSSetState(m_triangleRasterState.Get()); m_context->OMSetDepthStencilState(m_triangleDepthState.Get(), 1); m_context->OMSetRenderTargets(1, m_sceneTarget.GetAddressOf(), m_triangleDepthView.Get()); m_context->Draw(static_cast<UINT>(count), 0); ID3D11ShaderResourceView* nullView = nullptr; m_context->PSSetShaderResources(0, 1, &nullView); return true; }
void CreatePostPipeline() { const auto vertexBytes = ReadPackageBytes(L"PostVertexShader.cso"); const auto pixelBytes = ReadPackageBytes(L"PostPixelShader.cso"); if (vertexBytes.empty() || pixelBytes.empty()) { LogTelemetry("AC_NATIVE_POST unavailable=shader-assets"); return; } Check(m_device->CreateVertexShader(vertexBytes.data(), vertexBytes.size(), nullptr, &m_postVertexShader)); Check(m_device->CreatePixelShader(pixelBytes.data(), pixelBytes.size(), nullptr, &m_postPixelShader)); D3D11_BUFFER_DESC constants{}; constants.ByteWidth = sizeof(PostConstants); constants.Usage = D3D11_USAGE_DYNAMIC; constants.BindFlags = D3D11_BIND_CONSTANT_BUFFER; constants.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; Check(m_device->CreateBuffer(&constants, nullptr, &m_postConstants)); D3D11_DEPTH_STENCIL_DESC stencil{}; stencil.DepthEnable = FALSE; stencil.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ZERO; stencil.StencilEnable = TRUE; stencil.StencilReadMask = D3D11_DEFAULT_STENCIL_READ_MASK; stencil.StencilWriteMask = 0; stencil.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP; stencil.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_KEEP; stencil.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP; stencil.FrontFace.StencilFunc = D3D11_COMPARISON_EQUAL; stencil.BackFace = stencil.FrontFace; Check(m_device->CreateDepthStencilState(&stencil, &m_postStencilState)); LogTelemetry("AC_NATIVE_POST ready=1 filter=point effects=scan,dither,vignette geometryAA=fxaa stencil=d24s8"); }
void UpdatePostConstants(float stencilPass) { D3D11_MAPPED_SUBRESOURCE mapped{}; Check(m_context->Map(m_postConstants.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped)); const float seconds = m_api ? static_cast<float>((m_api->clock.monotonic_us % 1000000000ull) / 1000000.0) : 0.f; *static_cast<PostConstants*>(mapped.pData) = { 1.f / m_frameWidth, 1.f / m_frameHeight, seconds, stencilPass}; m_context->Unmap(m_postConstants.Get(), 0); }
void DrawPostProcess() { m_context->OMSetRenderTargets(0, nullptr, nullptr); if (!m_postVertexShader || !m_postPixelShader || !m_sceneView || !m_pointSampler) { m_context->CopyResource(m_backBuffer.Get(), m_sceneTexture.Get()); return; } ID3D11Buffer* nullBuffer = nullptr; const UINT zero = 0; m_context->IASetInputLayout(nullptr); m_context->IASetVertexBuffers(0, 1, &nullBuffer, &zero, &zero); m_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); m_context->VSSetShader(m_postVertexShader.Get(), nullptr, 0); m_context->PSSetShader(m_postPixelShader.Get(), nullptr, 0); m_context->PSSetShaderResources(0, 1, m_sceneView.GetAddressOf()); m_context->PSSetSamplers(0, 1, m_pointSampler.GetAddressOf()); m_context->PSSetConstantBuffers(0, 1, m_postConstants.GetAddressOf()); m_context->RSSetState(m_triangleRasterState.Get()); const D3D11_VIEWPORT viewport{0, 0, static_cast<float>(m_frameWidth), static_cast<float>(m_frameHeight), 0, 1}; m_context->RSSetViewports(1, &viewport);
UpdatePostConstants(0); m_context->OMSetDepthStencilState(nullptr, 0); m_context->OMSetRenderTargets(1, m_target.GetAddressOf(), nullptr); m_context->Draw(3, 0); // The second pass is rejected everywhere except triangle geometry, keeping // the D2D terrain and point-sampled sprite lanes crisp. UpdatePostConstants(1); m_context->PSSetSamplers(0, 1, m_linearSampler.GetAddressOf()); m_context->OMSetDepthStencilState(m_postStencilState.Get(), 1); m_context->OMSetRenderTargets(1, m_target.GetAddressOf(), m_triangleDepthView.Get()); m_context->Draw(3, 0); ID3D11ShaderResourceView* nullView = nullptr; m_context->PSSetShaderResources(0, 1, &nullView); m_context->OMSetDepthStencilState(nullptr, 0); }
void CreateAudio(uint32_t sampleRate) { DestroyAudio(); m_sampleRate = sampleRate; Check(XAudio2Create(&m_audio)); Check(m_audio->CreateMasteringVoice(&m_master, XAUDIO2_DEFAULT_CHANNELS, sampleRate, 0, nullptr, nullptr, AudioCategory_GameEffects));
WAVEFORMATEX format{}; format.wFormatTag = WAVE_FORMAT_PCM; format.nChannels = 1; format.nSamplesPerSec = sampleRate; format.wBitsPerSample = 16; format.nBlockAlign = format.nChannels * format.wBitsPerSample / 8; format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; Check(m_audio->CreateSourceVoice(&m_voice, &format, 0, XAUDIO2_DEFAULT_FREQ_RATIO)); Check(m_audio->CreateSourceVoice(&m_oscVoice, &format, 0, 64.0f));
const uint32_t frames = sampleRate / 20; // 50 ms; data is allocated once per rate. m_samples.resize(frames); constexpr double tau = 6.2831853071795864769; for (uint32_t i = 0; i < frames; ++i) { const double envelope = 1.0 - static_cast<double>(i) / frames; m_samples[i] = static_cast<int16_t>(std::sin(tau * 880.0 * i / sampleRate) * envelope * 12000.0); } m_buffer = {}; m_buffer.AudioBytes = static_cast<UINT32>(m_samples.size() * sizeof(int16_t)); m_buffer.pAudioData = reinterpret_cast<const BYTE*>(m_samples.data()); m_buffer.Flags = XAUDIO2_END_OF_STREAM;
const uint32_t oscillatorFrames = sampleRate / 100; // one 100 Hz cycle m_oscSamples.resize(oscillatorFrames); for (uint32_t i = 0; i < oscillatorFrames; ++i) { const double phase = tau * i / oscillatorFrames; m_oscSamples[i] = static_cast<int16_t>(std::sin(phase) * 15500.0); } m_oscBuffer = {}; m_oscBuffer.AudioBytes = static_cast<UINT32>(m_oscSamples.size() * sizeof(int16_t)); m_oscBuffer.pAudioData = reinterpret_cast<const BYTE*>(m_oscSamples.data()); m_oscBuffer.LoopCount = XAUDIO2_LOOP_INFINITE; Check(m_oscVoice->SubmitSourceBuffer(&m_oscBuffer)); Check(m_oscVoice->SetVolume(0)); Check(m_oscVoice->Start(0));
wchar_t line[160]; swprintf_s(line, L"AC_NATIVE_AUDIO_READY rate=%uHz frames=%u\n", sampleRate, frames); OutputDebugStringW(line); }
void DestroyAudio() { if (m_oscVoice) { m_oscVoice->DestroyVoice(); m_oscVoice = nullptr; } if (m_voice) { m_voice->DestroyVoice(); m_voice = nullptr; } if (m_master) { m_master->DestroyVoice(); m_master = nullptr; } m_audio.Reset(); }
void PlaySynth(const SynthVoice& voice) { if (!m_voice || m_sampleRate == 0) return; // XAudio2 keeps the submitted sample pointer rather than copying it. Stop // and release the old buffer before a resize can invalidate that pointer. m_voice->Stop(0); m_voice->FlushSourceBuffers(); const float frequency = (std::max)(20.0f, (std::min)(5000.0f, voice.frequency_hz)); const float duration = (std::max)(.005f, (std::min)(2.0f, voice.duration_s)); const float volume = (std::max)(0.0f, (std::min)(1.0f, voice.volume)); const auto frames = static_cast<uint32_t>(m_sampleRate * duration); m_samples.resize(frames); constexpr double tau = 6.2831853071795864769; for (uint32_t i = 0; i < frames; ++i) { const double envelope = 1.0 - static_cast<double>(i) / frames; m_samples[i] = static_cast<int16_t>(std::sin(tau * frequency * i / m_sampleRate) * envelope * volume * 32767.0); } m_buffer = {}; m_buffer.AudioBytes = static_cast<UINT32>(m_samples.size() * sizeof(int16_t)); m_buffer.pAudioData = reinterpret_cast<const BYTE*>(m_samples.data()); m_buffer.Flags = XAUDIO2_END_OF_STREAM; TriggerAudio(0); }
void PlayDrum(std::string_view name, float velocity, float pan) { if (!m_voice || m_sampleRate == 0) return; (void)pan; // The current game-effects voice is mono; preserve the API for a stereo pool. enum class Wave { Sine, Triangle, Square, Noise }; struct Layer { Wave wave; double frequency, duration, volume, attack, decay; }; std::vector<Layer> layers; if (name == "kick") { layers = { {Wave::Noise, 2500, .0025, .50, .0002, .0022}, {Wave::Sine, 200, .012, 1.10, .0005, .011}, {Wave::Sine, 150, .045, 1.30, .001, .044}, {Wave::Sine, 90, .080, .85, .002, .078}, {Wave::Sine, 55, .35, 1.00, .003, .345}, }; } else if (name == "snare") { layers = { {Wave::Noise, 3500, .004, .95, .0001, .004}, {Wave::Sine, 238, .030, .35, .0003, .029}, {Wave::Sine, 476, .030, .28, .0003, .029}, {Wave::Noise, 3500, .11, .85, .0005, .108}, {Wave::Noise, 1800, .07, .38, .0008, .068}, {Wave::Triangle, 180, .025, .22, .001, .024}, }; } else if (name == "clap") { layers = { {Wave::Noise, 1000, .025, .90, .005, .020}, {Wave::Noise, 1100, .035, .95, .015, .020}, {Wave::Noise, 900, .045, .85, .025, .020}, {Wave::Noise, 3000, .008, .55, .001, .007}, {Wave::Noise, 1000, .14, .85, .045, .095}, }; } else if (name == "hat") { layers = { {Wave::Square, 800, .008, .18, .0005, .0075}, {Wave::Square, 540, .008, .18, .0005, .0075}, {Wave::Square, 522.7, .008, .18, .0005, .0075}, {Wave::Square, 369.6, .008, .18, .0005, .0075}, {Wave::Noise, 8000, .040, .38, .0005, .038}, }; } else if (name == "bell") { layers = { {Wave::Sine, 880, .34, .72, .001, .338}, {Wave::Sine, 1320, .27, .38, .001, .268}, {Wave::Triangle, 1760, .18, .20, .001, .178}, }; } else if (name == "whoosh") { layers = { {Wave::Noise, 1450, .34, .68, .018, .32}, {Wave::Noise, 4200, .18, .28, .008, .17}, {Wave::Triangle, 110, .28, .24, .012, .26}, }; } else { layers = { {Wave::Noise, 5000, .002, .35, .0001, .0018}, {Wave::Triangle, 2500, .050, .52, .0003, .048}, {Wave::Triangle, 1250, .050, .18, .0005, .048}, }; }
const double hit = (std::max)(.1, (std::min)(1.5, static_cast<double>(velocity))); double duration = 0; for (const auto& layer : layers) duration = (std::max)(duration, layer.duration); const auto frames = static_cast<uint32_t>(m_sampleRate * duration); std::vector<double> mixed(frames, 0.0); constexpr double tau = 6.2831853071795864769; uint32_t seed = static_cast<uint32_t>(GetTickCount64()) | 1u; for (const auto& layer : layers) { double phase = 0, filteredNoise = 0; const double phaseStep = layer.frequency / m_sampleRate; const double noiseAlpha = 1.0 - std::exp(-tau * (std::min)(layer.frequency, m_sampleRate * .45) / m_sampleRate); const auto layerFrames = static_cast<uint32_t>(m_sampleRate * layer.duration); for (uint32_t index = 0; index < layerFrames && index < frames; ++index) { const double time = static_cast<double>(index) / m_sampleRate; double envelope = 1.0; if (layer.attack > 0 && time < layer.attack) envelope = time / layer.attack; const double decayStart = (std::max)(0.0, layer.duration - layer.decay); if (layer.decay > 0 && time > decayStart) envelope *= 1.0 - (time - decayStart) / layer.decay; double sample = 0; if (layer.wave == Wave::Noise) { seed ^= seed << 13; seed ^= seed >> 17; seed ^= seed << 5; const double noise = static_cast<double>(seed) / UINT32_MAX * 2.0 - 1.0; filteredNoise += noiseAlpha * (noise - filteredNoise); sample = filteredNoise; } else { phase += phaseStep; phase -= std::floor(phase); if (layer.wave == Wave::Sine) sample = std::sin(tau * phase); else if (layer.wave == Wave::Square) sample = phase < .5 ? 1.0 : -1.0; else sample = 1.0 - 4.0 * std::abs(phase - .5); } mixed[index] += sample * envelope * layer.volume; } }
m_voice->Stop(0); m_voice->FlushSourceBuffers(); m_samples.resize(frames); for (uint32_t index = 0; index < frames; ++index) { const double sample = (std::max)(-1.0, (std::min)(1.0, mixed[index] * hit * .28)); m_samples[index] = static_cast<int16_t>(sample * 32767.0); } m_buffer = {}; m_buffer.AudioBytes = static_cast<UINT32>(m_samples.size() * sizeof(int16_t)); m_buffer.pAudioData = reinterpret_cast<const BYTE*>(m_samples.data()); m_buffer.Flags = XAUDIO2_END_OF_STREAM; TriggerAudio(0); }
void SetOscillator(float frequency, float volume) { if (!m_oscVoice) return; frequency = (std::max)(20.0f, (std::min)(5000.0f, frequency)); volume = (std::max)(0.0f, (std::min)(.5f, volume)); Check(m_oscVoice->SetFrequencyRatio(frequency / 100.0f)); Check(m_oscVoice->SetVolume(volume)); m_oscillatorFrequency = frequency; m_oscillatorVolume = volume; }
void StopOscillator() { if (m_oscVoice) Check(m_oscVoice->SetVolume(0)); m_oscillatorVolume = 0; }
void TriggerAudio(unsigned buttonMask) { LARGE_INTEGER before{}, after{}, frequency{}; QueryPerformanceFrequency(&frequency); QueryPerformanceCounter(&before); m_voice->Stop(0); m_voice->FlushSourceBuffers(); Check(m_voice->SubmitSourceBuffer(&m_buffer)); Check(m_voice->Start(0)); QueryPerformanceCounter(&after); const double micros = (after.QuadPart - before.QuadPart) * 1000000.0 / frequency.QuadPart; if (m_api) { m_api->audio.submit_us = micros; const auto eventQpc = m_lastAudioEventQpc.exchange(0); if (eventQpc > 0) m_api->audio.input_to_submit_us = (after.QuadPart - eventQpc) * 1000000.0 / frequency.QuadPart; } wchar_t line[200]; swprintf_s(line, L"AC_NATIVE_INPUT button=0x%X rate=%uHz submit=%.2fus qpc=%lld\n", buttonMask, m_sampleRate, micros, before.QuadPart); OutputDebugStringW(line); }
void RefreshAudioPerformance() { const auto now = GetTickCount64(); if (!m_audio || !m_api || now < m_nextAudioPerfPollMs) return; m_nextAudioPerfPollMs = now + 1000; XAUDIO2_PERFORMANCE_DATA data{}; m_audio->GetPerformanceData(&data); m_api->audio.output_latency_ms = m_sampleRate ? data.CurrentLatencyInSamples * 1000.0 / m_sampleRate : 0; m_api->audio.glitches = data.GlitchesSinceEngineStarted; LogTelemetry("AC_NATIVE_AUDIO_PERF latencyMs=" + std::to_string(m_api->audio.output_latency_ms) + " submitUs=" + std::to_string(m_api->audio.submit_us) + " inputToSubmitUs=" + std::to_string(m_api->audio.input_to_submit_us) + " glitches=" + std::to_string(m_api->audio.glitches) + " midi=" + m_api->audio.midi_status); }
enum class MidiEventKind { NoteOn, NoteOff, PitchBend, ControlChange }; struct PendingMidiEvent { MidiEventKind kind = MidiEventKind::NoteOn; int channel = 0; int data1 = 0; int data2 = 0; long long qpc = 0; };
void InitializeNetworkMidi() { if (m_midiNetworkSocket) return; m_midiNetworkSocket = ref new DatagramSocket(); m_midiNetworkToken = m_midiNetworkSocket->MessageReceived += ref new TypedEventHandler<DatagramSocket^, DatagramSocketMessageReceivedEventArgs^>( this, &App::OnNetworkMidiMessage); create_task(m_midiNetworkSocket->BindServiceNameAsync(ref new String(L"51337"))) .then([this](task<void> completed) { try { completed.get(); m_midiNetworkListening = true; LogTelemetry("AC_NATIVE_MIDI_NETWORK status=listening udp=51337"); } catch (Exception^ error) { m_midiNetworkFailed = true; LogTelemetry("AC_NATIVE_MIDI_NETWORK_ERROR " + Utf8(error->Message)); } }); }
void DestroyNetworkMidi() { if (!m_midiNetworkSocket) return; m_midiNetworkSocket->MessageReceived -= m_midiNetworkToken; delete m_midiNetworkSocket; m_midiNetworkSocket = nullptr; }
void InitializeGameSignals() { if (m_gameSignalSocket) return; m_gameSignalSocket = ref new DatagramSocket(); auto host = ref new Windows::Networking::HostName(L"255.255.255.255"); create_task(m_gameSignalSocket->GetOutputStreamAsync(host, ref new String(L"51338"))) .then([this](task<IOutputStream^> completed) { try { m_gameSignalOutput = completed.get(); LogTelemetry("AC_NATIVE_OSKIEWAR_SIGNAL ready=1 host=255.255.255.255 port=51338 protocol=osc"); } catch (Exception^ error) { LogTelemetry("AC_NATIVE_OSKIEWAR_SIGNAL_ERROR " + Utf8(error->Message)); } }); }
void FlushGameSignals() { if (!m_gameSignalOutput) return; bool expected = false; if (!m_gameSignalWriteInFlight.compare_exchange_strong(expected, true)) return; PendingGameSignalDatagram datagram; { std::lock_guard<std::mutex> lock(m_gameSignalMutex); if (m_gameSignalQueue.empty()) { m_gameSignalWriteInFlight = false; return; } datagram = std::move(m_gameSignalQueue.front()); m_gameSignalQueue.pop_front(); } auto writer = ref new DataWriter(m_gameSignalOutput); writer->WriteBytes(ref new Array<uint8_t>(datagram.packet.data(), static_cast<unsigned>(datagram.packet.size()))); create_task(writer->StoreAsync()).then([this, writer, datagram](task<unsigned> completed) { try { completed.get(); const auto sent = ++m_gameSignalsSent; if (datagram.primary) { LARGE_INTEGER now{}, frequency{}; QueryPerformanceCounter(&now); QueryPerformanceFrequency(&frequency); const auto enqueueToStoreUs = static_cast<std::uint64_t>( (now.QuadPart - datagram.queuedAtQpc) * 1000000LL / frequency.QuadPart); auto previousMax = m_gameSignalMaxEnqueueToStoreUs.load(); while (enqueueToStoreUs > previousMax && !m_gameSignalMaxEnqueueToStoreUs.compare_exchange_weak( previousMax, enqueueToStoreUs)) {} const auto events = ++m_gameSignalEventsSent; if (events == 1 || events % 32 == 0) { LogTelemetry("AC_NATIVE_OSKIEWAR_SIGNAL_LATENCY events=" + std::to_string(events) + " enqueueToStoreUs=" + std::to_string(enqueueToStoreUs) + " maxEnqueueToStoreUs=" + std::to_string(m_gameSignalMaxEnqueueToStoreUs.load())); } } if (sent % 64 == 0) { std::size_t queued = 0; { std::lock_guard<std::mutex> lock(m_gameSignalMutex); queued = m_gameSignalQueue.size(); } LogTelemetry("AC_NATIVE_OSKIEWAR_SIGNAL sent=" + std::to_string(sent) + " queued=" + std::to_string(queued) + " dropped=" + std::to_string(m_gameSignalsDropped.load()) + " copies=3"); } } catch (Exception^ error) { LogTelemetry("AC_NATIVE_OSKIEWAR_SIGNAL_SEND_ERROR " + Utf8(error->Message)); } writer->DetachStream(); delete writer; m_gameSignalWriteInFlight = false; // Drain bursts as fast as StoreAsync completes instead of waiting for the // next video frame before each following datagram. FlushGameSignals(); }); }
void DestroyGameSignals() { if (m_api) m_api->game_signal = {}; m_gameSignalOutput = nullptr; if (m_gameSignalSocket) { delete m_gameSignalSocket; m_gameSignalSocket = nullptr; } const auto dropped = m_gameSignalsDropped.load(); if (dropped) LogTelemetry("AC_NATIVE_OSKIEWAR_SIGNAL dropped=" + std::to_string(dropped)); }
void FlushReplayUploads() { if (m_replayWriteInFlight.load()) return; std::string payload; { std::lock_guard<std::mutex> lock(m_replayMutex); if (m_replayQueue.empty()) return; payload = std::move(m_replayQueue.front()); m_replayQueue.pop_front(); } bool expected = false; if (!m_replayWriteInFlight.compare_exchange_strong(expected, true)) return; auto client = ref new HttpClient(); auto content = ref new HttpStringContent( ref new String(Wide(payload).c_str()), UnicodeEncoding::Utf8, ref new String(L"application/json")); auto uri = ref new Uri( L"https://aesthetic.computer/api/oskiewar-replays?surface=xbox"); create_task(client->PostAsync(uri, content)).then( [this, client, content](task<HttpResponseMessage^> completed) { try { auto response = completed.get(); if (response->IsSuccessStatusCode) { const auto uploaded = ++m_replaysUploaded; LogTelemetry("AC_NATIVE_REPLAY uploaded=" + std::to_string(uploaded) + " status=" + std::to_string(static_cast<int>(response->StatusCode))); } else { ++m_replaysDropped; LogTelemetry("AC_NATIVE_REPLAY_UPLOAD_ERROR status=" + std::to_string(static_cast<int>(response->StatusCode))); } } catch (Exception^ error) { ++m_replaysDropped; LogTelemetry("AC_NATIVE_REPLAY_UPLOAD_ERROR " + Utf8(error->Message)); } m_replayWriteInFlight = false; FlushReplayUploads(); }); }
void QueueClientErrorUpload(std::string detail) { if (detail.empty()) detail = "unknown client error"; if (detail.size() > 7000) detail.resize(7000); const auto sequence = ++m_clientErrorSequence; const auto pieceId = "xbox-oskiewar-" + std::to_string(GetTickCount64()) + "-" + std::to_string(sequence); auto root = ref new JsonObject(); root->SetNamedValue(L"pieceId", JsonValue::CreateStringValue( ref new String(Wide(pieceId).c_str()))); root->SetNamedValue(L"phase", JsonValue::CreateStringValue( ref new String(L"error"))); auto meta = ref new JsonObject(); meta->SetNamedValue(L"slug", JsonValue::CreateStringValue( ref new String(L"oskiewar"))); meta->SetNamedValue(L"host", JsonValue::CreateStringValue( ref new String(L"xbox-native"))); meta->SetNamedValue(L"platform", JsonValue::CreateStringValue( ref new String(L"xbox"))); meta->SetNamedValue(L"build", JsonValue::CreateStringValue( ref new String(Wide(m_api ? m_api->system.version : "unknown").c_str()))); root->SetNamedValue(L"meta", meta); auto data = ref new JsonObject(); data->SetNamedValue(L"message", JsonValue::CreateStringValue( ref new String(Wide(detail).c_str()))); data->SetNamedValue(L"stack", JsonValue::CreateNullValue()); root->SetNamedValue(L"data", data); const auto payload = Utf8(root->Stringify()); std::lock_guard<std::mutex> lock(m_clientErrorMutex); if (m_clientErrorQueue.size() >= 8) m_clientErrorQueue.pop_front(); m_clientErrorQueue.push_back({payload, pieceId}); m_clientErrorStatus = "queued for server " + pieceId; }
void FlushClientErrorUploads() { if (m_clientErrorWriteInFlight.load()) return; std::pair<std::string, std::string> report; { std::lock_guard<std::mutex> lock(m_clientErrorMutex); if (m_clientErrorQueue.empty()) return; report = std::move(m_clientErrorQueue.front()); m_clientErrorQueue.pop_front(); m_clientErrorStatus = "posting to server " + report.second; } bool expected = false; if (!m_clientErrorWriteInFlight.compare_exchange_strong(expected, true)) return; auto client = ref new HttpClient(); auto content = ref new HttpStringContent( ref new String(Wide(report.first).c_str()), UnicodeEncoding::Utf8, ref new String(L"application/json")); auto uri = ref new Uri(L"https://aesthetic.computer/api/piece-log"); create_task(client->PostAsync(uri, content)).then( [this, client, content, report](task<HttpResponseMessage^> completed) { bool retry = false; try { const auto response = completed.get(); retry = !response->IsSuccessStatusCode; LogTelemetry(std::string("AC_NATIVE_CLIENT_ERROR_POST status=") + std::to_string(static_cast<int>(response->StatusCode))); } catch (Exception^ error) { retry = true; LogTelemetry("AC_NATIVE_CLIENT_ERROR_POST_ERROR " + Utf8(error->Message)); } if (retry) { std::lock_guard<std::mutex> lock(m_clientErrorMutex); if (m_clientErrorQueue.size() < 8) m_clientErrorQueue.push_front(report); m_clientErrorStatus = "retrying server post " + report.second; } else { std::lock_guard<std::mutex> lock(m_clientErrorMutex); m_clientErrorStatus = "posted to server " + report.second; } m_clientErrorWriteInFlight = false; if (!retry) FlushClientErrorUploads(); }); }
void DestroyClientErrorUploads() { std::lock_guard<std::mutex> lock(m_clientErrorMutex); m_clientErrorQueue.clear(); m_clientErrorStatus.clear(); }
void DestroyReplayUploads() { if (m_api) m_api->replay_save = {}; std::lock_guard<std::mutex> lock(m_replayMutex); m_replayQueue.clear(); }
void OnNetworkMidiMessage(DatagramSocket^, DatagramSocketMessageReceivedEventArgs^ args) { if (!args) return; try { auto reader = args->GetDataReader(); const auto length = reader->UnconsumedBufferLength; if (length == 0 || length > 192) return; const auto text = Utf8(reader->ReadString(length)); unsigned sequence = 0, status = 0, data1 = 0, data2 = 0; long long sentUs = 0; if (sscanf_s(text.c_str(), "ACM1 %u %lld %x %u %u", &sequence, &sentUs, &status, &data1, &data2) != 5 || status > 255 || data1 > 127 || data2 > 127) return; PendingMidiEvent event; LARGE_INTEGER received{}; QueryPerformanceCounter(&received); event.qpc = received.QuadPart; event.channel = static_cast<int>(status & 0x0f); const auto command = status & 0xf0; if (command == 0x90) { event.kind = data2 ? MidiEventKind::NoteOn : MidiEventKind::NoteOff; event.data1 = static_cast<int>(data1); event.data2 = static_cast<int>(data2); } else if (command == 0x80) { event.kind = MidiEventKind::NoteOff; event.data1 = static_cast<int>(data1); event.data2 = static_cast<int>(data2); } else if (command == 0xe0) { event.kind = MidiEventKind::PitchBend; event.data1 = static_cast<int>(data1 | (data2 << 7)); } else if (command == 0xb0) { event.kind = MidiEventKind::ControlChange; event.data1 = static_cast<int>(data1); event.data2 = static_cast<int>(data2); } else { return; } { std::lock_guard<std::mutex> lock(m_midiMutex); if (m_pendingMidi.size() < 32) m_pendingMidi.push_back(event); } m_midiNetworkActive = true; ++m_midiNetworkPackets; } catch (Exception^) { // Ignore malformed or disconnected datagrams; the listener stays alive. } }
void InitializeMidi() { bool expected = false; if (m_midiInPort || !m_midiScanInFlight.compare_exchange_strong(expected, true)) return; create_task(DeviceInformation::FindAllAsync(MidiInPort::GetDeviceSelector())) .then([this](DeviceInformationCollection^ devices) { if (!m_api) return create_task([] {}); m_api->audio.midi_inputs = devices ? devices->Size : 0; if (!devices || devices->Size == 0) { if (m_midiNetworkActive) { m_api->audio.midi_inputs = 1; m_api->audio.midi_status = "network: reface YC"; } else { m_api->audio.midi_status = "no-input"; } LogTelemetry("AC_NATIVE_MIDI inputs=0 status=no-input network=" + std::to_string(m_midiNetworkActive.load())); return create_task([] {}); } const auto device = devices->GetAt(0); const auto name = Utf8(device->Name); return create_task(MidiInPort::FromIdAsync(device->Id)).then( [this, name](MidiInPort^ port) { if (!m_api) return; if (!port) { m_api->audio.midi_status = "open-failed"; LogTelemetry("AC_NATIVE_MIDI inputs=" + std::to_string(m_api->audio.midi_inputs) + " status=open-failed"); return; } m_midiInPort = port; m_midiToken = m_midiInPort->MessageReceived += ref new TypedEventHandler<MidiInPort^, MidiMessageReceivedEventArgs^>( this, &App::OnMidiMessage); m_midiSubscribed = true; m_api->audio.midi_status = "ready: " + name; LogTelemetry("AC_NATIVE_MIDI inputs=" + std::to_string(m_api->audio.midi_inputs) + " status=ready name=" + name); }); }).then([this](task<void> completed) { try { completed.get(); } catch (Exception^ error) { if (m_api) m_api->audio.midi_status = "error"; LogTelemetry("AC_NATIVE_MIDI_ERROR " + Utf8(error->Message)); } m_midiScanInFlight = false; m_nextMidiScanMs = GetTickCount64() + 3000; }); }
void DestroyMidi() { if (!m_midiInPort) return; if (m_midiSubscribed) m_midiInPort->MessageReceived -= m_midiToken; delete m_midiInPort; m_midiInPort = nullptr; m_midiSubscribed = false; }
void OnMidiMessage(MidiInPort^, MidiMessageReceivedEventArgs^ args) { if (!args || !args->Message) return; PendingMidiEvent event; LARGE_INTEGER now{}; QueryPerformanceCounter(&now); event.qpc = now.QuadPart; switch (args->Message->Type) { case MidiMessageType::NoteOn: { const auto message = dynamic_cast<MidiNoteOnMessage^>(args->Message); if (!message) return; event.kind = message->Velocity ? MidiEventKind::NoteOn : MidiEventKind::NoteOff; event.channel = message->Channel; event.data1 = message->Note; event.data2 = message->Velocity; break; } case MidiMessageType::NoteOff: { const auto message = dynamic_cast<MidiNoteOffMessage^>(args->Message); if (!message) return; event.kind = MidiEventKind::NoteOff; event.channel = message->Channel; event.data1 = message->Note; event.data2 = message->Velocity; break; } case MidiMessageType::PitchBendChange: { const auto message = dynamic_cast<MidiPitchBendChangeMessage^>(args->Message); if (!message) return; event.kind = MidiEventKind::PitchBend; event.channel = message->Channel; event.data1 = message->Bend; break; } case MidiMessageType::ControlChange: { const auto message = dynamic_cast<MidiControlChangeMessage^>(args->Message); if (!message) return; event.kind = MidiEventKind::ControlChange; event.channel = message->Channel; event.data1 = message->Controller; event.data2 = message->ControlValue; break; } default: return; } std::lock_guard<std::mutex> lock(m_midiMutex); if (m_pendingMidi.size() < 32) m_pendingMidi.push_back(event); }
void UpdateMidiOscillator(long long eventQpc) { if (!m_api || !m_api->audio.midi_gate) return; const double bendSemitones = (m_api->audio.midi_pitch_bend - 8192) / 8192.0 * 2.0; const float frequency = static_cast<float>(440.0 * std::pow(2.0, (m_api->audio.midi_note - 69 + bendSemitones) / 12.0)); const float volume = (std::max)(.02f, m_api->audio.midi_velocity / 127.0f * .34f * m_midiVolume); LARGE_INTEGER after{}, counterFrequency{}; SetOscillator(frequency, volume); QueryPerformanceCounter(&after); QueryPerformanceFrequency(&counterFrequency); if (eventQpc > 0) m_api->audio.input_to_submit_us = (after.QuadPart - eventQpc) * 1000000.0 / counterFrequency.QuadPart; }
void PollMidi() { if (!m_api) return; if (!m_midiInPort && GetTickCount64() >= m_nextMidiScanMs) InitializeMidi(); const auto networkPackets = m_midiNetworkPackets.exchange(0); if (networkPackets > 0) { m_api->audio.midi_inputs = 1; m_api->audio.midi_status = "network: reface YC"; } else if (!m_midiInPort && !m_midiNetworkActive && m_midiNetworkListening) { m_api->audio.midi_status = "network-listening :51337"; } else if (!m_midiInPort && m_midiNetworkFailed) { m_api->audio.midi_status = "network-bind-failed"; } std::vector<PendingMidiEvent> events; { std::lock_guard<std::mutex> lock(m_midiMutex); events.swap(m_pendingMidi); } for (const auto& event : events) { m_api->audio.midi_channel = event.channel; ++m_api->audio.midi_events; m_lastAudioEventQpc = event.qpc; if (event.kind == MidiEventKind::NoteOn) { m_api->audio.midi_note = event.data1; m_api->audio.midi_velocity = event.data2; m_api->audio.midi_gate = true; UpdateMidiOscillator(event.qpc); } else if (event.kind == MidiEventKind::NoteOff) { if (event.data1 == m_api->audio.midi_note) { m_api->audio.midi_gate = false; m_api->audio.midi_velocity = 0; StopOscillator(); } } else if (event.kind == MidiEventKind::PitchBend) { m_api->audio.midi_pitch_bend = event.data1; UpdateMidiOscillator(event.qpc); } else if (event.kind == MidiEventKind::ControlChange) { m_api->audio.midi_control = event.data1; m_api->audio.midi_control_value = event.data2; if (event.data1 == 7) m_midiVolume = event.data2 / 127.0f; UpdateMidiOscillator(event.qpc); } LogTelemetry("AC_NATIVE_MIDI kind=" + std::to_string(static_cast<int>(event.kind)) + " ch=" + std::to_string(event.channel + 1) + " data1=" + std::to_string(event.data1) + " data2=" + std::to_string(event.data2) + " gate=" + std::to_string(m_api->audio.midi_gate) + " bend=" + std::to_string(m_api->audio.midi_pitch_bend) + " inputToSubmitUs=" + std::to_string(m_api->audio.input_to_submit_us)); } }
void PollController() { LARGE_INTEGER pollStart{}, pollEnd{}, frequency{}; QueryPerformanceCounter(&pollStart); QueryPerformanceFrequency(&frequency); const auto pads = Gamepad::Gamepads; m_api->gamepad.down.clear(); m_api->gamepad.pads.clear(); if (pads->Size == 0) { m_previousButtons = 0; m_previousLatencyButtons.fill(0); m_previousLatencyGateX.fill(0); m_previousLatencyGateY.fill(0); m_api->gamepad.connected = false; m_api->gamepad.left_x = m_api->gamepad.left_y = 0; m_api->gamepad.right_x = m_api->gamepad.right_y = 0; m_api->gamepad.left_trigger = m_api->gamepad.right_trigger = 0; QueryPerformanceCounter(&pollEnd); m_lastControllerPollUs = (pollEnd.QuadPart - pollStart.QuadPart) * 1000000.0 / frequency.QuadPart; return; } struct ButtonName { GamepadButtons bit; const char* name; }; static constexpr ButtonName names[] = { {GamepadButtons::A, "A"}, {GamepadButtons::B, "B"}, {GamepadButtons::X, "X"}, {GamepadButtons::Y, "Y"}, {GamepadButtons::DPadUp, "ArrowUp"}, {GamepadButtons::DPadDown, "ArrowDown"}, {GamepadButtons::DPadLeft, "ArrowLeft"}, {GamepadButtons::DPadRight, "ArrowRight"}, {GamepadButtons::LeftShoulder, "LeftShoulder"}, {GamepadButtons::RightShoulder, "RightShoulder"}, {GamepadButtons::Menu, "Menu"}, {GamepadButtons::View, "View"}, {GamepadButtons::LeftThumbstick, "LeftStick"}, {GamepadButtons::RightThumbstick, "RightStick"} };
unsigned buttons = 0; bool latencyEdge = false; unsigned latencyPad = 0; for (unsigned index = 0; index < pads->Size; ++index) { const auto reading = pads->GetAt(index)->GetCurrentReading(); PadState state; state.connected = true; state.left_x = static_cast<float>(reading.LeftThumbstickX); state.left_y = static_cast<float>(reading.LeftThumbstickY); state.right_x = static_cast<float>(reading.RightThumbstickX); state.right_y = static_cast<float>(reading.RightThumbstickY); state.left_trigger = static_cast<float>(reading.LeftTrigger); state.right_trigger = static_cast<float>(reading.RightTrigger); const unsigned padButtons = static_cast<unsigned>(reading.Buttons); const int gateX = std::abs(state.left_x) >= .48f ? (state.left_x > 0 ? 1 : -1) : 0; const int gateY = std::abs(state.left_y) >= .48f ? (state.left_y > 0 ? 1 : -1) : 0; if (index < m_previousLatencyButtons.size()) { if (padButtons != m_previousLatencyButtons[index] || gateX != m_previousLatencyGateX[index] || gateY != m_previousLatencyGateY[index]) { latencyEdge = true; latencyPad = index; } m_previousLatencyButtons[index] = padButtons; m_previousLatencyGateX[index] = gateX; m_previousLatencyGateY[index] = gateY; } for (const auto& named : names) if (padButtons & static_cast<unsigned>(named.bit)) state.down.insert(named.name); if (index == 0) buttons = padButtons; m_api->gamepad.pads.push_back(std::move(state)); } QueryPerformanceCounter(&pollEnd); m_lastControllerPollUs = (pollEnd.QuadPart - pollStart.QuadPart) * 1000000.0 / frequency.QuadPart; if (latencyEdge) { m_lastControllerEdgeQpc = pollEnd.QuadPart; m_lastControllerEdgePad = latencyPad; m_controllerEdgePending = true; }
const auto& first = m_api->gamepad.pads.front(); m_api->gamepad.connected = true; m_api->gamepad.left_x = first.left_x; m_api->gamepad.left_y = first.left_y; m_api->gamepad.right_x = first.right_x; m_api->gamepad.right_y = first.right_y; m_api->gamepad.left_trigger = first.left_trigger; m_api->gamepad.right_trigger = first.right_trigger; m_api->gamepad.down = first.down; const unsigned pressed = buttons & ~m_previousButtons; m_previousButtons = buttons; if (!pressed) return;
if (m_supervisor && m_supervisor->active()) { for (const auto& named : names) { if (pressed & static_cast<unsigned>(named.bit)) { LARGE_INTEGER now{}, frequency{}; QueryPerformanceCounter(&now); QueryPerformanceFrequency(&frequency); // Same split divide as RefreshClock. This one was missed the first // time and still wrapped negative past eleven days of uptime. LogTelemetry("AC_NATIVE_INPUT button=" + std::string(named.name) + " qpc_us=" + std::to_string(static_cast<unsigned long long>( now.QuadPart / frequency.QuadPart * 1000000 + now.QuadPart % frequency.QuadPart * 1000000 / frequency.QuadPart))); m_lastAudioEventQpc = now.QuadPart; try { m_supervisor->active()->act(*m_api, {named.name, 1, 0}); } catch (const std::exception& error) { OutputDebugStringA((std::string("AC_NATIVE_BIOS_ACT_ERROR ") + error.what() + "\n").c_str()); } } } }
// Controller edges belong to the active piece. The old native latency // probe also played a generic tone and replaced two complete frames with // a color flash, which made every game action look and sound duplicated. }
void RefreshClock() { LARGE_INTEGER counter{}, frequency{}; QueryPerformanceCounter(&counter); QueryPerformanceFrequency(&frequency); // Split the divide. counter * 1000000 overflows int64 at about ten days of // uptime on a 10 MHz QPC, and reported negative time three times in the // field before anyone connected it to how long the console had been on. m_api->clock.monotonic_us = static_cast<std::uint64_t>( counter.QuadPart / frequency.QuadPart * 1000000 + counter.QuadPart % frequency.QuadPart * 1000000 / frequency.QuadPart); m_api->clock.seconds = static_cast<double>(counter.QuadPart) / frequency.QuadPart; m_api->seconds = m_api->clock.seconds; const auto localUnixMs = SystemUnixMs(); const auto syncAt = m_networkClockSyncUnixMs.load(); m_api->clock.network_synced = syncAt > 0; m_api->clock.network_offset_ms = m_networkClockOffsetMs.load(); m_api->clock.network_rtt_ms = m_networkClockRttMs.load(); m_api->clock.network_sync_age_ms = syncAt > 0 && localUnixMs >= syncAt ? static_cast<std::uint64_t>(localUnixMs - syncAt) : 0; m_api->clock.unix_ms = localUnixMs + m_api->clock.network_offset_ms; }
void RefreshNetworkClock(bool force) { const auto now = GetTickCount64(); if (!force && now < m_nextNetworkClockPollMs) return; m_nextNetworkClockPollMs = now + 10000; bool expected = false; if (!m_networkClockRequestInFlight.compare_exchange_strong(expected, true)) return; const auto sentAt = SystemUnixMs(); auto client = ref new HttpClient(); client->DefaultRequestHeaders->UserAgent->ParseAdd("oskiewar/1.0.0.38 Xbox ClockSync"); create_task(client->GetStringAsync( ref new Uri(L"https://aesthetic.computer/api/clock"))) .then([this, client, sentAt](task<String^> completed) { try { const auto serverAt = ParseIsoUnixMs(Utf8(completed.get())); const auto receivedAt = SystemUnixMs(); const auto rtt = (std::max)(std::int64_t{0}, receivedAt - sentAt); const auto midpoint = sentAt + rtt / 2; m_networkClockOffsetMs = serverAt - midpoint; m_networkClockRttMs = static_cast<std::uint32_t>((std::min)( rtt, static_cast<std::int64_t>(UINT32_MAX))); m_networkClockSyncUnixMs = receivedAt; LogTelemetry("AC_NATIVE_CLOCK_SYNC offsetMs=" + std::to_string(m_networkClockOffsetMs.load()) + " rttMs=" + std::to_string(m_networkClockRttMs.load()) + " source=/api/clock"); } catch (Exception^ error) { LogTelemetry("AC_NATIVE_CLOCK_ERROR " + Utf8(error->Message)); } catch (const std::exception& error) { LogTelemetry("AC_NATIVE_CLOCK_ERROR " + std::string(error.what())); } m_networkClockRequestInFlight = false; }); }
void RefreshCapabilities(bool force) { const auto now = GetTickCount64(); if (!force && now < m_nextCapabilityPollMs) return; m_nextCapabilityPollMs = now + 1000; try { m_api->system.device_family = Utf8(AnalyticsInfo::VersionInfo->DeviceFamily); const auto packed = std::stoull(Utf8(AnalyticsInfo::VersionInfo->DeviceFamilyVersion)); m_api->system.device_family_version = std::to_string((packed >> 48) & 0xffff) + "." + std::to_string((packed >> 32) & 0xffff) + "." + std::to_string((packed >> 16) & 0xffff) + "." + std::to_string(packed & 0xffff); m_api->system.product_name = GamingDeviceName(); if (m_api->system.product_name.empty()) { const auto device = ref new EasClientDeviceInformation(); m_api->system.product_name = Utf8(device->SystemProductName); } } catch (...) { if (m_api->system.device_family.empty()) m_api->system.device_family = "Windows.Xbox"; } m_api->system.memory_usage_bytes = MemoryManager::AppMemoryUsage; m_api->system.memory_limit_bytes = MemoryManager::AppMemoryUsageLimit; m_api->system.expected_memory_limit_bytes = MemoryManager::ExpectedAppMemoryUsageLimit; const auto profile = NetworkInformation::GetInternetConnectionProfile(); m_api->system.online = false; m_api->system.network_level = "none"; m_api->system.network_name.clear(); if (profile) { m_api->system.network_name = Utf8(profile->ProfileName); switch (profile->GetNetworkConnectivityLevel()) { case NetworkConnectivityLevel::InternetAccess: m_api->system.network_level = "internet"; m_api->system.online = true; break; case NetworkConnectivityLevel::ConstrainedInternetAccess: m_api->system.network_level = "constrained"; break; case NetworkConnectivityLevel::LocalAccess: m_api->system.network_level = "local"; break; default: break; } } m_api->gamepad.controllers.clear(); for (auto raw : RawGameController::RawGameControllers) { ControllerInfo info; info.id = Utf8(raw->NonRoamableId); info.name = Utf8(raw->DisplayName); info.vendor_id = raw->HardwareVendorId; info.product_id = raw->HardwareProductId; info.axes = raw->AxisCount; info.buttons = raw->ButtonCount; info.switches = raw->SwitchCount; info.gamepad = Gamepad::FromGameController(raw) != nullptr; m_api->gamepad.controllers.push_back(std::move(info)); } std::string inventory = "online=" + std::to_string(m_api->system.online) + " network=" + m_api->system.network_level + " profile=" + m_api->system.network_name + " family=" + m_api->system.device_family + " product=" + m_api->system.product_name + " memory=" + std::to_string(m_api->system.memory_usage_bytes) + "/" + std::to_string(m_api->system.memory_limit_bytes) + "/" + std::to_string(m_api->system.expected_memory_limit_bytes) + " controllers=" + std::to_string(m_api->gamepad.controllers.size()); for (const auto& controller : m_api->gamepad.controllers) { inventory += " | " + controller.name + " vendor=" + std::to_string(controller.vendor_id) + " product=" + std::to_string(controller.product_id) + " axes=" + std::to_string(controller.axes) + " buttons=" + std::to_string(controller.buttons) + " switches=" + std::to_string(controller.switches) + " gamepad=" + std::to_string(controller.gamepad); } if (inventory != m_lastCapabilityInventory) { m_lastCapabilityInventory = inventory; LogTelemetry("AC_NATIVE_CAPABILITIES " + inventory); } }
void RefreshAcData(bool force) { const auto now = GetTickCount64(); if (!force && now < m_nextAcPollMs) return; m_nextAcPollMs = now + 15000; bool expected = false; if (!m_acRequestInFlight.compare_exchange_strong(expected, true)) return;
auto client = ref new HttpClient(); client->DefaultRequestHeaders->UserAgent->ParseAdd("oskiewar/1.0.0.38 Xbox"); std::vector<task<String^>> requests; const auto safeGet = [client](const std::wstring& url) { return create_task(client->GetStringAsync(ref new Uri(ref new String(url.c_str())))) .then([](task<String^> response) -> String^ { try { return response.get(); } catch (...) { return ref new String(L"{}"); } }); }; requests.push_back(safeGet(L"https://aesthetic.computer/api/mood/moods-of-the-day")); requests.push_back(safeGet(L"https://aesthetic.computer/api/chat-messages?instance=clock&limit=12")); requests.push_back(safeGet(L"https://aesthetic.computer/media-collection?for=%40jeffrey%2Fpainting")); static const std::array<std::wstring, 4> fighterHandles = { L"jeffrey", L"fifi", L"oskie", L"sat" }; for (const auto& handle : fighterHandles) { requests.push_back(safeGet(L"https://aesthetic.computer/api/mood/%40" + handle)); requests.push_back(safeGet(L"https://aesthetic.computer/api/handle-colors?handle=" + handle)); requests.push_back(safeGet(L"https://aesthetic.computer/api/chat-messages?instance=system&limit=1&from=%40" + handle)); }
when_all(requests.begin(), requests.end()).then( [this, client](task<std::vector<String^>> completed) { auto snapshot = std::make_shared<AcSnapshot>(); try { const auto bodies = completed.get(); const auto mood = JsonObject::Parse(bodies[0]); if (mood->HasKey(L"mood")) snapshot->mood = Utf8(mood->GetNamedString(L"mood")); if (mood->HasKey(L"handle")) snapshot->mood_handle = Utf8(mood->GetNamedString(L"handle"));
const auto chat = JsonObject::Parse(bodies[1]); if (chat->HasKey(L"messages")) { const auto messages = chat->GetNamedArray(L"messages"); if (messages->Size > 0) { const auto latest = messages->GetObjectAt(messages->Size - 1); if (latest->HasKey(L"from")) snapshot->clock_from = Utf8(latest->GetNamedString(L"from")); if (latest->HasKey(L"text")) snapshot->clock_text = Utf8(latest->GetNamedString(L"text")); } }
const auto paintings = JsonObject::Parse(bodies[2]); if (paintings->HasKey(L"files")) { const auto files = paintings->GetNamedArray(L"files"); if (files->Size > 0) { snapshot->painting_url = Utf8(files->GetStringAt(files->Size - 1)); snapshot->painting_handle = "@jeffrey"; } } for (unsigned index = 0; index < fighterHandles.size(); ++index) { AcSnapshot::FighterProfile profile; profile.handle = "@" + Utf8(ref new String(fighterHandles[index].c_str())); const auto offset = 3 + index * 3; const auto profileMood = JsonObject::Parse(bodies[offset]); if (profileMood->HasKey(L"mood")) profile.mood = Utf8(profileMood->GetNamedString(L"mood")); const auto profileColors = JsonObject::Parse(bodies[offset + 1]); if (profileColors->HasKey(L"colors") && profileColors->GetNamedValue(L"colors")->ValueType == JsonValueType::Array) { const auto colors = profileColors->GetNamedArray(L"colors"); for (unsigned colorIndex = 0; colorIndex < colors->Size; ++colorIndex) { const auto color = colors->GetObjectAt(colorIndex); profile.colors.push_back({ static_cast<uint8_t>(color->GetNamedNumber(L"r", 255)), static_cast<uint8_t>(color->GetNamedNumber(L"g", 255)), static_cast<uint8_t>(color->GetNamedNumber(L"b", 255)), 255}); } } const auto profileChat = JsonObject::Parse(bodies[offset + 2]); if (profileChat->HasKey(L"messages")) { const auto messages = profileChat->GetNamedArray(L"messages"); if (messages->Size > 0) { const auto latest = messages->GetObjectAt(messages->Size - 1); if (latest->HasKey(L"text")) profile.last_chat = Utf8(latest->GetNamedString(L"text")); } } snapshot->fighters.push_back(std::move(profile)); } FILETIME fileTime{}; GetSystemTimeAsFileTime(&fileTime); ULARGE_INTEGER ticks{}; ticks.LowPart = fileTime.dwLowDateTime; ticks.HighPart = fileTime.dwHighDateTime; snapshot->refreshed_unix_ms = static_cast<std::int64_t>( (ticks.QuadPart - 116444736000000000ULL) / 10000ULL); snapshot->status = "ready"; std::atomic_store(&m_api->ac, std::static_pointer_cast<const AcSnapshot>(snapshot)); DownloadPainting("latest-painting", snapshot->painting_url); LogTelemetry("AC_NATIVE_AC_READY mood=" + snapshot->mood_handle + " clock=" + snapshot->clock_from + " painting=" + std::to_string(!snapshot->painting_url.empty()) + " fighters=" + std::to_string(snapshot->fighters.size())); } catch (Exception^ error) { snapshot->status = "error: " + Utf8(error->Message); std::atomic_store(&m_api->ac, std::static_pointer_cast<const AcSnapshot>(snapshot)); LogTelemetry("AC_NATIVE_AC_ERROR " + Utf8(error->Message)); } catch (const std::exception& error) { snapshot->status = "error: " + std::string(error.what()); std::atomic_store(&m_api->ac, std::static_pointer_cast<const AcSnapshot>(snapshot)); LogTelemetry("AC_NATIVE_AC_ERROR " + std::string(error.what())); } m_acRequestInFlight = false; }); }
void RequestFrameImage(const std::string& source) { if (source == "latest-painting" || source == "disc-photo") return; if (source.size() < 2 || source.size() > 9 || source.front() != '#' || !std::all_of(source.begin() + 1, source.end(), [](unsigned char character) { return (character >= '0' && character <= '9') || (character >= 'A' && character <= 'Z') || (character >= 'a' && character <= 'z'); })) { LogTelemetry("AC_NATIVE_IMAGE_REJECT reason=source"); return; } DownloadPainting(source, "https://aesthetic.computer/media/paintings/" + source.substr(1) + ".png"); }
std::shared_ptr<const PaintingImage> FrameImage(const std::string& source) { std::lock_guard<std::mutex> lock(m_imageMutex); const auto found = m_paintingImages.find(source); return found == m_paintingImages.end() ? nullptr : found->second; }
void DownloadPainting(const std::string& key, const std::string& url) { if (key.empty() || url.empty()) return; { std::lock_guard<std::mutex> lock(m_imageMutex); const auto current = m_paintingImages.find(key); if (current != m_paintingImages.end() && current->second && current->second->url == url) return; if (m_paintingRequests.find(key) != m_paintingRequests.end()) return; if (m_paintingImages.size() + m_paintingRequests.size() >= 8) { LogTelemetry("AC_NATIVE_IMAGE_REJECT reason=cache-limit"); return; } m_paintingRequests.insert(key); } auto client = ref new HttpClient(); create_task(client->GetBufferAsync(ref new Uri(ref new String(Wide(url).c_str())))) .then([](IBuffer^ buffer) { if (!buffer || buffer->Length == 0 || buffer->Length > 8 * 1024 * 1024) throw std::runtime_error("painting payload exceeds 8 MiB limit"); auto stream = ref new InMemoryRandomAccessStream(); auto writer = ref new DataWriter(stream); writer->WriteBuffer(buffer); return create_task(writer->StoreAsync()).then([stream, writer](unsigned) { writer->DetachStream(); stream->Seek(0); return stream; }); }) .then([](InMemoryRandomAccessStream^ stream) { return create_task(BitmapDecoder::CreateAsync(stream)).then([stream](BitmapDecoder^ decoder) { auto transform = ref new BitmapTransform(); const double scale = (std::min)(1.0, 1024.0 / static_cast<double>((std::max)(decoder->PixelWidth, decoder->PixelHeight))); const unsigned width = (std::max)(1u, static_cast<unsigned>(decoder->PixelWidth * scale)); const unsigned height = (std::max)(1u, static_cast<unsigned>(decoder->PixelHeight * scale)); transform->ScaledWidth = width; transform->ScaledHeight = height; return create_task(decoder->GetPixelDataAsync(BitmapPixelFormat::Bgra8, BitmapAlphaMode::Straight, transform, ExifOrientationMode::RespectExifOrientation, ColorManagementMode::DoNotColorManage)).then( [stream, width, height](PixelDataProvider^ provider) { auto result = std::make_shared<PaintingImage>(); result->width = width; result->height = height; const auto bytes = provider->DetachPixelData(); result->pixels.resize(static_cast<std::size_t>(width) * height); for (std::size_t i = 0; i < result->pixels.size(); ++i) { const auto offset = i * 4; result->pixels[i] = (static_cast<uint32_t>(bytes[offset + 3]) << 24) | (static_cast<uint32_t>(bytes[offset + 2]) << 16) | (static_cast<uint32_t>(bytes[offset + 1]) << 8) | static_cast<uint32_t>(bytes[offset]); } return result; }); }); }) .then([this, client, key, url](task<std::shared_ptr<PaintingImage>> completed) { try { auto image = completed.get(); image->url = url; { std::lock_guard<std::mutex> lock(m_imageMutex); m_paintingImages[key] = std::static_pointer_cast<const PaintingImage>(image); } LogTelemetry("AC_NATIVE_PAINTING_READY key=" + key + " " + std::to_string(image->width) + "x" + std::to_string(image->height)); } catch (Exception^ error) { LogTelemetry("AC_NATIVE_PAINTING_ERROR " + Utf8(error->Message)); } catch (const std::exception& error) { LogTelemetry("AC_NATIVE_PAINTING_ERROR " + std::string(error.what())); } std::lock_guard<std::mutex> lock(m_imageMutex); m_paintingRequests.erase(key); }); }
#if AC_DEV_LIVE_PIECE // Dev only. Store Policy 10.2.5 requires console products to be "installed // and updated only through the Microsoft Store", and XR-009 restates it as // "installed, serviced, and updated only through the Store". A retail build // defines AC_DEV_LIVE_PIECE=0 and this function does not exist in it. void PollLivePiece() { const auto now = GetTickCount64(); if (now < m_nextLivePollMs) return; m_nextLivePollMs = now + 500;
const auto folder = ApplicationData::Current->LocalFolder->Path; std::wstring path(folder->Data()); path += L"\\live-piece.js"; struct _stat64 info{}; if (_wstat64(path.c_str(), &info) != 0) return; const auto signature = static_cast<unsigned long long>(info.st_mtime) ^ (static_cast<unsigned long long>(info.st_size) << 32); if (signature == m_livePieceSignature) return; m_livePieceSignature = signature;
if (info.st_size <= 0 || info.st_size > 2 * 1024 * 1024) { LogTelemetry("AC_NATIVE_LIVE_REJECT reason=source-size"); return; } FILE* file = nullptr; if (_wfopen_s(&file, path.c_str(), L"rb") != 0 || !file) { LogTelemetry("AC_NATIVE_LIVE_REJECT reason=open-failed"); return; } std::string source(static_cast<std::size_t>(info.st_size), '\0'); const auto bytes = std::fread(source.data(), 1, source.size(), file); std::fclose(file); source.resize(bytes);
std::string error; if (!m_supervisor->stage({"live", std::to_string(signature), source, "local-dev"}, *m_api, error) || !m_supervisor->activate(*m_api)) { for (auto& character : error) if (character == '\n' || character == '\r') character = ' '; LogTelemetry("AC_NATIVE_LIVE_REJECT reason=" + error); QueueClientErrorUpload("live deploy rejected: " + error); return; } m_loggedTextFrame = false; LogTelemetry("AC_NATIVE_LIVE_READY bytes=" + std::to_string(source.size()) + " generation=" + std::to_string(m_supervisor->generation())); }#endif // AC_DEV_LIVE_PIECE
void ApplyBoxBlur(unsigned radius) { BoxBlurBgra(m_cpuFrame, m_frameWidth, m_frameHeight, radius, m_blurScratch); }
void DrawVectorBackground(const std::array<float, 4>& color, float scaleX, float scaleY) { m_d2dContext->BeginDraw(); m_d2dContext->SetTransform(D2D1::Matrix3x2F::Identity()); m_d2dContext->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED); m_d2dContext->Clear(D2D1::ColorF(color[0], color[1], color[2], color[3])); for (const auto& rect : m_frameRects) { m_textBrush->SetColor(D2D1::ColorF(rect.color.r / 255.f, rect.color.g / 255.f, rect.color.b / 255.f, rect.color.a / 255.f)); m_d2dContext->FillRectangle(D2D1::RectF(rect.x * scaleX, rect.y * scaleY, (rect.x + rect.width) * scaleX, (rect.y + rect.height) * scaleY), m_textBrush.Get()); } for (const auto& line : m_frameLines) { m_textBrush->SetColor(D2D1::ColorF(line.color.r / 255.f, line.color.g / 255.f, line.color.b / 255.f, line.color.a / 255.f)); m_d2dContext->DrawLine(D2D1::Point2F(line.x1 * scaleX, line.y1 * scaleY), D2D1::Point2F(line.x2 * scaleX, line.y2 * scaleY), m_textBrush.Get(), (std::max)(1.f, line.width * (scaleX + scaleY) * .5f)); } const auto hr = m_d2dContext->EndDraw(); if (FAILED(hr) && hr != D2DERR_RECREATE_TARGET) Check(hr); }
void Render(const std::array<float, 4>& color) { if (!m_target) return; LARGE_INTEGER renderStart{}, beforePresent{}, afterPresent{}, frequency{}; QueryPerformanceFrequency(&frequency); QueryPerformanceCounter(&renderStart); if (!m_frameTexts.empty() || !m_frameRects.empty() || !m_frameLines.empty() || !m_frameTriangles.empty() || !m_frameTexturedTriangles.empty() || !m_frameSprites.empty() || !m_frameImages.empty() || m_frameBlurRadius > 0 || !m_frameSystemTexts.empty() || !m_frameSystemGlyphs.empty()) { const float scaleX = m_frameWidth / 1920.0f; const float scaleY = m_frameHeight / 1080.0f; const bool vectorFastPath = m_frameImages.empty() && m_frameTexts.empty() && m_frameBlurRadius == 0 && m_d2dContext.Get() && m_d2dTarget.Get(); if (vectorFastPath) DrawVectorBackground(color, scaleX, scaleY); else { const auto byte = [](float value) { return static_cast<unsigned>(255.0f * (std::max)(0.0f, (std::min)(1.0f, value))); }; const uint32_t background = 0xff000000u | (byte(color[0]) << 16) | (byte(color[1]) << 8) | byte(color[2]); m_cpuFrame.assign(static_cast<std::size_t>(m_frameWidth) * m_frameHeight, background); const auto fill = [this](int left, int top, int right, int bottom, uint32_t value) { left = (std::max)(0, left); top = (std::max)(0, top); right = (std::min)(static_cast<int>(m_frameWidth), right); bottom = (std::min)(static_cast<int>(m_frameHeight), bottom); for (int y = top; y < bottom; ++y) std::fill(m_cpuFrame.begin() + static_cast<std::size_t>(y) * m_frameWidth + left, m_cpuFrame.begin() + static_cast<std::size_t>(y) * m_frameWidth + right, value); }; const auto packed = [](Color value) { return 0xff000000u | (static_cast<uint32_t>(value.r) << 16) | (static_cast<uint32_t>(value.g) << 8) | value.b; }; for (const auto& rect : m_frameRects) { fill(static_cast<int>(rect.x * scaleX), static_cast<int>(rect.y * scaleY), static_cast<int>((rect.x + rect.width) * scaleX), static_cast<int>((rect.y + rect.height) * scaleY), packed(rect.color)); } // Images sit above panel rectangles and below vector lines/type. This // preserves the piece's intended dashboard layering without requiring a // GPU resource per downloaded painting. for (const auto& draw : m_frameImages) { const auto painting = FrameImage(draw.source); if (painting && painting->width > 0 && painting->height > 0) { const float requestedWidth = draw.width > 0 ? draw.width : painting->width * draw.scale; const float requestedHeight = draw.height > 0 ? draw.height : painting->height * draw.scale; const int destWidth = (std::max)(1, static_cast<int>(std::abs(requestedWidth) * scaleX)); const int destHeight = (std::max)(1, static_cast<int>(std::abs(requestedHeight) * scaleY)); const int centerOffsetX = draw.centered ? destWidth / 2 : 0; const int centerOffsetY = draw.centered ? destHeight / 2 : 0; const int destLeft = static_cast<int>(draw.x * scaleX) - centerOffsetX; const int destTop = static_cast<int>(draw.y * scaleY) - centerOffsetY; const int left = (std::max)(0, destLeft); const int top = (std::max)(0, destTop); const int right = (std::min)(static_cast<int>(m_frameWidth), destLeft + destWidth); const int bottom = (std::min)(static_cast<int>(m_frameHeight), destTop + destHeight); for (int y = top; y < bottom; ++y) { const unsigned sourceY = (std::min)(painting->height - 1, static_cast<unsigned>((y - destTop) * painting->height / destHeight)); for (int x = left; x < right; ++x) { const unsigned sourceX = (std::min)(painting->width - 1, static_cast<unsigned>((x - destLeft) * painting->width / destWidth)); const uint32_t source = painting->pixels[ static_cast<std::size_t>(sourceY) * painting->width + sourceX]; const unsigned alpha = source >> 24; auto& destination = m_cpuFrame[static_cast<std::size_t>(y) * m_frameWidth + x]; if (alpha >= 255) destination = source; else if (alpha > 0) { const unsigned inverse = 255 - alpha; const unsigned red = (((source >> 16) & 255) * alpha + ((destination >> 16) & 255) * inverse) / 255; const unsigned green = (((source >> 8) & 255) * alpha + ((destination >> 8) & 255) * inverse) / 255; const unsigned blue = ((source & 255) * alpha + (destination & 255) * inverse) / 255; destination = 0xff000000u | (red << 16) | (green << 8) | blue; } } } } } ApplyBoxBlur(m_frameBlurRadius); const auto edge = [](float ax, float ay, float bx, float by, float px, float py) { return (px - ax) * (by - ay) - (py - ay) * (bx - ax); }; if (!m_triangleVertexBuffer && !m_frameTriangles.empty()) { m_cpuDepth.resize(static_cast<std::size_t>(m_frameWidth) * m_frameHeight); std::fill(m_cpuDepth.begin(), m_cpuDepth.end(), std::numeric_limits<float>::infinity()); for (const auto& triangle : m_frameTriangles) { const float x1 = triangle.x1 * scaleX, y1 = triangle.y1 * scaleY; const float x2 = triangle.x2 * scaleX, y2 = triangle.y2 * scaleY; const float x3 = triangle.x3 * scaleX, y3 = triangle.y3 * scaleY; const float area = edge(x1, y1, x2, y2, x3, y3); if (std::abs(area) < 0.25f) continue; const int left = (std::max)(0, static_cast<int>(std::floor( (std::min)(x1, (std::min)(x2, x3))))); const int right = (std::min)(static_cast<int>(m_frameWidth) - 1, static_cast<int>(std::ceil((std::max)(x1, (std::max)(x2, x3))))); const int top = (std::max)(0, static_cast<int>(std::floor( (std::min)(y1, (std::min)(y2, y3))))); const int bottom = (std::min)(static_cast<int>(m_frameHeight) - 1, static_cast<int>(std::ceil((std::max)(y1, (std::max)(y2, y3))))); const uint32_t ink = packed(triangle.color); for (int y = top; y <= bottom; ++y) for (int x = left; x <= right; ++x) { const float px = x + 0.5f, py = y + 0.5f; const float w0 = edge(x2, y2, x3, y3, px, py); const float w1 = edge(x3, y3, x1, y1, px, py); const float w2 = edge(x1, y1, x2, y2, px, py); if (!((area > 0 && w0 >= 0 && w1 >= 0 && w2 >= 0) || (area < 0 && w0 <= 0 && w1 <= 0 && w2 <= 0))) continue; const float depth = (w0 * triangle.z1 + w1 * triangle.z2 + w2 * triangle.z3) / area; const auto pixel = static_cast<std::size_t>(y) * m_frameWidth + x; if (depth <= m_cpuDepth[pixel]) { m_cpuDepth[pixel] = depth; m_cpuFrame[pixel] = ink; } } } } for (const auto& line : m_frameLines) { const float x1 = line.x1 * scaleX, y1 = line.y1 * scaleY; const float x2 = line.x2 * scaleX, y2 = line.y2 * scaleY; const float dx = x2 - x1, dy = y2 - y1; const int steps = (std::max)(1, static_cast<int>((std::max)(std::abs(dx), std::abs(dy)))); const int radius = (std::max)(1, static_cast<int>(line.width * (scaleX + scaleY) * .25f)); for (int step = 0; step <= steps; ++step) { const float mix = static_cast<float>(step) / steps; const int x = static_cast<int>(x1 + dx * mix); const int y = static_cast<int>(y1 + dy * mix); fill(x - radius, y - radius, x + radius + 1, y + radius + 1, packed(line.color)); } } for (const auto& text : m_frameTexts) { const int cell = (std::max)(2, static_cast<int>(text.size / 7.0f * scaleY)); const int originX = static_cast<int>(text.x * scaleX); int penX = originX; int penY = static_cast<int>(text.y * scaleY); const uint32_t ink = packed(text.color); for (const char character : text.value) { if (character == '\n') { penX = originX; penY += cell * 9; continue; } const auto glyph = BlockGlyph(character); for (int row = 0; row < 7; ++row) for (int column = 0; column < 5; ++column) { if (!(glyph[row] & (1 << (4 - column)))) continue; fill(penX + column * cell, penY + row * cell, penX + (column + 1) * cell - 1, penY + (row + 1) * cell - 1, ink); } penX += cell * 6; } } m_context->OMSetRenderTargets(0, nullptr, nullptr); m_context->UpdateSubresource(m_sceneTexture.Get(), 0, nullptr, m_cpuFrame.data(), m_frameWidth * sizeof(uint32_t), 0); } if ((!m_frameTriangles.empty() || !m_frameTexturedTriangles.empty() || !m_frameSprites.empty()) && m_triangleDepthView) m_context->ClearDepthStencilView(m_triangleDepthView.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1, 0); DrawGpuTriangles(); DrawGpuTexturedTriangles(); DrawGpuSprites(); if (!m_frameSystemTexts.empty() || !m_frameSystemGlyphs.empty()) { m_d2dContext->BeginDraw(); m_d2dContext->SetTransform(D2D1::Matrix3x2F::Identity()); const auto drawText = [this, scaleX, scaleY](const std::wstring& value, const wchar_t* family, float x, float y, float size, Color color) { // DirectWrite retains internal font data for each format it sees. // Creating 25 formats at 60 Hz grew the Xbox UWP working set by // roughly 8 MB/s. Quantize and cache the two system families so a // piece cannot turn animated point sizes into an unbounded cache. const auto pointSize = static_cast<unsigned>((std::max)(6.0f, (std::min)(256.0f, std::round(size * scaleY)))); std::wstring key(family); key += L'#'; key += std::to_wstring(pointSize); auto& format = m_textFormats[key]; if (!format && FAILED(m_dwriteFactory->CreateTextFormat(family, nullptr, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, static_cast<float>(pointSize), L"en-us", &format))) return; m_textBrush->SetColor(D2D1::ColorF(color.r / 255.0f, color.g / 255.0f, color.b / 255.0f, color.a / 255.0f)); const auto area = D2D1::RectF(x * scaleX, y * scaleY, static_cast<float>(m_frameWidth), static_cast<float>(m_frameHeight)); m_d2dContext->DrawText(value.data(), static_cast<UINT32>(value.size()), format.Get(), area, m_textBrush.Get()); }; const auto drawPackagedFont = [this, scaleX, scaleY](IDWriteFontFace* fontFace, const std::wstring& value, float x, float y, float size, Color color) { if (!fontFace) return; const float emSize = (std::max)(6.0f, (std::min)(256.0f, std::round(size * scaleY))); DWRITE_FONT_METRICS fontMetrics{}; fontFace->GetMetrics(&fontMetrics); const float designScale = emSize / fontMetrics.designUnitsPerEm; m_textBrush->SetColor(D2D1::ColorF(color.r / 255.0f, color.g / 255.0f, color.b / 255.0f, color.a / 255.0f)); float baselineY = y * scaleY + fontMetrics.ascent * designScale; std::vector<UINT32> codepoints; const auto flush = [&]() { if (codepoints.empty()) return; std::vector<UINT16> glyphs(codepoints.size()); std::vector<DWRITE_GLYPH_METRICS> metrics(codepoints.size()); std::vector<FLOAT> advances(codepoints.size()); if (FAILED(fontFace->GetGlyphIndices(codepoints.data(), static_cast<UINT32>(codepoints.size()), glyphs.data())) || FAILED(fontFace->GetDesignGlyphMetrics(glyphs.data(), static_cast<UINT32>(glyphs.size()), metrics.data()))) return; for (std::size_t index = 0; index < metrics.size(); ++index) advances[index] = metrics[index].advanceWidth * designScale; DWRITE_GLYPH_RUN run{}; run.fontFace = fontFace; run.fontEmSize = emSize; run.glyphCount = static_cast<UINT32>(glyphs.size()); run.glyphIndices = glyphs.data(); run.glyphAdvances = advances.data(); m_d2dContext->DrawGlyphRun(D2D1::Point2F(x * scaleX, baselineY), &run, m_textBrush.Get(), DWRITE_MEASURING_MODE_NATURAL); codepoints.clear(); }; for (const auto character : value) { if (character == L'\r') continue; if (character == L'\n') { flush(); baselineY += emSize * 1.22f; } else codepoints.push_back(static_cast<UINT32>(character)); } flush(); }; for (const auto& text : m_frameSystemTexts) { if (text.family == "YWFT Processing" && m_ywftFontFace) drawPackagedFont(m_ywftFontFace.Get(), Wide(text.value), text.x, text.y, text.size, text.color); else if (text.family == "Comic Relief" && m_comicFontFace) drawPackagedFont(m_comicFontFace.Get(), Wide(text.value), text.x, text.y, text.size, text.color); else drawText(Wide(text.value), L"Segoe UI", text.x, text.y, text.size, text.color); } for (const auto& glyph : m_frameSystemGlyphs) { const std::wstring symbol(1, Mdl2Glyph(glyph.name)); drawText(symbol, L"Segoe MDL2 Assets", glyph.x, glyph.y, glyph.size, glyph.color); } const auto hr = m_d2dContext->EndDraw(); if (FAILED(hr) && hr != D2DERR_RECREATE_TARGET) Check(hr); } DrawPostProcess(); if (!m_loggedTextFrame) { LogTelemetry("AC_NATIVE_FRAME trianglePath=" + std::string(m_triangleVertexBuffer ? "gpu" : "cpu") + " backgroundPath=" + std::string(vectorFastPath ? "d2d" : "cpu") + " texts=" + std::to_string(m_frameTexts.size()) + " systemTexts=" + std::to_string(m_frameSystemTexts.size()) + " glyphs=" + std::to_string(m_frameSystemGlyphs.size()) + " systemDropped=" + std::to_string(m_frameSystemDrawsDropped) + " images=" + std::to_string(m_frameImages.size()) + " blur=" + std::to_string(m_frameBlurRadius) + " boxes=" + std::to_string(m_frameRects.size()) + " triangles=" + std::to_string(m_frameTriangles.size()) + " trianglesDropped=" + std::to_string(m_frameTrianglesDropped) + " texturedTriangles=" + std::to_string(m_frameTexturedTriangles.size()) + " texturedDropped=" + std::to_string(m_frameTexturedTrianglesDropped) + " sprites=" + std::to_string(m_frameSprites.size()) + " spritesDropped=" + std::to_string(m_frameSpritesDropped) + " lines=" + std::to_string(m_frameLines.size()) + " surface=" + std::to_string(m_frameWidth) + "x" + std::to_string(m_frameHeight)); m_loggedTextFrame = true; } } else { m_context->OMSetRenderTargets(1, m_target.GetAddressOf(), nullptr); m_context->ClearRenderTargetView(m_target.Get(), color.data()); } QueryPerformanceCounter(&beforePresent); const HRESULT hr = m_swapChain->Present(1, 0); QueryPerformanceCounter(&afterPresent); m_lastRenderCpuMs = (beforePresent.QuadPart - renderStart.QuadPart) * 1000.0 / frequency.QuadPart; m_lastPresentMs = (afterPresent.QuadPart - beforePresent.QuadPart) * 1000.0 / frequency.QuadPart; if (m_controllerEdgePending) { const auto edgeToPresentMs = (afterPresent.QuadPart - m_lastControllerEdgeQpc) * 1000.0 / frequency.QuadPart; LogTelemetry("AC_NATIVE_INPUT_LATENCY pad=" + std::to_string(m_lastControllerEdgePad + 1) + " edgeToPresentMs=" + std::to_string(edgeToPresentMs) + " pollUs=" + std::to_string(m_lastControllerPollUs) + " renderCpuMs=" + std::to_string(m_lastRenderCpuMs) + " presentMs=" + std::to_string(m_lastPresentMs)); m_controllerEdgePending = false; } if (FAILED(hr) && hr != DXGI_STATUS_OCCLUDED) Check(hr); }
CoreWindow^ m_window = nullptr; std::unique_ptr<ac::xbox::render::SurfaceHost> m_surface; bool m_closed = false; bool m_needsIdleFrame = false; unsigned m_previousButtons = 0; std::array<unsigned, 4> m_previousLatencyButtons{}; std::array<int, 4> m_previousLatencyGateX{}; std::array<int, 4> m_previousLatencyGateY{}; long long m_lastControllerEdgeQpc = 0; unsigned m_lastControllerEdgePad = 0; bool m_controllerEdgePending = false; double m_lastControllerPollUs = 0;#if AC_DEV_LIVE_PIECE unsigned long long m_livePieceSignature = 0; unsigned long long m_nextLivePollMs = 0;#endif unsigned long long m_nextCapabilityPollMs = 0; unsigned long long m_nextAcPollMs = 0; unsigned long long m_nextNetworkClockPollMs = 0; unsigned long long m_nextAudioPerfPollMs = 0; std::atomic<bool> m_acRequestInFlight{false}; std::atomic<bool> m_networkClockRequestInFlight{false}; std::atomic<std::int64_t> m_networkClockOffsetMs{0}; std::atomic<std::uint32_t> m_networkClockRttMs{0}; std::atomic<std::int64_t> m_networkClockSyncUnixMs{0}; std::atomic<long long> m_lastAudioEventQpc{0}; unsigned m_frameWidth = 0; unsigned m_frameHeight = 0; unsigned m_frameBlurRadius = 0; static constexpr std::size_t kMaxSystemDraws = 128; static constexpr std::size_t kMaxTriangles = 8192; static constexpr std::size_t kMaxTexturedTriangles = 2048; static constexpr std::size_t kMaxSprites = 512; std::size_t m_frameSystemDrawsDropped = 0; std::size_t m_frameTrianglesDropped = 0; std::size_t m_frameTexturedTrianglesDropped = 0; std::size_t m_frameSpritesDropped = 0; std::string m_lastCapabilityInventory; uint32_t m_sampleRate = 48000; std::array<float, 4> m_frameColor{0.025f, 0.02f, 0.04f, 1.0f}; std::vector<ac::xbox::Rect> m_frameRects; std::vector<ac::xbox::Line> m_frameLines; std::vector<ac::xbox::Triangle> m_frameTriangles; std::vector<ac::xbox::TexturedTriangle> m_frameTexturedTriangles; std::vector<ac::xbox::Sprite> m_frameSprites; std::vector<ac::xbox::Text> m_frameTexts; std::vector<ac::xbox::SystemText> m_frameSystemTexts; std::vector<ac::xbox::SystemGlyph> m_frameSystemGlyphs; std::vector<ac::xbox::ImageDraw> m_frameImages; std::mutex m_imageMutex; std::unordered_map<std::string, std::shared_ptr<const PaintingImage>> m_paintingImages; std::unordered_set<std::string> m_paintingRequests; bool m_loggedTextFrame = false; double m_lastRenderCpuMs = 0; double m_lastPresentMs = 0;
ComPtr<ID3D11Device1> m_device; ComPtr<ID3D11DeviceContext1> m_context; ComPtr<IDXGISwapChain1> m_swapChain; ComPtr<ID3D11Texture2D> m_backBuffer; ComPtr<ID3D11RenderTargetView> m_target; ComPtr<ID3D11Texture2D> m_sceneTexture; ComPtr<ID3D11RenderTargetView> m_sceneTarget; ComPtr<ID3D11ShaderResourceView> m_sceneView; ComPtr<ID2D1Factory1> m_d2dFactory; ComPtr<ID2D1Device> m_d2dDevice; ComPtr<ID2D1DeviceContext> m_d2dContext; ComPtr<ID2D1Bitmap1> m_d2dTarget; ComPtr<ID2D1SolidColorBrush> m_textBrush; ComPtr<IDWriteFactory> m_dwriteFactory; ComPtr<IDWriteFontFile> m_ywftFontFile; ComPtr<IDWriteFontFace> m_ywftFontFace; ComPtr<IDWriteFontFile> m_comicFontFile; ComPtr<IDWriteFontFace> m_comicFontFace; std::unordered_map<std::wstring, ComPtr<IDWriteTextFormat>> m_textFormats; ComPtr<ID3D11VertexShader> m_triangleVertexShader; ComPtr<ID3D11PixelShader> m_trianglePixelShader; ComPtr<ID3D11InputLayout> m_triangleInputLayout; ComPtr<ID3D11Buffer> m_triangleVertexBuffer; ComPtr<ID3D11DepthStencilView> m_triangleDepthView; ComPtr<ID3D11DepthStencilState> m_triangleDepthState; ComPtr<ID3D11RasterizerState> m_triangleRasterState; ComPtr<ID3D11VertexShader> m_spriteVertexShader; ComPtr<ID3D11PixelShader> m_spritePixelShader; ComPtr<ID3D11InputLayout> m_spriteInputLayout; ComPtr<ID3D11Buffer> m_spriteVertexBuffer; ComPtr<ID3D11ShaderResourceView> m_spriteAtlasView; ComPtr<ID3D11ShaderResourceView> m_jeffreyTextureView; ComPtr<ID3D11SamplerState> m_pointSampler; ComPtr<ID3D11SamplerState> m_linearSampler; UINT m_jeffreyTextureSize = 0; ComPtr<ID3D11VertexShader> m_postVertexShader; ComPtr<ID3D11PixelShader> m_postPixelShader; ComPtr<ID3D11Buffer> m_postConstants; ComPtr<ID3D11DepthStencilState> m_postStencilState; ComPtr<IXAudio2> m_audio; IXAudio2MasteringVoice* m_master = nullptr; IXAudio2SourceVoice* m_voice = nullptr; IXAudio2SourceVoice* m_oscVoice = nullptr; MidiInPort^ m_midiInPort = nullptr; Windows::Foundation::EventRegistrationToken m_midiToken{}; bool m_midiSubscribed = false; std::mutex m_midiMutex; std::vector<PendingMidiEvent> m_pendingMidi; std::atomic_bool m_midiScanInFlight{false}; ULONGLONG m_nextMidiScanMs = 0; float m_midiVolume = 1; DatagramSocket^ m_midiNetworkSocket = nullptr; Windows::Foundation::EventRegistrationToken m_midiNetworkToken{}; std::atomic_bool m_midiNetworkListening{false}; std::atomic_bool m_midiNetworkFailed{false}; std::atomic_bool m_midiNetworkActive{false}; std::atomic_uint64_t m_midiNetworkPackets{0}; DatagramSocket^ m_gameSignalSocket = nullptr; IOutputStream^ m_gameSignalOutput = nullptr; struct PendingGameSignalDatagram { std::vector<uint8_t> packet; bool primary = false; long long queuedAtQpc = 0; }; std::mutex m_gameSignalMutex; std::deque<PendingGameSignalDatagram> m_gameSignalQueue; std::atomic_bool m_gameSignalWriteInFlight{false}; std::atomic_uint64_t m_gameSignalsSent{0}; std::atomic_uint64_t m_gameSignalEventsSent{0}; std::atomic_uint64_t m_gameSignalMaxEnqueueToStoreUs{0}; std::atomic_uint64_t m_gameSignalsDropped{0}; std::uint32_t m_gameSignalSequence = 0; std::mutex m_replayMutex; std::deque<std::string> m_replayQueue; std::atomic_bool m_replayWriteInFlight{false}; std::atomic_uint64_t m_replaysUploaded{0}; std::atomic_uint64_t m_replaysDropped{0}; std::mutex m_clientErrorMutex; std::deque<std::pair<std::string, std::string>> m_clientErrorQueue; std::string m_clientErrorStatus; std::atomic_bool m_clientErrorWriteInFlight{false}; std::atomic_uint64_t m_clientErrorSequence{0}; std::vector<int16_t> m_samples; std::vector<int16_t> m_oscSamples; std::vector<uint32_t> m_cpuFrame; std::vector<float> m_cpuDepth; std::vector<uint32_t> m_blurScratch; XAUDIO2_BUFFER m_buffer{}; XAUDIO2_BUFFER m_oscBuffer{}; float m_oscillatorFrequency = 0; float m_oscillatorVolume = 0; std::unique_ptr<HostGraphics> m_graphics; std::unique_ptr<HostSound> m_sound; std::unique_ptr<Api> m_api; std::unique_ptr<QuickJsEngine> m_engine; std::unique_ptr<PieceSupervisor> m_supervisor; std::unique_ptr<PhotoDiscService> m_photoDisc; std::unique_ptr<OskiewarLivePublisher> m_oskiewarLive;};
ref class AppSource sealed : public IFrameworkViewSource {public: virtual IFrameworkView^ CreateView() { return ref new App(); }};
} // namespace NativeBios
[MTAThread]int main(Array<String^>^) { CoreApplication::Run(ref new NativeBios::AppSource()); return 0;}