Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
161 kB · 3318 lines
C++
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319#include "pch.h"#include <cstdio>#include "QuickJsEngine.hpp"#include "PhotoDiscService.hpp"#include "OskiewarLivePublisher.hpp"#include "OskiewarAccountService.hpp"#include "../runtime/include/ac/image_effects.hpp"#include "../runtime/include/ac/glass_sound.hpp"#include "../runtime/include/ac/skate_sound.hpp"#include "../runtime/include/ac/chord_sound.hpp"#include "../runtime/include/ac/decal_atlas.hpp"#include "../runtime/include/ac/decal_surface.hpp"#include "../runtime/include/ac/theme_assets.hpp"#include "render/ac_surface.hpp"#if AC_DEV_LIVE_PIECE#include "LivePostShader.hpp"#include <future>#endif
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; float reciprocalDepth = 1;};
struct PostConstants { float texelX, texelY, timeSeconds, stencilPass; float focusY, band, feather, tiltPx; float motionX, motionY, hudDepth, pad;};
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<bool()> is_theme_ready; std::function<bool(int)> is_theme_asset_ready; std::function<void(const ac::xbox::ThemeQuad&)> on_theme_quad; std::function<bool()> on_decal_clear; std::function<bool(const std::array<float,12>&)> on_decal_stamp; std::function<bool(const std::array<float,12>&,const std::array<float,3>&)> on_decal_tint; std::function<void(const ac::xbox::TexturedTriangle&)> on_decal_triangle; std::function<bool(const float*,const float*,std::size_t)> on_sdf_figure; 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); } bool theme_ready() const override { return is_theme_ready && is_theme_ready(); } bool theme_asset_ready(int asset) const override { return is_theme_asset_ready && is_theme_asset_ready(asset); } void theme_quad(const ac::xbox::ThemeQuad& quad) override { if (on_theme_quad) on_theme_quad(quad); } bool decal_clear() override { return on_decal_clear && on_decal_clear(); } bool decal_stamp(const std::array<float,12>& stamp) override { return on_decal_stamp && on_decal_stamp(stamp); } bool decal_tint(const std::array<float,12>& stamp,const std::array<float,3>& tint) override { return on_decal_tint && on_decal_tint(stamp,tint); } void decal_triangle(const ac::xbox::TexturedTriangle& triangle) override { if(on_decal_triangle)on_decal_triangle(triangle); } bool sdf_figure(const float* camera,const float* prims,std::size_t count) override { return on_sdf_figure && on_sdf_figure(camera,prims,count); } void theme_sprite(const ac::xbox::ThemeSprite& sprite) override { const float c = std::cos(sprite.angle), s = std::sin(sprite.angle); const float hx = sprite.width * .5f, hy = sprite.height * .5f; const auto x = [&](float dx, float dy) { return sprite.x + dx*c - dy*s; }; const auto y = [&](float dx, float dy) { return sprite.y + dx*s + dy*c; }; theme_quad({sprite.asset, sprite.sx, sprite.sy, sprite.sw, sprite.sh, x(-hx,-hy),y(-hx,-hy),sprite.z, x(hx,-hy),y(hx,-hy),sprite.z, x(hx,hy),y(hx,hy),sprite.z, x(-hx,hy),y(-hx,hy),sprite.z,sprite.flip,sprite.depth_write}); } 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(float, float)> on_skate_audio; 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 skate_audio(float speed, float volume) override { if (on_skate_audio) on_skate_audio(speed, 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->is_theme_ready = [this]() { return m_themeViews[0] && m_themeViews[1] && m_spriteVertexShader && m_spritePixelShader && m_spriteVertexBuffer && m_linearSampler && m_triangleDepthView; }; m_graphics->is_theme_asset_ready = [this](int asset) { return asset >= 0 && asset < static_cast<int>(theme_assets.size()) && m_themeViews[asset] && m_spriteVertexShader && m_spritePixelShader && m_spriteVertexBuffer && m_linearSampler && m_triangleDepthView && (asset < 2 || asset == 4 || (m_themePixelShader && m_themeBlendState && m_themeSoftDepthState)); }; m_graphics->on_theme_quad = [this](const ac::xbox::ThemeQuad& quad) { if (m_frameThemeQuads.size() < kMaxThemeQuads) m_frameThemeQuads.push_back(quad); }; m_graphics->on_decal_clear = [this]() { if(!m_decalView || !m_themePixelShader)return false; m_decalSurface.clear();return true; }; m_graphics->on_decal_stamp = [this](const std::array<float,12>& stamp) { return m_decalView && m_decalSurface.stamp(stamp); }; m_graphics->on_decal_tint = [this](const std::array<float,12>& stamp,const std::array<float,3>& tint) { return m_decalView && m_decalSurface.stamp(stamp,&tint); }; m_graphics->on_decal_triangle = [this](const ac::xbox::TexturedTriangle& triangle) { if(m_frameDecalTriangles.size()<kMaxDecalTriangles)m_frameDecalTriangles.push_back(triangle); }; m_graphics->on_sdf_figure = [this](const float* camera,const float* prims,std::size_t count) { return QueueSdfFigure(camera,prims,count); }; 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_skateVoice) m_skateVoice->SetVolume(0); if (m_voice) { m_voice->Stop(0); m_voice->FlushSourceBuffers(); } for(auto* voice:m_chordVoices)if(voice){voice->Stop(0);voice->FlushSourceBuffers();} for(auto* voice:m_drumVoices)if(voice){voice->Stop(0);voice->FlushSourceBuffers();} for (auto* voice : m_glassVoices) if (voice) { voice->Stop(0); voice->FlushSourceBuffers(); } }; m_sound->on_oscillator = [this](float frequency, float volume) { SetOscillator(frequency, volume); }; m_sound->on_skate_audio = [this](float speed, float volume) { if (!m_skateVoice) return; m_skateVoice->SetFrequencyRatio(.55f + speed * 1.35f); m_skateVoice->SetVolume(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_oskiewarAccount = std::make_shared<OskiewarAccountService>(*m_api); m_api->system.render_width = m_frameWidth; m_api->system.render_height = m_frameHeight; { // What capabilities().version reports is the installed package, not a // literal that drifts from Package.appxmanifest. const auto id = Windows::ApplicationModel::Package::Current->Id->Version; char version[32]; std::snprintf(version, sizeof version, "%u.%u.%u.%u", unsigned(id.Major), unsigned(id.Minor), unsigned(id.Build), unsigned(id.Revision)); m_api->system.version = version; } 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_api->net_send = [this](std::string_view room, std::string_view packet) { return m_oskiewarLive->send_net(room, packet); }; m_api->net_poll = [this]() { return m_oskiewarLive->poll_net(); }; 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 = {}; m_api->net_send = {}; m_api->net_poll = {}; } 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(); PollLiveShader();#endif FlushGameSignals(); FlushReplayUploads(); FlushClientErrorUploads(); RefreshClock(); RefreshAudioPerformance(); m_frameRects.clear(); m_frameLines.clear(); m_frameTriangles.clear(); m_frameTexturedTriangles.clear(); m_frameDecalTriangles.clear(); m_frameSdfFigures.clear(); m_frameSdfPrims.clear(); m_frameSdfCameras.clear(); m_frameSprites.clear(); m_frameThemeQuads.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 + " sdfFigures=" + std::to_string(m_lastSdfFigureCount)); 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)); ComPtr<IDXGISurface> overlaySurface; Check(m_backBuffer.As(&overlaySurface)); Check(m_d2dContext->CreateBitmapFromDxgiSurface(overlaySurface.Get(), &bitmapProperties, &m_d2dOverlayTarget)); 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(); CreateSdfPipeline(); 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; // Typeless so the post pass can read depth: HUD triangles sit in front of // every body, and the blur leaves them sharp. depth.Format = DXGI_FORMAT_R24G8_TYPELESS; depth.SampleDesc.Count = 1; depth.BindFlags = D3D11_BIND_DEPTH_STENCIL | D3D11_BIND_SHADER_RESOURCE; ComPtr<ID3D11Texture2D> depthTexture; Check(m_device->CreateTexture2D(&depth, nullptr, &depthTexture)); D3D11_DEPTH_STENCIL_VIEW_DESC depthView{}; depthView.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; depthView.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D; Check(m_device->CreateDepthStencilView(depthTexture.Get(), &depthView, &m_triangleDepthView)); depthView.Flags = D3D11_DSV_READ_ONLY_DEPTH | D3D11_DSV_READ_ONLY_STENCIL; Check(m_device->CreateDepthStencilView(depthTexture.Get(), &depthView, &m_triangleDepthReadOnly)); D3D11_SHADER_RESOURCE_VIEW_DESC depthRead{}; depthRead.Format = DXGI_FORMAT_R24_UNORM_X8_TYPELESS; depthRead.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; depthRead.Texture2D.MipLevels = 1; Check(m_device->CreateShaderResourceView(depthTexture.Get(), &depthRead, &m_triangleDepthRead));
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)); depthState.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ZERO; Check(m_device->CreateDepthStencilState(&depthState, &m_themeSoftDepthState));
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(bool hud = false) { 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, color.a / 255.0f, }; }; for (const auto& triangle : m_frameTriangles) { const bool overlay = triangle.z1 <= -1.46f && triangle.z2 <= -1.46f && triangle.z3 <= -1.46f; if (overlay != hud) continue; 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); if (!count) return true;
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); auto* target = hud ? m_target.Get() : m_sceneTarget.Get(); m_context->OMSetRenderTargets(1, &target, m_triangleDepthView.Get()); m_context->Draw(static_cast<UINT>(count), 0); return true; }
// ---- Raymarched figures (sceneApi 3). The piece sends one list of round // cones per figure; each figure draws as one screen rectangle whose pixels // march only that figure, writing true depth beside the triangle world. struct SdfFigureGpu { float sphere[4]; float range[4]; }; struct SdfViewConstants { float camPos[4], right[4], up[4], forward[4], proj[4], depthMap[4], viewport[4], target[4]; }; struct SdfQueued { SdfFigureGpu gpu; std::uint32_t camera; }; static constexpr std::size_t kMaxSdfFigures = 256, kMaxSdfPrims = 8192; static constexpr float kSdfOutlinePx = 1.6f, kSdfBlend = 6.0f;
void CreateSdfPipeline() { const auto vertexBytes = ReadPackageBytes(L"SdfVertexShader.cso"); const auto pixelBytes = ReadPackageBytes(L"SdfPixelShader.cso"); if (vertexBytes.empty() || pixelBytes.empty()) { LogTelemetry("AC_NATIVE_SDF unavailable=shader-assets"); return; } if (FAILED(m_device->CreateVertexShader(vertexBytes.data(), vertexBytes.size(), nullptr, &m_sdfVertexShader)) || FAILED(m_device->CreatePixelShader(pixelBytes.data(), pixelBytes.size(), nullptr, &m_sdfPixelShader))) { LogTelemetry("AC_NATIVE_SDF unavailable=shader-create"); m_sdfVertexShader.Reset(); m_sdfPixelShader.Reset(); return; } const auto structured = [this](UINT stride, UINT count, ComPtr<ID3D11Buffer>& buffer, ComPtr<ID3D11ShaderResourceView>& view) { D3D11_BUFFER_DESC desc{}; desc.ByteWidth = stride * count; desc.Usage = D3D11_USAGE_DYNAMIC; desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; desc.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_STRUCTURED; desc.StructureByteStride = stride; if (FAILED(m_device->CreateBuffer(&desc, nullptr, &buffer))) return false; D3D11_SHADER_RESOURCE_VIEW_DESC srv{}; srv.Format = DXGI_FORMAT_UNKNOWN; srv.ViewDimension = D3D11_SRV_DIMENSION_BUFFER; srv.Buffer.NumElements = count; return SUCCEEDED(m_device->CreateShaderResourceView(buffer.Get(), &srv, &view)); }; D3D11_BUFFER_DESC constants{}; constants.ByteWidth = sizeof(SdfViewConstants); constants.Usage = D3D11_USAGE_DYNAMIC; constants.BindFlags = D3D11_BIND_CONSTANT_BUFFER; constants.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; if (!structured(sizeof(SdfFigureGpu), kMaxSdfFigures, m_sdfFigureBuffer, m_sdfFigureView) || !structured(12 * sizeof(float), kMaxSdfPrims, m_sdfPrimBuffer, m_sdfPrimView) || FAILED(m_device->CreateBuffer(&constants, nullptr, &m_sdfConstants))) { LogTelemetry("AC_NATIVE_SDF unavailable=buffers"); m_sdfVertexShader.Reset(); m_sdfPixelShader.Reset(); return; } LogTelemetry("AC_NATIVE_SDF ready=1 figures=" + std::to_string(kMaxSdfFigures) + " prims=" + std::to_string(kMaxSdfPrims)); }
bool SdfReady() const { return m_sdfVertexShader && m_sdfPixelShader && m_sdfConstants && m_sdfFigureView && m_sdfPrimView && m_triangleDepthView && m_triangleVertexBuffer; }
bool QueueSdfFigure(const float* camera, const float* prims, std::size_t count) { if (!SdfReady() || count == 0 || m_frameSdfFigures.size() >= kMaxSdfFigures || m_frameSdfPrims.size() / 12 + count > kMaxSdfPrims) return false; std::array<float, 27> view{}; std::copy(camera, camera + 27, view.begin()); std::uint32_t cameraIndex = 0; if (m_frameSdfCameras.empty() || m_frameSdfCameras.back() != view) { m_frameSdfCameras.push_back(view); } cameraIndex = static_cast<std::uint32_t>(m_frameSdfCameras.size() - 1); float lo[3]{1e30f, 1e30f, 1e30f}, hi[3]{-1e30f, -1e30f, -1e30f}; for (std::size_t i = 0; i < count; ++i) { const float* p = prims + i * 12; for (int end = 0; end < 2; ++end) { const float* c = p + end * 4; for (int k = 0; k < 3; ++k) { lo[k] = (std::min)(lo[k], c[k] - c[3]); hi[k] = (std::max)(hi[k], c[k] + c[3]); } } } SdfQueued figure{}; for (int k = 0; k < 3; ++k) figure.gpu.sphere[k] = (lo[k] + hi[k]) * .5f; figure.gpu.sphere[3] = .5f * std::sqrt((hi[0] - lo[0]) * (hi[0] - lo[0]) + (hi[1] - lo[1]) * (hi[1] - lo[1]) + (hi[2] - lo[2]) * (hi[2] - lo[2])) + kSdfBlend + 2; figure.gpu.range[1] = static_cast<float>(count); figure.camera = cameraIndex; // range[0] (the first prim) is assigned at upload, after sorting by camera. figure.gpu.range[2] = static_cast<float>(m_frameSdfPrims.size() / 12); m_frameSdfPrims.insert(m_frameSdfPrims.end(), prims, prims + count * 12); m_frameSdfFigures.push_back(figure); return true; }
void DrawGpuSdfFigures() { m_lastSdfFigureCount = m_frameSdfFigures.size(); if (m_frameSdfFigures.empty() || !SdfReady()) return; // A new camera index opens whenever the view changes, so the queue is // already in runs of one camera: one instanced draw per run. D3D11_MAPPED_SUBRESOURCE mapped{}; if (FAILED(m_context->Map(m_sdfFigureBuffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped))) return; auto* figures = static_cast<SdfFigureGpu*>(mapped.pData); for (std::size_t i = 0; i < m_frameSdfFigures.size(); ++i) { figures[i] = m_frameSdfFigures[i].gpu; figures[i].range[0] = figures[i].range[2]; figures[i].range[2] = 0; } m_context->Unmap(m_sdfFigureBuffer.Get(), 0); if (FAILED(m_context->Map(m_sdfPrimBuffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped))) return; std::memcpy(mapped.pData, m_frameSdfPrims.data(), m_frameSdfPrims.size() * sizeof(float)); m_context->Unmap(m_sdfPrimBuffer.Get(), 0);
m_context->IASetInputLayout(nullptr); m_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); m_context->VSSetShader(m_sdfVertexShader.Get(), nullptr, 0); m_context->PSSetShader(m_sdfPixelShader.Get(), nullptr, 0); ID3D11ShaderResourceView* views[] = {m_sdfFigureView.Get(), m_sdfPrimView.Get()}; m_context->VSSetShaderResources(0, 2, views); m_context->PSSetShaderResources(0, 2, views); m_context->VSSetConstantBuffers(0, 1, m_sdfConstants.GetAddressOf()); m_context->PSSetConstantBuffers(0, 1, m_sdfConstants.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); m_context->OMSetDepthStencilState(m_triangleDepthState.Get(), 1); m_context->OMSetRenderTargets(1, m_sceneTarget.GetAddressOf(), m_triangleDepthView.Get()); std::size_t first = 0; while (first < m_frameSdfFigures.size()) { const auto cameraIndex = m_frameSdfFigures[first].camera; std::size_t last = first; while (last < m_frameSdfFigures.size() && m_frameSdfFigures[last].camera == cameraIndex) ++last; const auto& m = m_frameSdfCameras[cameraIndex]; SdfViewConstants c{ {m[0], m[1], m[2], 0}, {m[3], m[4], m[5], m[12]}, {m[6], m[7], m[8], m[13]}, {m[9], m[10], m[11], m[17]}, {m[14], m[15], m[16], 1920.0f / static_cast<float>(m_frameWidth)}, {m[22], m[23], kSdfOutlinePx, kSdfBlend}, {m[18], m[19], m[20], m[21]}, {static_cast<float>(m_frameWidth), static_cast<float>(m_frameHeight), static_cast<float>(first), 0}}; if (SUCCEEDED(m_context->Map(m_sdfConstants.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped))) { std::memcpy(mapped.pData, &c, sizeof(c)); m_context->Unmap(m_sdfConstants.Get(), 0); m_context->DrawInstanced(6, static_cast<UINT>(last - first), 0, 0); } first = last; } ID3D11ShaderResourceView* none[2]{}; m_context->VSSetShaderResources(0, 2, none); m_context->PSSetShaderResources(0, 2, none); }
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 auto themePixelBytes = ReadPackageBytes(L"ThemePixelShader.cso"); if (!themePixelBytes.empty()) Check(m_device->CreatePixelShader(themePixelBytes.data(), themePixelBytes.size(), nullptr, &m_themePixelShader)); D3D11_BLEND_DESC blend{}; blend.RenderTarget[0].BlendEnable = TRUE; blend.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA; blend.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA; blend.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; blend.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE; blend.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA; blend.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; blend.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; Check(m_device->CreateBlendState(&blend, &m_themeBlendState)); 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}, {"TEXCOORD", 1, DXGI_FORMAT_R32_FLOAT, 0, 36, 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, kMaxDecalTriangles * 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")); const wchar_t* themePaths[] = {L"Assets\\ThemeUnderpass.rgba", L"Assets\\ThemeProps.rgba", L"Assets\\ThemeExplosions.rgba", L"Assets\\ThemeWeapons.rgba", L"Assets\\ThemeSkyClouds.rgba"}; for (int asset = 0; asset < static_cast<int>(std::size(themePaths)); ++asset) { const auto rgba = ReadPackageBytes(themePaths[asset]); texture.Width = theme_assets[asset].width; texture.Height = theme_assets[asset].height; if (rgba.size() != static_cast<std::size_t>(texture.Width) * texture.Height * 4) continue; pixels.pSysMem = rgba.data(); pixels.SysMemPitch = texture.Width * 4; ComPtr<ID3D11Texture2D> retained; if (SUCCEEDED(m_device->CreateTexture2D(&texture, &pixels, &retained))) m_device->CreateShaderResourceView(retained.Get(), nullptr, &m_themeViews[asset]); } const auto decalPixels = make_decal_atlas(); texture.Width = theme_assets[5].width; texture.Height = theme_assets[5].height; pixels.pSysMem = decalPixels.data(); pixels.SysMemPitch = texture.Width * 4; ComPtr<ID3D11Texture2D> decalTexture; Check(m_device->CreateTexture2D(&texture, &pixels, &decalTexture)); Check(m_device->CreateShaderResourceView(decalTexture.Get(), nullptr, &m_themeViews[5])); texture.Width=texture.Height=DecalSurface::side; texture.Usage=D3D11_USAGE_DEFAULT; pixels.pSysMem=m_decalSurface.pixels.data();pixels.SysMemPitch=DecalSurface::side*4; Check(m_device->CreateTexture2D(&texture,&pixels,&m_decalTexture)); Check(m_device->CreateShaderResourceView(m_decalTexture.Get(),nullptr,&m_decalView)); m_decalSurface.clean(); LogTelemetry("AC_NATIVE_DECALS ready=1 atlas=256x256 surface=4096x4096 retained=1 alpha=1"); m_frameThemeQuads.reserve(kMaxThemeQuads); LogTelemetry(std::string("AC_NATIVE_THEME ready=") + (m_themeViews[0] && m_themeViews[1] ? "1" : "0") + " effects=" + (m_themeViews[2] && m_themeViews[3] && m_themePixelShader ? "1" : "0") + " sky=" + (m_themeViews[4] ? "1" : "0") + " rgbaBytes=13107200 maxQuads=1024"); }
bool DrawGpuThemeQuads() { if (m_frameThemeQuads.empty()) return true; if (!m_spriteVertexBuffer || !m_triangleDepthView) return false; // Solid props precede soft effects, which depth-test without punching // holes in subsequent translucent draws. Base atlases keep their cutout path. const int assets[] = {4, 0, 1, 3, 3, 2, 5}; for (int pass = 0; pass < static_cast<int>(std::size(assets)); ++pass) { const int asset = assets[pass]; if (!m_themeViews[asset] || ((asset == 2 || asset == 3 || asset == 5) && !m_themePixelShader)) continue; const bool depthWrite = pass < 4; 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 = [&](float x, float y, float z, float u, float v) { 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,1,1,1,1}; }; const float sourceWidth = static_cast<float>(theme_assets[asset].master_width); const float sourceHeight = static_cast<float>(theme_assets[asset].master_height); for (const auto& q : m_frameThemeQuads) { if (q.asset != asset) continue; if ((asset == 3 || asset == 5) && q.depth_write != depthWrite) continue; float u0=q.sx/sourceWidth, u1=(q.sx+q.sw)/sourceWidth; if (q.flip) std::swap(u0,u1); const float v0=q.sy/sourceHeight,v1=(q.sy+q.sh)/sourceHeight; append(q.x1,q.y1,q.z1,u0,v0);append(q.x2,q.y2,q.z2,u1,v0);append(q.x3,q.y3,q.z3,u1,v1); append(q.x1,q.y1,q.z1,u0,v0);append(q.x3,q.y3,q.z3,u1,v1);append(q.x4,q.y4,q.z4,u0,v1); } m_context->Unmap(m_spriteVertexBuffer.Get(),0); if (!count) continue; 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(!depthWrite ? m_themePixelShader.Get() : m_spritePixelShader.Get(),nullptr,0); m_context->PSSetShaderResources(0,1,m_themeViews[asset].GetAddressOf()); m_context->PSSetSamplers(0,1,asset==5?m_pointSampler.GetAddressOf():m_linearSampler.GetAddressOf()); m_context->RSSetState(m_triangleRasterState.Get()); m_context->OMSetDepthStencilState(depthWrite ? m_triangleDepthState.Get() : m_themeSoftDepthState.Get(),1); m_context->OMSetBlendState((asset == 2 || asset == 3 || asset == 5) ? m_themeBlendState.Get() : nullptr,nullptr,0xffffffff); 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); } m_context->OMSetBlendState(nullptr,nullptr,0xffffffff); m_context->OMSetDepthStencilState(m_triangleDepthState.Get(),1); return true; }
bool DrawGpuTexturedTriangles(bool decals=false) { const auto& triangles=decals?m_frameDecalTriangles:m_frameTexturedTriangles; auto* view=decals?m_decalView.Get():m_jeffreyTextureView.Get(); if (triangles.empty()) return true; if(decals && m_decalSurface.dirty && m_decalTexture){ const auto& surface=m_decalSurface; const D3D11_BOX box{surface.left,surface.top,0,surface.right,surface.bottom,1}; m_context->UpdateSubresource(m_decalTexture.Get(),0,&box, surface.pixels.data()+(surface.top*DecalSurface::side+surface.left)*4,DecalSurface::side*4,0); m_decalSurface.clean(); } if (!m_spriteVertexBuffer || !m_spriteVertexShader || !m_spritePixelShader || !view || !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, float reciprocalDepth) { 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, reciprocalDepth}; }; for (const auto& triangle : triangles) { append(triangle.x1, triangle.y1, triangle.z1, triangle.u1, triangle.v1, triangle.color, triangle.q1); append(triangle.x2, triangle.y2, triangle.z2, triangle.u2, triangle.v2, triangle.color, triangle.q2); append(triangle.x3, triangle.y3, triangle.z3, triangle.u3, triangle.v3, triangle.color, triangle.q3); } 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(decals?m_themePixelShader.Get():m_spritePixelShader.Get(), nullptr, 0); m_context->PSSetShaderResources(0, 1, &view); m_context->PSSetSamplers(0, 1, decals?m_pointSampler.GetAddressOf():m_linearSampler.GetAddressOf()); m_context->RSSetState(m_triangleRasterState.Get()); m_context->OMSetDepthStencilState(decals?m_themeSoftDepthState.Get():m_triangleDepthState.Get(), 1); m_context->OMSetBlendState(decals?m_themeBlendState.Get():nullptr,nullptr,0xffffffff); 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); m_context->OMSetBlendState(nullptr,nullptr,0xffffffff); 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,tiltshift,motionblur 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; const auto post = m_api ? m_api->post_effects : ac::xbox::Api::PostEffects{}; // HUD triangles are drawn at z <= -1.46; the depth pass maps (z + 1.5) / 3. *static_cast<PostConstants*>(mapped.pData) = { 1.f / m_frameWidth, 1.f / m_frameHeight, seconds, stencilPass, post.focus_y, post.band, post.feather, post.tilt_px, post.motion_x, post.motion_y, (-1.46f + 1.5f) / 3.f, 0.f}; 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); ID3D11ShaderResourceView* postInputs[] = {m_sceneView.Get(), m_triangleDepthRead.Get()}; m_context->PSSetShaderResources(0, 2, postInputs); 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); // Read-only depth-stencil, so the same depth can be sampled for the HUD mask. m_context->OMSetRenderTargets(1, m_target.GetAddressOf(), m_triangleDepthReadOnly.Get()); m_context->Draw(3, 0); ID3D11ShaderResourceView* nullViews[] = {nullptr, nullptr}; m_context->PSSetShaderResources(0, 2, nullViews); 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)); for (auto& voice : m_glassVoices) Check(m_audio->CreateSourceVoice(&voice, &format, 0, XAUDIO2_DEFAULT_FREQ_RATIO)); for(auto& voice:m_drumVoices)Check(m_audio->CreateSourceVoice(&voice,&format,0,XAUDIO2_DEFAULT_FREQ_RATIO)); Check(m_audio->CreateSourceVoice(&m_skateVoice, &format, 0, 2.0f)); for(int i=0;i<4;i++){Check(m_audio->CreateSourceVoice(&m_chordVoices[i],&format,0,XAUDIO2_DEFAULT_FREQ_RATIO));m_chordSamples[i]=ac::synthesize_park_chord(sampleRate,i);} m_skateSamples = synthesize_skate_roll(sampleRate); XAUDIO2_BUFFER skateBuffer{}; skateBuffer.AudioBytes = static_cast<UINT32>(m_skateSamples.size() * sizeof(int16_t)); skateBuffer.pAudioData = reinterpret_cast<const BYTE*>(m_skateSamples.data()); skateBuffer.LoopCount = XAUDIO2_LOOP_INFINITE; Check(m_skateVoice->SubmitSourceBuffer(&skateBuffer)); Check(m_skateVoice->SetVolume(0)); Check(m_skateVoice->Start(0)); m_glassSamples = synthesize_glass(sampleRate); m_glassShardSamples = synthesize_glass(sampleRate, true);
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_skateVoice) { m_skateVoice->DestroyVoice(); m_skateVoice = nullptr; } for (auto& voice : m_glassVoices) { if (voice) { voice->DestroyVoice(); voice = nullptr; } } m_nextGlassVoice = 0; for(auto& voice:m_drumVoices)if(voice){voice->DestroyVoice();voice=nullptr;} for(auto& voice:m_chordVoices)if(voice){voice->DestroyVoice();voice=nullptr;} 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 PlayGlass(bool shard, float velocity) { auto* voice = m_glassVoices[m_nextGlassVoice++ % m_glassVoices.size()]; const auto& samples = shard ? m_glassShardSamples : m_glassSamples; if (!voice || samples.empty()) return; voice->Stop(0); voice->FlushSourceBuffers(); XAUDIO2_BUFFER buffer{}; buffer.AudioBytes = static_cast<UINT32>(samples.size() * sizeof(int16_t)); buffer.pAudioData = reinterpret_cast<const BYTE*>(samples.data()); buffer.Flags = XAUDIO2_END_OF_STREAM; Check(voice->SetVolume((std::max)(0.f, (std::min)(1.f, velocity)))); Check(voice->SubmitSourceBuffer(&buffer)); Check(voice->Start(0)); }
void PlayDrum(std::string_view name, float velocity, float pan) { if(name.size()==5 && name.substr(0,4)=="pad-" && name[4]>='0' && name[4]<='3') { const int index=name[4]-'0';auto* voice=m_chordVoices[m_nextChordVoice++%4]; const auto& samples=m_chordSamples[index];if(!voice||samples.empty())return; voice->Stop(0);voice->FlushSourceBuffers(); voice->SetVolume((std::max)(0.f,(std::min)(1.f,velocity))); XAUDIO2_VOICE_DETAILS details{};m_master->GetVoiceDetails(&details); if(details.InputChannels>=2){std::vector<float> matrix(details.InputChannels,0);const float p=(std::max)(-1.f,(std::min)(1.f,pan));matrix[0]=std::sqrt((1-p)*.5f);matrix[1]=std::sqrt((1+p)*.5f);voice->SetOutputMatrix(m_master,1,details.InputChannels,matrix.data());} XAUDIO2_BUFFER buffer{};buffer.AudioBytes=static_cast<UINT32>(samples.size()*sizeof(int16_t));buffer.pAudioData=reinterpret_cast<const BYTE*>(samples.data());buffer.Flags=XAUDIO2_END_OF_STREAM; Check(voice->SubmitSourceBuffer(&buffer));Check(voice->Start(0));return; } if (name == "glass" || name == "glass-shard") { PlayGlass(name == "glass-shard", velocity); return; } if (!m_voice || m_sampleRate == 0) return;
enum class Wave { Sine, Triangle, Square, Noise }; struct Layer { Wave wave; double frequency, duration, volume, attack, decay; }; std::vector<Layer> layers; if (name == "gunshot" || name == "smg-shot") { layers = {{Wave::Noise,11000,.006,1.25,.0001,.0059},{Wave::Noise,3800,.075,.9,.0002,.074},{Wave::Noise,900,.18,.46,.001,.179},{Wave::Sine,80,.055,.45,.0003,.054}}; if(name=="smg-shot")for(auto& layer:layers)layer.duration*=.7; } else if (name == "bass") { layers = {{Wave::Triangle,55,.18,.6,.003,.17},{Wave::Sine,55,.2,.7,.002,.19}}; } else 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)(0.0, (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; } }
const auto slot=m_nextDrumVoice++%m_drumVoices.size();auto* voice=m_drumVoices[slot];if(!voice)return; voice->Stop(0);voice->FlushSourceBuffers();auto& samples=m_drumSamples[slot];samples.resize(frames); for(uint32_t i=0;i<frames;i++)samples[i]=static_cast<int16_t>((std::max)(-1.0,(std::min)(1.0,mixed[i]*hit*.28))*32767.0); XAUDIO2_VOICE_DETAILS details{};m_master->GetVoiceDetails(&details); if(details.InputChannels>=2){std::vector<float> matrix(details.InputChannels,0);const float p=(std::max)(-1.f,(std::min)(1.f,pan));matrix[0]=std::sqrt((1-p)*.5f);matrix[1]=std::sqrt((1+p)*.5f);voice->SetOutputMatrix(m_master,1,details.InputChannels,matrix.data());} XAUDIO2_BUFFER buffer{};buffer.AudioBytes=static_cast<UINT32>(samples.size()*sizeof(int16_t));buffer.pAudioData=reinterpret_cast<const BYTE*>(samples.data());buffer.Flags=XAUDIO2_END_OF_STREAM; Check(voice->SubmitSourceBuffer(&buffer));Check(voice->Start(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 void ShaderStatus(const std::string& id, const char* status, const std::string& error = "", double milliseconds = 0) { auto record = ref new JsonObject(); record->Insert("id", JsonValue::CreateStringValue(ref new String(Wide(id).c_str()))); record->Insert("status", JsonValue::CreateStringValue(ref new String(Wide(status).c_str()))); record->Insert("error", JsonValue::CreateStringValue(ref new String(Wide(error).c_str()))); record->Insert("compileMs", JsonValue::CreateNumberValue(milliseconds)); const auto json = Utf8(record->Stringify()); const auto path = std::wstring(ApplicationData::Current->LocalFolder->Path->Data()) + L"\\live-post-status.json"; FILE* file = nullptr; if (_wfopen_s(&file, path.c_str(), L"wb") == 0 && file) { std::fwrite(json.data(), 1, json.size(), file); std::fclose(file); } LogTelemetry("AC_NATIVE_SHADER " + json); }
void PollLiveShader() { const auto now = GetTickCount64(); if (now >= m_nextShaderPollMs) { m_nextShaderPollMs = now + 500; const auto path = std::wstring(ApplicationData::Current->LocalFolder->Path->Data()) + L"\\live-post.hlsl"; FILE* file = nullptr; if (_wfopen_s(&file, path.c_str(), L"rb") == 0 && file) { // Content comparison catches same-size edits within one second. std::string text(kLiveShaderLimit + 128, '\0'); const auto count = std::fread(text.data(), 1, text.size(), file); std::fclose(file); text.resize(count); if (!text.empty()) m_liveShaderSource = std::move(text); } } if (m_shaderCompile.valid()) { if (m_shaderCompile.wait_for(std::chrono::milliseconds(0)) != std::future_status::ready) return; LiveShaderResult result; try { result = m_shaderCompile.get(); } catch (const std::exception& error) { result.error = error.what(); } // New uploads supersede an in-flight compile, including reset. if (m_shaderAttempt == m_liveShaderSource) { const auto request = parse_live_shader(m_shaderAttempt); ComPtr<ID3D11PixelShader> shader; if (result.error.empty()) { const auto hr = m_device->CreatePixelShader(result.bytes.data(), result.bytes.size(), nullptr, &shader); if (FAILED(hr)) result.error = "CreatePixelShader failed: " + std::to_string(hr); } if (result.error.empty()) { m_postPixelShader = shader; ShaderStatus(request.id, "applied", "", result.milliseconds); } else ShaderStatus(request.id, "rejected", result.error, result.milliseconds); } } if (m_liveShaderSource.empty() || m_liveShaderSource == m_shaderAttempt) return; m_shaderAttempt = m_liveShaderSource; const auto request = parse_live_shader(m_shaderAttempt); if (request.reset) { const auto bytes = ReadPackageBytes(L"PostPixelShader.cso"); ComPtr<ID3D11PixelShader> shader; const auto hr = bytes.empty() ? E_FAIL : m_device->CreatePixelShader( bytes.data(), bytes.size(), nullptr, &shader); if (FAILED(hr)) ShaderStatus(request.id, "rejected", "packaged shader unavailable"); else { m_postPixelShader = shader; ShaderStatus(request.id, "reset"); } return; } ShaderStatus(request.id, "compiling"); m_shaderCompile = std::async(std::launch::async, [source = request.source]() { return compile_live_post_shader(source); }); }
// 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(); m_d2dContext->SetTarget(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_triangleDepthView) m_context->ClearDepthStencilView(m_triangleDepthView.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1, 0); DrawGpuTriangles(); DrawGpuSdfFigures(); DrawGpuTexturedTriangles(); DrawGpuSprites(); DrawGpuThemeQuads(); DrawGpuTexturedTriangles(true); // Blur the world before adding any HUD pixels. Text is Direct2D and // never writes depth, so a depth mask cannot protect it in the post pass. DrawPostProcess(); DrawGpuTriangles(true); m_context->OMSetRenderTargets(0, nullptr, nullptr); m_d2dContext->SetTarget(m_d2dOverlayTarget.Get()); 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); } 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) + " decalTriangles=" + std::to_string(m_frameDecalTriangles.size()) + " 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; unsigned long long m_nextShaderPollMs = 0; std::string m_liveShaderSource, m_shaderAttempt; std::future<LiveShaderResult> m_shaderCompile;#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 kMaxDecalTriangles = 8192; static constexpr std::size_t kMaxSprites = 512; static constexpr std::size_t kMaxThemeQuads = 1024; 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::TexturedTriangle> m_frameDecalTriangles; std::vector<SdfQueued> m_frameSdfFigures; std::vector<float> m_frameSdfPrims; std::vector<std::array<float, 27>> m_frameSdfCameras; std::size_t m_lastSdfFigureCount = 0; ComPtr<ID3D11VertexShader> m_sdfVertexShader; ComPtr<ID3D11PixelShader> m_sdfPixelShader; ComPtr<ID3D11Buffer> m_sdfConstants; ComPtr<ID3D11Buffer> m_sdfFigureBuffer; ComPtr<ID3D11ShaderResourceView> m_sdfFigureView; ComPtr<ID3D11Buffer> m_sdfPrimBuffer; ComPtr<ID3D11ShaderResourceView> m_sdfPrimView; DecalSurface m_decalSurface; ComPtr<ID3D11Texture2D> m_decalTexture; ComPtr<ID3D11ShaderResourceView> m_decalView; std::vector<ac::xbox::Sprite> m_frameSprites; std::vector<ac::xbox::ThemeQuad> m_frameThemeQuads; 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<ID2D1Bitmap1> m_d2dOverlayTarget; 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<ID3D11DepthStencilView> m_triangleDepthReadOnly; ComPtr<ID3D11ShaderResourceView> m_triangleDepthRead; 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<ID3D11ShaderResourceView> m_themeViews[6]; ComPtr<ID3D11PixelShader> m_themePixelShader; ComPtr<ID3D11BlendState> m_themeBlendState; ComPtr<ID3D11DepthStencilState> m_themeSoftDepthState; 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::array<IXAudio2SourceVoice*, 4> m_glassVoices{}; std::array<IXAudio2SourceVoice*,4> m_chordVoices{}; std::array<std::vector<int16_t>,4> m_chordSamples; size_t m_nextChordVoice=0; std::array<IXAudio2SourceVoice*,12> m_drumVoices{}; std::array<std::vector<int16_t>,12> m_drumSamples; std::size_t m_nextDrumVoice=0; std::size_t m_nextGlassVoice = 0; std::vector<int16_t> m_glassSamples, m_glassShardSamples; IXAudio2SourceVoice* m_skateVoice = nullptr; std::vector<int16_t> m_skateSamples; 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; std::shared_ptr<OskiewarAccountService> m_oskiewarAccount;};
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;}