diff --git a/Data/Sys/Load/GraphicMods/Arc Rise Fantasia/RPJ.txt b/Data/Sys/Load/GraphicMods/Arc Rise Fantasia/RPJ.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Data/Sys/Load/GraphicMods/Arc Rise Fantasia/metadata.json b/Data/Sys/Load/GraphicMods/Arc Rise Fantasia/metadata.json new file mode 100644 index 0000000000..d1a072f611 --- /dev/null +++ b/Data/Sys/Load/GraphicMods/Arc Rise Fantasia/metadata.json @@ -0,0 +1,19 @@ +{ + "meta": + { + "title": "Bloom Texture Definitions", + "author": "iwubcode" + }, + "groups": + [ + { + "name": "Bloom", + "targets": [ + { + "type": "efb", + "texture_filename": "efb1_n33_160x112_6" + } + ] + } + ] +} \ No newline at end of file diff --git a/Data/Sys/Load/GraphicMods/Battallion Wars 2/RBW.txt b/Data/Sys/Load/GraphicMods/Battallion Wars 2/RBW.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Data/Sys/Load/GraphicMods/Battallion Wars 2/metadata.json b/Data/Sys/Load/GraphicMods/Battallion Wars 2/metadata.json new file mode 100644 index 0000000000..358266ac55 --- /dev/null +++ b/Data/Sys/Load/GraphicMods/Battallion Wars 2/metadata.json @@ -0,0 +1,19 @@ +{ + "meta": + { + "title": "Bloom Texture Definitions", + "author": "iwubcode" + }, + "groups": + [ + { + "name": "Bloom", + "targets": [ + { + "type": "efb", + "texture_filename": "efb1_n000023_80x60_6" + } + ] + } + ] +} \ No newline at end of file diff --git a/Data/Sys/Load/GraphicMods/Donkey Kong Country Returns/SF8.txt b/Data/Sys/Load/GraphicMods/Donkey Kong Country Returns/SF8.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Data/Sys/Load/GraphicMods/Donkey Kong Country Returns/metadata.json b/Data/Sys/Load/GraphicMods/Donkey Kong Country Returns/metadata.json new file mode 100644 index 0000000000..d07afa792d --- /dev/null +++ b/Data/Sys/Load/GraphicMods/Donkey Kong Country Returns/metadata.json @@ -0,0 +1,19 @@ +{ + "meta": + { + "title": "Bloom Texture Definitions", + "author": "iwubcode" + }, + "groups": + [ + { + "name": "Bloom", + "targets": [ + { + "type": "efb", + "texture_filename": "efb1_n2_320x224_4" + } + ] + } + ] +} \ No newline at end of file diff --git a/Data/Sys/Load/GraphicMods/Fragile Dreams - Farewell Ruins of the Moon/R2G.txt b/Data/Sys/Load/GraphicMods/Fragile Dreams - Farewell Ruins of the Moon/R2G.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Data/Sys/Load/GraphicMods/Fragile Dreams - Farewell Ruins of the Moon/metadata.json b/Data/Sys/Load/GraphicMods/Fragile Dreams - Farewell Ruins of the Moon/metadata.json new file mode 100644 index 0000000000..09d54d9344 --- /dev/null +++ b/Data/Sys/Load/GraphicMods/Fragile Dreams - Farewell Ruins of the Moon/metadata.json @@ -0,0 +1,47 @@ +{ + "meta": + { + "title": "HUD definitions", + "author": "iwubcode" + }, + "groups": [ + { + "name": "HUD", + "targets": [ + { + "type": "draw_started", + "prettyname": "map border", + "texture_filename": "tex1_180x180_6e5c9aa7004f377b_1" + }, + { + "type": "projection", + "value": "2d", + "prettyname": "map border", + "texture_filename": "tex1_180x180_6e5c9aa7004f377b_1" + }, + { + "type": "draw_started", + "prettyname": "map player marker", + "texture_filename": "tex1_64x64_f84b318707ee8455_1" + }, + { + "type": "projection", + "value": "2d", + "prettyname": "map player marker", + "texture_filename": "tex1_64x64_f84b318707ee8455_1" + }, + { + "type": "draw_started", + "prettyname": "map player facing pointer", + "texture_filename": "tex1_64x64_2ece13b26442de5a_0" + }, + { + "type": "projection", + "value": "2d", + "prettyname": "map player facing pointer", + "texture_filename": "tex1_64x64_2ece13b26442de5a_0" + } + ] + } + ] +} \ No newline at end of file diff --git a/Data/Sys/Load/GraphicMods/House of the Dead Overkill/RHO.txt b/Data/Sys/Load/GraphicMods/House of the Dead Overkill/RHO.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Data/Sys/Load/GraphicMods/House of the Dead Overkill/metadata.json b/Data/Sys/Load/GraphicMods/House of the Dead Overkill/metadata.json new file mode 100644 index 0000000000..63e06d60c4 --- /dev/null +++ b/Data/Sys/Load/GraphicMods/House of the Dead Overkill/metadata.json @@ -0,0 +1,19 @@ +{ + "meta": + { + "title": "Bloom Texture Definitions", + "author": "iwubcode" + }, + "groups": + [ + { + "name": "Bloom", + "targets": [ + { + "type": "efb", + "texture_filename": "efb1_n000008_80x60_6" + } + ] + } + ] +} \ No newline at end of file diff --git a/Data/Sys/Load/GraphicMods/Last Story/SLS.txt b/Data/Sys/Load/GraphicMods/Last Story/SLS.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Data/Sys/Load/GraphicMods/Last Story/metadata.json b/Data/Sys/Load/GraphicMods/Last Story/metadata.json new file mode 100644 index 0000000000..dca7f6f578 --- /dev/null +++ b/Data/Sys/Load/GraphicMods/Last Story/metadata.json @@ -0,0 +1,103 @@ +{ + "meta": + { + "title": "Bloom and HUD Texture Definitions", + "author": "iwubcode" + }, + "groups": + [ + { + "name": "HUD", + "targets": [ + { + "type": "draw_started", + "pretty_name": "faces", + "texture_filename": "tex1_512x256_ff742945bb1f5cd6_14" + }, + { + "type": "projection", + "value": "2d", + "pretty_name": "faces", + "texture_filename": "tex1_512x256_ff742945bb1f5cd6_14" + }, + { + "type": "draw_started", + "pretty_name": "text", + "texture_filename": "tex1_512x512_09ca8ef639199fa9_0" + }, + { + "type": "projection", + "value": "2d", + "pretty_name": "text", + "texture_filename": "tex1_512x512_09ca8ef639199fa9_0" + }, + { + "type": "draw_started", + "pretty_name": "text2", + "texture_filename": "tex1_512x512_79e54f19ff7811de_0" + }, + + { + "type": "projection", + "value": "2d", + "pretty_name": "text2", + "texture_filename": "tex1_512x512_79e54f19ff7811de_0" + }, + { + "type": "draw_started", + "pretty_name": "text3", + "texture_filename": "tex1_512x256_cb8c5f14fa63398f_0" + }, + + { + "type": "projection", + "value": "2d", + "pretty_name": "text3", + "texture_filename": "tex1_512x256_cb8c5f14fa63398f_0" + }, + { + "type": "draw_started", + "pretty_name": "large numbers", + "texture_filename": "tex1_128x256_5fd7e727abd256a9_0" + }, + { + "type": "projection", + "value": "2d", + "pretty_name": "large numbers", + "texture_filename": "tex1_128x256_5fd7e727abd256a9_0" + }, + { + "type": "draw_started", + "pretty_name": "life gradient", + "texture_filename": "tex1_128x64_e3c9e617a9fdf915_14" + }, + { + "type": "projection", + "value": "2d", + "pretty_name": "life gradient", + "texture_filename": "tex1_128x64_e3c9e617a9fdf915_14" + }, + { + "type": "draw_started", + "pretty_name": "life outline", + "texture_filename": "tex1_128x64_deeeaa33ca3dc0f1_14" + }, + { + "type": "projection", + "value": "2d", + "pretty_name": "life outline", + "texture_filename": "tex1_128x64_deeeaa33ca3dc0f1_14" + } + ] + }, + { + "name": "Bloom", + "targets": [ + { + "type": "efb", + "texture_filename": "efb1_n56_80x56_6" + } + ] + } + ] +} diff --git a/Data/Sys/Load/GraphicMods/Link's Crossbow Training/RZP.txt b/Data/Sys/Load/GraphicMods/Link's Crossbow Training/RZP.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Data/Sys/Load/GraphicMods/Link's Crossbow Training/metadata.json b/Data/Sys/Load/GraphicMods/Link's Crossbow Training/metadata.json new file mode 100644 index 0000000000..b31aaecf04 --- /dev/null +++ b/Data/Sys/Load/GraphicMods/Link's Crossbow Training/metadata.json @@ -0,0 +1,23 @@ +{ + "meta": + { + "title": "Bloom Texture Definitions", + "author": "iwubcode" + }, + "groups": + [ + { + "name": "Bloom", + "targets": [ + { + "type": "efb", + "texture_filename": "efb1_n23_80x57_6" + }, + { + "type": "efb", + "texture_filename": "efb1_n22_160x114_6" + } + ] + } + ] +} \ No newline at end of file diff --git a/Data/Sys/Load/GraphicMods/Little King's Story/RO3.txt b/Data/Sys/Load/GraphicMods/Little King's Story/RO3.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Data/Sys/Load/GraphicMods/Little King's Story/metadata.json b/Data/Sys/Load/GraphicMods/Little King's Story/metadata.json new file mode 100644 index 0000000000..3b1b552161 --- /dev/null +++ b/Data/Sys/Load/GraphicMods/Little King's Story/metadata.json @@ -0,0 +1,167 @@ +{ + "meta": + { + "title": "Bloom and HUD definitions", + "author": "iwubcode" + }, + "groups": [ + { + "name": "HUD", + "targets": [ + { + "type": "draw_started", + "texture_filename": "tex1_96x96_7b5b0f693c1200ad_5" + }, + { + "type": "draw_started", + "texture_filename": "tex1_40x48_b510b4434b7de70c_5" + }, + { + "type": "draw_started", + "texture_filename": "tex1_96x96_633c30835459df0f_5" + }, + { + "type": "draw_started", + "texture_filename": "tex1_24x68_715518a00c14e148_5" + }, + { + "type": "draw_started", + "texture_filename": "tex1_32x80_f310048c1139815d_14" + }, + { + "type": "draw_started", + "texture_filename": "tex1_12x16_1e9016c61dfffb7a_14" + }, + { + "type": "draw_started", + "texture_filename": "tex1_12x16_459c7d7576547909_14" + }, + { + "type": "draw_started", + "texture_filename": "tex1_12x16_d1b77f0000ff337a_14" + }, + { + "type": "draw_started", + "texture_filename": "tex1_20x16_798aee4dc7001432_14" + }, + { + "type": "draw_started", + "texture_filename": "tex1_108x122_911fa08f1554752c_5" + }, + { + "type": "draw_started", + "texture_filename": "tex1_64x64_96894941f5454ead_3" + }, + { + "type": "draw_started", + "texture_filename": "tex1_8x8_0017d44adaf2291b_14" + }, + { + "type": "draw_started", + "texture_filename": "tex1_10x8_7a6a869d5553c4a0_14" + }, + { + "type": "draw_started", + "texture_filename": "tex1_16x16_49c191eaf4314e9d_14" + }, + { + "type": "draw_started", + "texture_filename": "tex1_18x16_472b403cdcfc31a3_3" + }, + { + "type": "draw_started", + "texture_filename": "tex1_30x26_629956a45175e53a_14" + }, + { + "type": "projection", + "value": "2d", + "texture_filename": "tex1_96x96_7b5b0f693c1200ad_5" + }, + { + "type": "projection", + "value": "2d", + "texture_filename": "tex1_40x48_b510b4434b7de70c_5" + }, + { + "type": "projection", + "value": "2d", + "texture_filename": "tex1_96x96_633c30835459df0f_5" + }, + { + "type": "projection", + "value": "2d", + "texture_filename": "tex1_24x68_715518a00c14e148_5" + }, + { + "type": "projection", + "value": "2d", + "texture_filename": "tex1_32x80_f310048c1139815d_14" + }, + { + "type": "projection", + "value": "2d", + "texture_filename": "tex1_12x16_1e9016c61dfffb7a_14" + }, + { + "type": "projection", + "value": "2d", + "texture_filename": "tex1_12x16_459c7d7576547909_14" + }, + { + "type": "projection", + "value": "2d", + "texture_filename": "tex1_12x16_d1b77f0000ff337a_14" + }, + { + "type": "projection", + "value": "2d", + "texture_filename": "tex1_20x16_798aee4dc7001432_14" + }, + { + "type": "projection", + "value": "2d", + "texture_filename": "tex1_108x122_911fa08f1554752c_5" + }, + { + "type": "projection", + "value": "2d", + "texture_filename": "tex1_64x64_96894941f5454ead_3" + }, + { + "type": "projection", + "value": "2d", + "texture_filename": "tex1_8x8_0017d44adaf2291b_14" + }, + { + "type": "projection", + "value": "2d", + "texture_filename": "tex1_10x8_7a6a869d5553c4a0_14" + }, + { + "type": "projection", + "value": "2d", + "texture_filename": "tex1_16x16_49c191eaf4314e9d_14" + }, + { + "type": "projection", + "value": "2d", + "texture_filename": "tex1_18x16_472b403cdcfc31a3_3" + }, + { + "type": "projection", + "value": "2d", + "texture_filename": "tex1_30x26_629956a45175e53a_14" + } + ] + }, + { + "name": "Bloom", + "targets": [ + { + "type": "efb", + "texture_filename": "efb1_n1_320x240_6" + } + ] + } + ] +} \ No newline at end of file diff --git a/Data/Sys/Load/GraphicMods/Mario Kart Wii/RMC.txt b/Data/Sys/Load/GraphicMods/Mario Kart Wii/RMC.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Data/Sys/Load/GraphicMods/Mario Kart Wii/metadata.json b/Data/Sys/Load/GraphicMods/Mario Kart Wii/metadata.json new file mode 100644 index 0000000000..914ca97da0 --- /dev/null +++ b/Data/Sys/Load/GraphicMods/Mario Kart Wii/metadata.json @@ -0,0 +1,19 @@ +{ + "meta": + { + "title": "Bloom Texture Definitions", + "author": "iwubcode" + }, + "groups": + [ + { + "name": "Bloom", + "targets": [ + { + "type": "efb", + "texture_filename": "efb1_n49_152x114_6" + } + ] + } + ] +} \ No newline at end of file diff --git a/Data/Sys/Load/GraphicMods/Monster Hunter Tri - Demo/DMH.txt b/Data/Sys/Load/GraphicMods/Monster Hunter Tri - Demo/DMH.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Data/Sys/Load/GraphicMods/Monster Hunter Tri - Demo/metadata.json b/Data/Sys/Load/GraphicMods/Monster Hunter Tri - Demo/metadata.json new file mode 100644 index 0000000000..2401f8cf65 --- /dev/null +++ b/Data/Sys/Load/GraphicMods/Monster Hunter Tri - Demo/metadata.json @@ -0,0 +1,27 @@ +{ + "meta": + { + "title": "Bloom Texture Definitions", + "author": "iwubcode" + }, + "groups": + [ + { + "name": "Bloom", + "targets": [ + { + "type": "efb", + "texture_filename": "efb1_n3_80x56_6" + }, + { + "type": "efb", + "texture_filename": "efb1_n2_160x112_6" + }, + { + "type": "efb", + "texture_filename": "efb1_n6_320x224_6" + } + ] + } + ] +} \ No newline at end of file diff --git a/Data/Sys/Load/GraphicMods/Nights Journey of Dreams/R7E.txt b/Data/Sys/Load/GraphicMods/Nights Journey of Dreams/R7E.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Data/Sys/Load/GraphicMods/Nights Journey of Dreams/metadata.json b/Data/Sys/Load/GraphicMods/Nights Journey of Dreams/metadata.json new file mode 100644 index 0000000000..f234ad0dba --- /dev/null +++ b/Data/Sys/Load/GraphicMods/Nights Journey of Dreams/metadata.json @@ -0,0 +1,19 @@ +{ + "meta": + { + "title": "Bloom Texture Definitions", + "author": "iwubcode" + }, + "groups": + [ + { + "name": "Bloom", + "targets": [ + { + "type": "efb", + "texture_filename": "efb1_n000019_128x128_4" + } + ] + } + ] +} \ No newline at end of file diff --git a/Data/Sys/Load/GraphicMods/Okami/ROW.txt b/Data/Sys/Load/GraphicMods/Okami/ROW.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Data/Sys/Load/GraphicMods/Okami/metadata.json b/Data/Sys/Load/GraphicMods/Okami/metadata.json new file mode 100644 index 0000000000..3c93e5f88f --- /dev/null +++ b/Data/Sys/Load/GraphicMods/Okami/metadata.json @@ -0,0 +1,19 @@ +{ + "meta": + { + "title": "Bloom Texture Definitions", + "author": "iwubcode" + }, + "groups": + [ + { + "name": "Bloom", + "targets": [ + { + "type": "efb", + "texture_filename": "efb1_n51_320x240_6" + } + ] + } + ] +} \ No newline at end of file diff --git a/Data/Sys/Load/GraphicMods/Rune Factory Frontier/RUF.txt b/Data/Sys/Load/GraphicMods/Rune Factory Frontier/RUF.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Data/Sys/Load/GraphicMods/Rune Factory Frontier/metadata.json b/Data/Sys/Load/GraphicMods/Rune Factory Frontier/metadata.json new file mode 100644 index 0000000000..4c2407a892 --- /dev/null +++ b/Data/Sys/Load/GraphicMods/Rune Factory Frontier/metadata.json @@ -0,0 +1,103 @@ +{ + "meta": + { + "title": "Bloom and HUD Texture Definitions", + "author": "iwubcode" + }, + "groups": + [ + { + "name": "HUD", + "targets": [ + { + "type": "draw_started", + "pretty_name": "hp_rp", + "texture_filename": "tex1_200x64_29bf40765535b389_fb4403f0539ecfc6_9" + }, + { + "type": "projection", + "value": "2d", + "pretty_name": "hp_rp", + "texture_filename": "tex1_200x64_29bf40765535b389_fb4403f0539ecfc6_9" + }, + { + "type": "draw_started", + "pretty_name": "hp_gradient", + "texture_filename": "tex1_96x8_491977b196c249d8_1feafe410943bfac_8" + }, + { + "type": "projection", + "value": "2d", + "pretty_name": "hp_gradient", + "texture_filename": "tex1_96x8_491977b196c249d8_1feafe410943bfac_8" + }, + { + "type": "draw_started", + "pretty_name": "rp_gradient", + "texture_filename": "tex1_96x8_cdcb5c030686767c_2c5a8138bfca228c_8" + }, + { + "type": "projection", + "value": "2d", + "pretty_name": "rp_gradient", + "texture_filename": "tex1_96x8_cdcb5c030686767c_2c5a8138bfca228c_8" + }, + { + "type": "draw_started", + "pretty_name": "spring_season", + "texture_filename": "tex1_256x40_30d99f26895bc811_02e626cce31a83ae_9" + }, + { + "type": "projection", + "value": "2d", + "pretty_name": "spring_season", + "texture_filename": "tex1_256x40_30d99f26895bc811_02e626cce31a83ae_9" + }, + { + "type": "draw_started", + "pretty_name": "quick_pick_box", + "texture_filename": "tex1_128x128_b87c102764a80c67_0488ebdbd87cfc9d_9" + }, + { + "type": "projection", + "value": "2d", + "pretty_name": "quick_pick_box", + "texture_filename": "tex1_128x128_b87c102764a80c67_0488ebdbd87cfc9d_9" + }, + { + "type": "draw_started", + "pretty_name": "face", + "texture_filename": "tex1_48x48_92405f9277895cd2_914f5a4762aa04ae_9" + }, + { + "type": "projection", + "value": "2d", + "pretty_name": "face", + "texture_filename": "tex1_48x48_92405f9277895cd2_914f5a4762aa04ae_9" + }, + { + "type": "draw_started", + "pretty_name": "sunny_icon", + "texture_filename": "tex1_24x24_3791555ba7e8186f_e82e2316ceba262d_9" + }, + { + "type": "projection", + "value": "2d", + "pretty_name": "sunny_icon", + "texture_filename": "tex1_24x24_3791555ba7e8186f_e82e2316ceba262d_9" + }, + { + "type": "draw_started", + "pretty_name": "text", + "texture_filename": "tex1_256x256_83aa16840fa69ffb_0" + }, + { + "type": "projection", + "value": "2d", + "pretty_name": "text", + "texture_filename": "tex1_256x256_83aa16840fa69ffb_0" + } + ] + } + ] +} \ No newline at end of file diff --git a/Data/Sys/Load/GraphicMods/Super Mario Galaxy 2/SB4.txt b/Data/Sys/Load/GraphicMods/Super Mario Galaxy 2/SB4.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Data/Sys/Load/GraphicMods/Super Mario Galaxy 2/metadata.json b/Data/Sys/Load/GraphicMods/Super Mario Galaxy 2/metadata.json new file mode 100644 index 0000000000..5b641bfa49 --- /dev/null +++ b/Data/Sys/Load/GraphicMods/Super Mario Galaxy 2/metadata.json @@ -0,0 +1,27 @@ +{ + "meta": + { + "title": "Bloom Texture Definitions", + "author": "iwubcode" + }, + "groups": + [ + { + "name": "Bloom", + "targets": [ + { + "type": "efb", + "texture_filename": "efb1_n000031_80x57_4" + }, + { + "type": "efb", + "texture_filename": "efb1_n000033_160x114_4" + }, + { + "type": "efb", + "texture_filename": "efb1_n000038_320x228_4" + } + ] + } + ] +} \ No newline at end of file diff --git a/Data/Sys/Load/GraphicMods/Super Mario Strikers Charged/R4Q.txt b/Data/Sys/Load/GraphicMods/Super Mario Strikers Charged/R4Q.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Data/Sys/Load/GraphicMods/Super Mario Strikers Charged/metadata.json b/Data/Sys/Load/GraphicMods/Super Mario Strikers Charged/metadata.json new file mode 100644 index 0000000000..f67b9e6539 --- /dev/null +++ b/Data/Sys/Load/GraphicMods/Super Mario Strikers Charged/metadata.json @@ -0,0 +1,31 @@ +{ + "meta": + { + "title": "Bloom Texture Definitions", + "author": "iwubcode" + }, + "groups": + [ + { + "name": "Bloom", + "targets": [ + { + "type": "efb", + "texture_filename": "efb1_n001461_40x28_1" + }, + { + "type": "efb", + "texture_filename": "efb1_n001460_80x56_1" + }, + { + "type": "efb", + "texture_filename": "efb1_n001459_160x112_1" + }, + { + "type": "efb", + "texture_filename": "efb1_n001458_320x224_1" + } + ] + } + ] +} \ No newline at end of file diff --git a/Data/Sys/Load/GraphicMods/The Conduit/RCJ.txt b/Data/Sys/Load/GraphicMods/The Conduit/RCJ.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Data/Sys/Load/GraphicMods/The Conduit/metadata.json b/Data/Sys/Load/GraphicMods/The Conduit/metadata.json new file mode 100644 index 0000000000..ad9307d041 --- /dev/null +++ b/Data/Sys/Load/GraphicMods/The Conduit/metadata.json @@ -0,0 +1,31 @@ +{ + "meta": + { + "title": "Bloom Texture Definitions", + "author": "iwubcode" + }, + "groups": + [ + { + "name": "Bloom", + "targets": [ + { + "type": "efb", + "texture_filename": "efb1_n000022_40x28_6" + }, + { + "type": "efb", + "texture_filename": "efb1_n000021_80x56_6" + }, + { + "type": "efb", + "texture_filename": "efb1_n000020_160x112_6" + }, + { + "type": "efb", + "texture_filename": "efb1_n000025_320x224_6" + } + ] + } + ] +} \ No newline at end of file diff --git a/Data/Sys/Load/GraphicMods/The Legend of Zelda Twilight Princess/RZD.txt b/Data/Sys/Load/GraphicMods/The Legend of Zelda Twilight Princess/RZD.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Data/Sys/Load/GraphicMods/The Legend of Zelda Twilight Princess/metadata.json b/Data/Sys/Load/GraphicMods/The Legend of Zelda Twilight Princess/metadata.json new file mode 100644 index 0000000000..2ec7bce760 --- /dev/null +++ b/Data/Sys/Load/GraphicMods/The Legend of Zelda Twilight Princess/metadata.json @@ -0,0 +1,23 @@ +{ + "meta": + { + "title": "Bloom Texture Definitions", + "author": "iwubcode" + }, + "groups": + [ + { + "name": "Bloom", + "targets": [ + { + "type": "efb", + "texture_filename": "efb1_n55_80x57_6" + }, + { + "type": "efb", + "texture_filename": "efb1_n54_160x114_6" + } + ] + } + ] +} \ No newline at end of file diff --git a/Data/Sys/Load/GraphicMods/Xenoblade Chronicles/SX4.txt b/Data/Sys/Load/GraphicMods/Xenoblade Chronicles/SX4.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Data/Sys/Load/GraphicMods/Xenoblade Chronicles/metadata.json b/Data/Sys/Load/GraphicMods/Xenoblade Chronicles/metadata.json new file mode 100644 index 0000000000..9dc56f815c --- /dev/null +++ b/Data/Sys/Load/GraphicMods/Xenoblade Chronicles/metadata.json @@ -0,0 +1,31 @@ +{ + "meta": + { + "title": "Bloom Texture Definitions", + "author": "iwubcode" + }, + "groups": + [ + { + "name": "Bloom", + "targets": [ + { + "type": "efb", + "texture_filename": "efb1_n15_20x16_4" + }, + { + "type": "efb", + "texture_filename": "efb1_n9_40x30_4" + }, + { + "type": "efb", + "texture_filename": "efb1_n7_80x58_4" + }, + { + "type": "efb", + "texture_filename": "efb1_n1_320x228_4" + } + ] + } + ] +} \ No newline at end of file diff --git a/Source/Core/Common/CommonPaths.h b/Source/Core/Common/CommonPaths.h index 1482b9656c..306dbb78ff 100644 --- a/Source/Core/Common/CommonPaths.h +++ b/Source/Core/Common/CommonPaths.h @@ -72,6 +72,7 @@ #define BACKUP_DIR "Backup" #define RESOURCEPACK_DIR "ResourcePacks" #define DYNAMICINPUT_DIR "DynamicInputTextures" +#define GRAPHICSMOD_DIR "GraphicMods" // This one is only used to remove it if it was present #define SHADERCACHE_LEGACY_DIR "ShaderCache" @@ -137,3 +138,6 @@ // Subdirs in Sys #define GC_SYS_DIR "GC" #define WII_SYS_DIR "Wii" + +// Subdirs in Config +#define GRAPHICSMOD_CONFIG_DIR "GraphicMods" diff --git a/Source/Core/Common/FileUtil.cpp b/Source/Core/Common/FileUtil.cpp index 522d3028fa..c9dae68b60 100644 --- a/Source/Core/Common/FileUtil.cpp +++ b/Source/Core/Common/FileUtil.cpp @@ -967,6 +967,7 @@ static void RebuildUserDirectories(unsigned int dir_index) s_user_paths[D_BACKUP_IDX] = s_user_paths[D_USER_IDX] + BACKUP_DIR DIR_SEP; s_user_paths[D_RESOURCEPACK_IDX] = s_user_paths[D_USER_IDX] + RESOURCEPACK_DIR DIR_SEP; s_user_paths[D_DYNAMICINPUT_IDX] = s_user_paths[D_LOAD_IDX] + DYNAMICINPUT_DIR DIR_SEP; + s_user_paths[D_GRAPHICSMOD_IDX] = s_user_paths[D_LOAD_IDX] + GRAPHICSMOD_DIR DIR_SEP; s_user_paths[F_DOLPHINCONFIG_IDX] = s_user_paths[D_CONFIG_IDX] + DOLPHIN_CONFIG; s_user_paths[F_GCPADCONFIG_IDX] = s_user_paths[D_CONFIG_IDX] + GCPAD_CONFIG; s_user_paths[F_WIIPADCONFIG_IDX] = s_user_paths[D_CONFIG_IDX] + WIIPAD_CONFIG; @@ -1045,6 +1046,7 @@ static void RebuildUserDirectories(unsigned int dir_index) s_user_paths[D_HIRESTEXTURES_IDX] = s_user_paths[D_LOAD_IDX] + HIRES_TEXTURES_DIR DIR_SEP; s_user_paths[D_RIIVOLUTION_IDX] = s_user_paths[D_LOAD_IDX] + RIIVOLUTION_DIR DIR_SEP; s_user_paths[D_DYNAMICINPUT_IDX] = s_user_paths[D_LOAD_IDX] + DYNAMICINPUT_DIR DIR_SEP; + s_user_paths[D_GRAPHICSMOD_IDX] = s_user_paths[D_LOAD_IDX] + GRAPHICSMOD_DIR DIR_SEP; break; } } diff --git a/Source/Core/Common/FileUtil.h b/Source/Core/Common/FileUtil.h index 2672e237de..2b8d59ae2a 100644 --- a/Source/Core/Common/FileUtil.h +++ b/Source/Core/Common/FileUtil.h @@ -60,6 +60,7 @@ enum D_BACKUP_IDX, D_RESOURCEPACK_IDX, D_DYNAMICINPUT_IDX, + D_GRAPHICSMOD_IDX, D_GBAUSER_IDX, D_GBASAVES_IDX, FIRST_FILE_USER_PATH_IDX, diff --git a/Source/Core/Core/Config/GraphicsSettings.cpp b/Source/Core/Core/Config/GraphicsSettings.cpp index b5b4f369f8..4bbcf99413 100644 --- a/Source/Core/Core/Config/GraphicsSettings.cpp +++ b/Source/Core/Core/Config/GraphicsSettings.cpp @@ -101,6 +101,8 @@ const Info GFX_SW_DRAW_END{{System::GFX, "Settings", "SWDrawEnd"}, 100000}; const Info GFX_PREFER_GLES{{System::GFX, "Settings", "PreferGLES"}, false}; +const Info GFX_MODS_ENABLE{{System::GFX, "Settings", "EnableMods"}, false}; + // Graphics.Enhancements const Info GFX_ENHANCE_FORCE_FILTERING{{System::GFX, "Enhancements", "ForceFiltering"}, diff --git a/Source/Core/Core/Config/GraphicsSettings.h b/Source/Core/Core/Config/GraphicsSettings.h index 6895501cb0..cdac08a77b 100644 --- a/Source/Core/Core/Config/GraphicsSettings.h +++ b/Source/Core/Core/Config/GraphicsSettings.h @@ -82,6 +82,8 @@ extern const Info GFX_SW_DRAW_END; extern const Info GFX_PREFER_GLES; +extern const Info GFX_MODS_ENABLE; + // Graphics.Enhancements extern const Info GFX_ENHANCE_FORCE_FILTERING; diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index c1635eb6fb..ca81c1ff48 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -631,6 +631,21 @@ + + + + + + + + + + + + + + + @@ -1209,6 +1224,18 @@ + + + + + + + + + + + + diff --git a/Source/Core/DolphinQt/CMakeLists.txt b/Source/Core/DolphinQt/CMakeLists.txt index 585cf62a3e..0e08a4ed98 100644 --- a/Source/Core/DolphinQt/CMakeLists.txt +++ b/Source/Core/DolphinQt/CMakeLists.txt @@ -129,6 +129,10 @@ add_executable(dolphin-emu Config/Graphics/PostProcessingConfigWindow.h Config/Graphics/SoftwareRendererWidget.cpp Config/Graphics/SoftwareRendererWidget.h + Config/GraphicsModListWidget.cpp + Config/GraphicsModListWidget.h + Config/GraphicsModWarningWidget.cpp + Config/GraphicsModWarningWidget.h Config/InfoWidget.cpp Config/InfoWidget.h Config/LogConfigWidget.cpp diff --git a/Source/Core/DolphinQt/Config/Graphics/AdvancedWidget.cpp b/Source/Core/DolphinQt/Config/Graphics/AdvancedWidget.cpp index e604f7277a..7a2570f97c 100644 --- a/Source/Core/DolphinQt/Config/Graphics/AdvancedWidget.cpp +++ b/Source/Core/DolphinQt/Config/Graphics/AdvancedWidget.cpp @@ -20,6 +20,7 @@ #include "DolphinQt/Config/Graphics/GraphicsInteger.h" #include "DolphinQt/Config/Graphics/GraphicsWindow.h" #include "DolphinQt/Config/ToolTipControls/ToolTipCheckBox.h" +#include "DolphinQt/QtUtils/SignalBlocking.h" #include "DolphinQt/Settings.h" #include "VideoCommon/VideoConfig.h" @@ -72,13 +73,15 @@ void AdvancedWidget::CreateWidgets() m_dump_xfb_target = new GraphicsBool(tr("Dump XFB Target"), Config::GFX_DUMP_XFB_TARGET); m_disable_vram_copies = new GraphicsBool(tr("Disable EFB VRAM Copies"), Config::GFX_HACK_DISABLE_COPY_TO_VRAM); + m_enable_graphics_mods = new ToolTipCheckBox(tr("Enable Graphics Mods")); utility_layout->addWidget(m_load_custom_textures, 0, 0); utility_layout->addWidget(m_prefetch_custom_textures, 0, 1); utility_layout->addWidget(m_disable_vram_copies, 1, 0); + utility_layout->addWidget(m_enable_graphics_mods, 1, 1); - utility_layout->addWidget(m_dump_efb_target, 1, 1); + utility_layout->addWidget(m_dump_efb_target, 2, 0); utility_layout->addWidget(m_dump_xfb_target, 2, 1); // Texture dumping @@ -165,6 +168,7 @@ void AdvancedWidget::ConnectWidgets() connect(m_dump_use_ffv1, &QCheckBox::toggled, this, &AdvancedWidget::SaveSettings); connect(m_enable_prog_scan, &QCheckBox::toggled, this, &AdvancedWidget::SaveSettings); connect(m_dump_textures, &QCheckBox::toggled, this, &AdvancedWidget::SaveSettings); + connect(m_enable_graphics_mods, &QCheckBox::toggled, this, &AdvancedWidget::SaveSettings); } void AdvancedWidget::LoadSettings() @@ -175,6 +179,8 @@ void AdvancedWidget::LoadSettings() m_enable_prog_scan->setChecked(Config::Get(Config::SYSCONF_PROGRESSIVE_SCAN)); m_dump_mip_textures->setEnabled(Config::Get(Config::GFX_DUMP_TEXTURES)); m_dump_base_textures->setEnabled(Config::Get(Config::GFX_DUMP_TEXTURES)); + + SignalBlocking(m_enable_graphics_mods)->setChecked(Settings::Instance().GetGraphicModsEnabled()); } void AdvancedWidget::SaveSettings() @@ -185,6 +191,7 @@ void AdvancedWidget::SaveSettings() Config::SetBase(Config::SYSCONF_PROGRESSIVE_SCAN, m_enable_prog_scan->isChecked()); m_dump_mip_textures->setEnabled(Config::Get(Config::GFX_DUMP_TEXTURES)); m_dump_base_textures->setEnabled(Config::Get(Config::GFX_DUMP_TEXTURES)); + Settings::Instance().SetGraphicModsEnabled(m_enable_graphics_mods->isChecked()); } void AdvancedWidget::OnBackendChanged() @@ -245,6 +252,9 @@ void AdvancedWidget::AddDescriptions() QT_TR_NOOP("Disables the VRAM copy of the EFB, forcing a round-trip to RAM. Inhibits all " "upscaling.

If unsure, leave this " "unchecked."); + static const char TR_LOAD_GRAPHICS_MODS_DESCRIPTION[] = + QT_TR_NOOP("Loads graphics mods from User/Load/GraphicsMods/.

If " + "unsure, leave this unchecked."); static const char TR_INTERNAL_RESOLUTION_FRAME_DUMPING_DESCRIPTION[] = QT_TR_NOOP( "Creates frame dumps and screenshots at the internal resolution of the renderer, rather than " "the size of the window it is displayed within.

If the aspect ratio is widescreen, " @@ -316,6 +326,7 @@ void AdvancedWidget::AddDescriptions() m_dump_efb_target->SetDescription(tr(TR_DUMP_EFB_DESCRIPTION)); m_dump_xfb_target->SetDescription(tr(TR_DUMP_XFB_DESCRIPTION)); m_disable_vram_copies->SetDescription(tr(TR_DISABLE_VRAM_COPIES_DESCRIPTION)); + m_enable_graphics_mods->SetDescription(tr(TR_LOAD_GRAPHICS_MODS_DESCRIPTION)); m_use_fullres_framedumps->SetDescription(tr(TR_INTERNAL_RESOLUTION_FRAME_DUMPING_DESCRIPTION)); #ifdef HAVE_FFMPEG m_dump_use_ffv1->SetDescription(tr(TR_USE_FFV1_DESCRIPTION)); diff --git a/Source/Core/DolphinQt/Config/Graphics/AdvancedWidget.h b/Source/Core/DolphinQt/Config/Graphics/AdvancedWidget.h index 1148a2df1b..4530db5fcf 100644 --- a/Source/Core/DolphinQt/Config/Graphics/AdvancedWidget.h +++ b/Source/Core/DolphinQt/Config/Graphics/AdvancedWidget.h @@ -42,6 +42,7 @@ private: GraphicsBool* m_dump_xfb_target; GraphicsBool* m_disable_vram_copies; GraphicsBool* m_load_custom_textures; + ToolTipCheckBox* m_enable_graphics_mods; // Texture dumping GraphicsBool* m_dump_textures; diff --git a/Source/Core/DolphinQt/Config/GraphicsModListWidget.cpp b/Source/Core/DolphinQt/Config/GraphicsModListWidget.cpp new file mode 100644 index 0000000000..712a18a558 --- /dev/null +++ b/Source/Core/DolphinQt/Config/GraphicsModListWidget.cpp @@ -0,0 +1,279 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "DolphinQt/Config/GraphicsModListWidget.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "Core/ConfigManager.h" +#include "Core/Core.h" +#include "DolphinQt/Config/GraphicsModWarningWidget.h" +#include "DolphinQt/Settings.h" +#include "UICommon/GameFile.h" +#include "VideoCommon/GraphicsModSystem/Config/GraphicsMod.h" +#include "VideoCommon/VideoConfig.h" + +GraphicsModListWidget::GraphicsModListWidget(const UICommon::GameFile& game) + : m_game_id(game.GetGameID()), m_mod_group(m_game_id) +{ + CalculateGameRunning(Core::GetState()); + if (m_loaded_game_is_running && g_Config.graphics_mod_config) + { + m_mod_group.SetChangeCount(g_Config.graphics_mod_config->GetChangeCount()); + } + CreateWidgets(); + ConnectWidgets(); + + RefreshModList(); + OnModChanged(std::nullopt); +} + +GraphicsModListWidget::~GraphicsModListWidget() +{ + if (m_needs_save) + { + m_mod_group.Save(); + } +} + +void GraphicsModListWidget::CreateWidgets() +{ + auto* main_v_layout = new QVBoxLayout(this); + + auto* main_layout = new QHBoxLayout; + + auto* left_v_layout = new QVBoxLayout; + + m_mod_list = new QListWidget; + m_mod_list->setSortingEnabled(false); + m_mod_list->setSelectionBehavior(QAbstractItemView::SelectionBehavior::SelectItems); + m_mod_list->setSelectionMode(QAbstractItemView::SelectionMode::SingleSelection); + m_mod_list->setSelectionRectVisible(true); + m_mod_list->setDragDropMode(QAbstractItemView::InternalMove); + + m_refresh = new QPushButton(tr("&Refresh List")); + QHBoxLayout* hlayout = new QHBoxLayout; + hlayout->addStretch(); + hlayout->addWidget(m_refresh); + + left_v_layout->addWidget(m_mod_list); + left_v_layout->addLayout(hlayout); + + auto* right_v_layout = new QVBoxLayout; + + m_selected_mod_name = new QLabel(); + right_v_layout->addWidget(m_selected_mod_name); + + m_mod_meta_layout = new QVBoxLayout; + right_v_layout->addLayout(m_mod_meta_layout); + right_v_layout->addStretch(); + + main_layout->addLayout(left_v_layout); + main_layout->addLayout(right_v_layout, 1); + + m_warning = new GraphicsModWarningWidget(this); + main_v_layout->addWidget(m_warning); + main_v_layout->addLayout(main_layout); + + setLayout(main_v_layout); +} + +void GraphicsModListWidget::ConnectWidgets() +{ + connect(m_warning, &GraphicsModWarningWidget::GraphicsModEnableSettings, this, + &GraphicsModListWidget::OpenGraphicsSettings); + + connect(m_mod_list, &QListWidget::itemSelectionChanged, this, + &GraphicsModListWidget::ModSelectionChanged); + + connect(m_mod_list, &QListWidget::itemChanged, this, &GraphicsModListWidget::ModItemChanged); + + connect(m_mod_list->model(), &QAbstractItemModel::rowsMoved, this, + &GraphicsModListWidget::SaveModList); + + connect(m_refresh, &QPushButton::clicked, this, &GraphicsModListWidget::RefreshModList); + + connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, + &GraphicsModListWidget::CalculateGameRunning); +} + +void GraphicsModListWidget::RefreshModList() +{ + m_mod_list->setCurrentItem(nullptr); + m_mod_list->clear(); + + m_mod_group = GraphicsModGroupConfig(m_game_id); + m_mod_group.Load(); + + std::set groups; + + for (const auto& mod : m_mod_group.GetMods()) + { + if (mod.m_groups.empty()) + continue; + + for (const auto& group : mod.m_groups) + { + groups.insert(group.m_name); + } + } + + for (const auto& mod : m_mod_group.GetMods()) + { + // Group only mods shouldn't be shown + if (mod.m_features.empty()) + continue; + + // If the group doesn't exist in the available mod's features, skip + if (std::none_of(mod.m_features.begin(), mod.m_features.end(), + [&groups](const GraphicsModFeatureConfig& feature) { + return groups.count(feature.m_group) == 1; + })) + { + continue; + } + + QListWidgetItem* item = new QListWidgetItem(QString::fromStdString(mod.m_title)); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + item->setData(Qt::UserRole, QString::fromStdString(mod.GetAbsolutePath())); + item->setCheckState(mod.m_enabled ? Qt::Checked : Qt::Unchecked); + + m_mod_list->addItem(item); + } +} + +void GraphicsModListWidget::ModSelectionChanged() +{ + if (m_mod_list->currentItem() == nullptr) + return; + if (m_mod_list->count() == 0) + return; + const auto absolute_path = m_mod_list->currentItem()->data(Qt::UserRole).toString().toStdString(); + OnModChanged(absolute_path); +} + +void GraphicsModListWidget::ModItemChanged(QListWidgetItem* item) +{ + const auto absolute_path = item->data(Qt::UserRole).toString(); + GraphicsModConfig* mod = m_mod_group.GetMod(absolute_path.toStdString()); + if (!mod) + return; + + const bool was_enabled = mod->m_enabled; + const bool should_enable = item->checkState() == Qt::Checked; + mod->m_enabled = should_enable; + if (was_enabled == should_enable) + return; + + m_mod_group.SetChangeCount(m_mod_group.GetChangeCount() + 1); + if (m_loaded_game_is_running) + { + g_Config.graphics_mod_config = m_mod_group; + } + m_needs_save = true; +} + +void GraphicsModListWidget::OnModChanged(std::optional absolute_path) +{ + ClearLayoutRecursively(m_mod_meta_layout); + + adjustSize(); + + if (!absolute_path) + { + m_selected_mod_name->setText(QStringLiteral("No graphics mod selected")); + m_selected_mod_name->setAlignment(Qt::AlignCenter); + return; + } + + GraphicsModConfig* mod = m_mod_group.GetMod(*absolute_path); + if (!mod) + return; + + m_selected_mod_name->setText(QString::fromStdString(mod->m_title)); + m_selected_mod_name->setAlignment(Qt::AlignLeft); + QFont font = m_selected_mod_name->font(); + font.setWeight(QFont::Bold); + m_selected_mod_name->setFont(font); + + if (!mod->m_author.empty()) + { + auto* author_label = new QLabel(tr("By: ") + QString::fromStdString(mod->m_author)); + m_mod_meta_layout->addWidget(author_label); + } + + if (!mod->m_description.empty()) + { + auto* description_label = + new QLabel(tr("Description: ") + QString::fromStdString(mod->m_description)); + m_mod_meta_layout->addWidget(description_label); + } +} + +void GraphicsModListWidget::SaveModList() +{ + for (int i = 0; i < m_mod_list->count(); i++) + { + const auto absolute_path = m_mod_list->model() + ->data(m_mod_list->model()->index(i, 0), Qt::UserRole) + .toString() + .toStdString(); + m_mod_group.GetMod(absolute_path)->m_weight = i; + } + + if (m_loaded_game_is_running) + { + g_Config.graphics_mod_config = m_mod_group; + } + m_needs_save = true; +} + +void GraphicsModListWidget::ClearLayoutRecursively(QLayout* layout) +{ + while (QLayoutItem* child = layout->takeAt(0)) + { + if (child == nullptr) + continue; + + if (child->widget()) + { + layout->removeWidget(child->widget()); + delete child->widget(); + } + else if (child->layout()) + { + ClearLayoutRecursively(child->layout()); + layout->removeItem(child); + } + else + { + layout->removeItem(child); + } + delete child; + } +} + +void GraphicsModListWidget::SaveToDisk() +{ + m_needs_save = false; + m_mod_group.Save(); +} + +const GraphicsModGroupConfig& GraphicsModListWidget::GetGraphicsModConfig() const +{ + return m_mod_group; +} + +void GraphicsModListWidget::CalculateGameRunning(Core::State state) +{ + m_loaded_game_is_running = + state == Core::State::Running ? m_game_id == SConfig::GetInstance().GetGameID() : false; +} diff --git a/Source/Core/DolphinQt/Config/GraphicsModListWidget.h b/Source/Core/DolphinQt/Config/GraphicsModListWidget.h new file mode 100644 index 0000000000..b2ea082f12 --- /dev/null +++ b/Source/Core/DolphinQt/Config/GraphicsModListWidget.h @@ -0,0 +1,75 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include + +#include "Common/CommonTypes.h" +#include "VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.h" + +class GraphicsModWarningWidget; +class QHBoxLayout; +class QLabel; +class QListWidget; +class QListWidgetItem; +class QModelIndex; +class QPushButton; +class QVBoxLayout; + +namespace Core +{ +enum class State; +} + +namespace UICommon +{ +class GameFile; +} + +class GraphicsModListWidget : public QWidget +{ + Q_OBJECT +public: + explicit GraphicsModListWidget(const UICommon::GameFile& game); + ~GraphicsModListWidget(); + + void SaveToDisk(); + + const GraphicsModGroupConfig& GetGraphicsModConfig() const; + +signals: + void OpenGraphicsSettings(); + +private: + void CreateWidgets(); + void ConnectWidgets(); + + void RefreshModList(); + void ModSelectionChanged(); + void ModItemChanged(QListWidgetItem* item); + + void OnModChanged(std::optional absolute_path); + + void SaveModList(); + + void ClearLayoutRecursively(QLayout* layout); + + void CalculateGameRunning(Core::State state); + bool m_loaded_game_is_running = false; + bool m_needs_save = false; + + QListWidget* m_mod_list; + + QLabel* m_selected_mod_name; + QVBoxLayout* m_mod_meta_layout; + + QPushButton* m_refresh; + GraphicsModWarningWidget* m_warning; + + std::string m_game_id; + GraphicsModGroupConfig m_mod_group; +}; diff --git a/Source/Core/DolphinQt/Config/GraphicsModWarningWidget.cpp b/Source/Core/DolphinQt/Config/GraphicsModWarningWidget.cpp new file mode 100644 index 0000000000..281836073c --- /dev/null +++ b/Source/Core/DolphinQt/Config/GraphicsModWarningWidget.cpp @@ -0,0 +1,70 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "DolphinQt/Config/GraphicsModWarningWidget.h" + +#include +#include +#include +#include +#include + +#include "DolphinQt/Settings.h" + +GraphicsModWarningWidget::GraphicsModWarningWidget(QWidget* parent) : QWidget(parent) +{ + CreateWidgets(); + ConnectWidgets(); + + connect(&Settings::Instance(), &Settings::EnableGfxModsChanged, this, + &GraphicsModWarningWidget::Update); + Update(); +} + +void GraphicsModWarningWidget::CreateWidgets() +{ + auto* icon = new QLabel; + + const auto size = 1.5 * QFontMetrics(font()).height(); + + QPixmap warning_icon = style()->standardIcon(QStyle::SP_MessageBoxWarning).pixmap(size, size); + + icon->setPixmap(warning_icon); + + m_text = new QLabel(); + m_config_button = new QPushButton(tr("Configure Dolphin")); + + m_config_button->setHidden(true); + + auto* layout = new QHBoxLayout; + + layout->addWidget(icon); + layout->addWidget(m_text, 1); + layout->addWidget(m_config_button); + + layout->setContentsMargins(0, 0, 0, 0); + + setLayout(layout); +} + +void GraphicsModWarningWidget::Update() +{ + bool hide_widget = true; + bool hide_config_button = true; + + if (!Settings::Instance().GetGraphicModsEnabled()) + { + hide_widget = false; + hide_config_button = false; + m_text->setText(tr("Graphics mods are currently disabled.")); + } + + setHidden(hide_widget); + m_config_button->setHidden(hide_config_button); +} + +void GraphicsModWarningWidget::ConnectWidgets() +{ + connect(m_config_button, &QPushButton::clicked, this, + &GraphicsModWarningWidget::GraphicsModEnableSettings); +} diff --git a/Source/Core/DolphinQt/Config/GraphicsModWarningWidget.h b/Source/Core/DolphinQt/Config/GraphicsModWarningWidget.h new file mode 100644 index 0000000000..964ba0f862 --- /dev/null +++ b/Source/Core/DolphinQt/Config/GraphicsModWarningWidget.h @@ -0,0 +1,28 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +class QLabel; +class QPushButton; + +class GraphicsModWarningWidget final : public QWidget +{ + Q_OBJECT +public: + explicit GraphicsModWarningWidget(QWidget* parent); + +signals: + void GraphicsModEnableSettings(); + +private: + void CreateWidgets(); + void ConnectWidgets(); + + void Update(); + + QLabel* m_text; + QPushButton* m_config_button; +}; diff --git a/Source/Core/DolphinQt/Config/PropertiesDialog.cpp b/Source/Core/DolphinQt/Config/PropertiesDialog.cpp index a7589a4fc1..b72e950862 100644 --- a/Source/Core/DolphinQt/Config/PropertiesDialog.cpp +++ b/Source/Core/DolphinQt/Config/PropertiesDialog.cpp @@ -17,12 +17,14 @@ #include "DolphinQt/Config/FilesystemWidget.h" #include "DolphinQt/Config/GameConfigWidget.h" #include "DolphinQt/Config/GeckoCodeWidget.h" +#include "DolphinQt/Config/GraphicsModListWidget.h" #include "DolphinQt/Config/InfoWidget.h" #include "DolphinQt/Config/PatchesWidget.h" #include "DolphinQt/Config/VerifyWidget.h" #include "DolphinQt/QtUtils/WrapInScrollArea.h" #include "UICommon/GameFile.h" +#include "VideoCommon/VideoConfig.h" PropertiesDialog::PropertiesDialog(QWidget* parent, const UICommon::GameFile& game) : QDialog(parent) @@ -43,12 +45,16 @@ PropertiesDialog::PropertiesDialog(QWidget* parent, const UICommon::GameFile& ga new GeckoCodeWidget(game.GetGameID(), game.GetGameTDBID(), game.GetRevision()); PatchesWidget* patches = new PatchesWidget(game); GameConfigWidget* game_config = new GameConfigWidget(game); + GraphicsModListWidget* graphics_mod_list = new GraphicsModListWidget(game); connect(gecko, &GeckoCodeWidget::OpenGeneralSettings, this, &PropertiesDialog::OpenGeneralSettings); connect(ar, &ARCodeWidget::OpenGeneralSettings, this, &PropertiesDialog::OpenGeneralSettings); + connect(graphics_mod_list, &GraphicsModListWidget::OpenGraphicsSettings, this, + &PropertiesDialog::OpenGraphicsSettings); + const int padding_width = 120; const int padding_height = 100; tab_widget->addTab(GetWrappedWidget(game_config, this, padding_width, padding_height), @@ -57,6 +63,8 @@ PropertiesDialog::PropertiesDialog(QWidget* parent, const UICommon::GameFile& ga tab_widget->addTab(GetWrappedWidget(ar, this, padding_width, padding_height), tr("AR Codes")); tab_widget->addTab(GetWrappedWidget(gecko, this, padding_width, padding_height), tr("Gecko Codes")); + tab_widget->addTab(GetWrappedWidget(graphics_mod_list, this, padding_width, padding_height), + tr("Graphics Mods")); tab_widget->addTab(GetWrappedWidget(info, this, padding_width, padding_height), tr("Info")); if (game.GetPlatform() != DiscIO::Platform::ELFOrDOL) @@ -82,6 +90,8 @@ PropertiesDialog::PropertiesDialog(QWidget* parent, const UICommon::GameFile& ga QDialogButtonBox* close_box = new QDialogButtonBox(QDialogButtonBox::Close); connect(close_box, &QDialogButtonBox::rejected, this, &QDialog::reject); + connect(close_box, &QDialogButtonBox::rejected, graphics_mod_list, + &GraphicsModListWidget::SaveToDisk); layout->addWidget(close_box); diff --git a/Source/Core/DolphinQt/Config/PropertiesDialog.h b/Source/Core/DolphinQt/Config/PropertiesDialog.h index b31dbbb391..2135be8948 100644 --- a/Source/Core/DolphinQt/Config/PropertiesDialog.h +++ b/Source/Core/DolphinQt/Config/PropertiesDialog.h @@ -18,4 +18,5 @@ public: signals: void OpenGeneralSettings(); + void OpenGraphicsSettings(); }; diff --git a/Source/Core/DolphinQt/DolphinQt.vcxproj b/Source/Core/DolphinQt/DolphinQt.vcxproj index e6df0c3122..cde2161e7b 100644 --- a/Source/Core/DolphinQt/DolphinQt.vcxproj +++ b/Source/Core/DolphinQt/DolphinQt.vcxproj @@ -76,6 +76,8 @@ + + @@ -270,6 +272,8 @@ + + diff --git a/Source/Core/DolphinQt/GameList/GameList.cpp b/Source/Core/DolphinQt/GameList/GameList.cpp index b2fdb46df2..abbe3b5ca8 100644 --- a/Source/Core/DolphinQt/GameList/GameList.cpp +++ b/Source/Core/DolphinQt/GameList/GameList.cpp @@ -540,6 +540,8 @@ void GameList::OpenProperties() properties->setAttribute(Qt::WA_DeleteOnClose, true); connect(properties, &PropertiesDialog::OpenGeneralSettings, this, &GameList::OpenGeneralSettings); + connect(properties, &PropertiesDialog::OpenGraphicsSettings, this, + &GameList::OpenGraphicsSettings); properties->show(); } diff --git a/Source/Core/DolphinQt/GameList/GameList.h b/Source/Core/DolphinQt/GameList/GameList.h index e65f2e06e5..ebe2ec7152 100644 --- a/Source/Core/DolphinQt/GameList/GameList.h +++ b/Source/Core/DolphinQt/GameList/GameList.h @@ -55,6 +55,7 @@ signals: void NetPlayHost(const UICommon::GameFile& game); void SelectionChanged(std::shared_ptr game_file); void OpenGeneralSettings(); + void OpenGraphicsSettings(); private: void ShowHeaderContextMenu(const QPoint& pos); diff --git a/Source/Core/DolphinQt/MainWindow.cpp b/Source/Core/DolphinQt/MainWindow.cpp index d2dea76787..dab8f45c24 100644 --- a/Source/Core/DolphinQt/MainWindow.cpp +++ b/Source/Core/DolphinQt/MainWindow.cpp @@ -672,6 +672,7 @@ void MainWindow::ConnectGameList() &MainWindow::ShowRiivolutionBootWidget); connect(m_game_list, &GameList::OpenGeneralSettings, this, &MainWindow::ShowGeneralWindow); + connect(m_game_list, &GameList::OpenGraphicsSettings, this, &MainWindow::ShowGraphicsWindow); } void MainWindow::ConnectRenderWidget() diff --git a/Source/Core/DolphinQt/Settings.cpp b/Source/Core/DolphinQt/Settings.cpp index e08e32c0ad..a6580acbaa 100644 --- a/Source/Core/DolphinQt/Settings.cpp +++ b/Source/Core/DolphinQt/Settings.cpp @@ -29,6 +29,7 @@ #include "Common/FileUtil.h" #include "Common/StringUtil.h" +#include "Core/Config/GraphicsSettings.h" #include "Core/Config/MainSettings.h" #include "Core/ConfigManager.h" #include "Core/Core.h" @@ -351,6 +352,22 @@ bool Settings::IsKeepWindowOnTopEnabled() const return Config::Get(Config::MAIN_KEEP_WINDOW_ON_TOP); } +bool Settings::GetGraphicModsEnabled() const +{ + return Config::Get(Config::GFX_MODS_ENABLE); +} + +void Settings::SetGraphicModsEnabled(bool enabled) +{ + if (GetGraphicModsEnabled() == enabled) + { + return; + } + + Config::SetBaseOrCurrent(Config::GFX_MODS_ENABLE, enabled); + emit EnableGfxModsChanged(enabled); +} + int Settings::GetVolume() const { return Config::Get(Config::MAIN_AUDIO_VOLUME); diff --git a/Source/Core/DolphinQt/Settings.h b/Source/Core/DolphinQt/Settings.h index 335cef9a76..30ab757753 100644 --- a/Source/Core/DolphinQt/Settings.h +++ b/Source/Core/DolphinQt/Settings.h @@ -108,6 +108,8 @@ public: bool GetLockCursor() const; void SetKeepWindowOnTop(bool top); bool IsKeepWindowOnTopEnabled() const; + bool GetGraphicModsEnabled() const; + void SetGraphicModsEnabled(bool enabled); // Audio int GetVolume() const; @@ -200,6 +202,7 @@ signals: void DevicesChanged(); void SDCardInsertionChanged(bool inserted); void USBKeyboardConnectionChanged(bool connected); + void EnableGfxModsChanged(bool enabled); private: Settings(); diff --git a/Source/Core/UICommon/UICommon.cpp b/Source/Core/UICommon/UICommon.cpp index cb857fd3c7..1eb76d6b8b 100644 --- a/Source/Core/UICommon/UICommon.cpp +++ b/Source/Core/UICommon/UICommon.cpp @@ -69,6 +69,7 @@ static void CreateLoadPath(std::string path) File::SetUserPath(D_LOAD_IDX, std::move(path)); File::CreateFullPath(File::GetUserPath(D_HIRESTEXTURES_IDX)); File::CreateFullPath(File::GetUserPath(D_RIIVOLUTION_IDX)); + File::CreateFullPath(File::GetUserPath(D_GRAPHICSMOD_IDX)); } static void CreateResourcePackPath(std::string path) @@ -188,6 +189,7 @@ void CreateDirectories() File::CreateFullPath(File::GetUserPath(D_CACHE_IDX)); File::CreateFullPath(File::GetUserPath(D_COVERCACHE_IDX)); File::CreateFullPath(File::GetUserPath(D_CONFIG_IDX)); + File::CreateFullPath(File::GetUserPath(D_CONFIG_IDX) + GRAPHICSMOD_CONFIG_DIR DIR_SEP); File::CreateFullPath(File::GetUserPath(D_DUMPDSP_IDX)); File::CreateFullPath(File::GetUserPath(D_DUMPSSL_IDX)); File::CreateFullPath(File::GetUserPath(D_DUMPTEXTURES_IDX)); @@ -197,6 +199,7 @@ void CreateDirectories() File::CreateFullPath(File::GetUserPath(D_GCUSER_IDX) + EUR_DIR DIR_SEP); File::CreateFullPath(File::GetUserPath(D_GCUSER_IDX) + JAP_DIR DIR_SEP); File::CreateFullPath(File::GetUserPath(D_HIRESTEXTURES_IDX)); + File::CreateFullPath(File::GetUserPath(D_GRAPHICSMOD_IDX)); File::CreateFullPath(File::GetUserPath(D_MAILLOGS_IDX)); File::CreateFullPath(File::GetUserPath(D_MAPS_IDX)); File::CreateFullPath(File::GetUserPath(D_SCREENSHOTS_IDX)); diff --git a/Source/Core/VideoCommon/CMakeLists.txt b/Source/Core/VideoCommon/CMakeLists.txt index ec8f89c829..b46ac98d77 100644 --- a/Source/Core/VideoCommon/CMakeLists.txt +++ b/Source/Core/VideoCommon/CMakeLists.txt @@ -39,6 +39,32 @@ add_library(videocommon GeometryShaderGen.h GeometryShaderManager.cpp GeometryShaderManager.h + GraphicsModSystem/Config/GraphicsMod.cpp + GraphicsModSystem/Config/GraphicsMod.h + GraphicsModSystem/Config/GraphicsModFeature.cpp + GraphicsModSystem/Config/GraphicsModFeature.h + GraphicsModSystem/Config/GraphicsModGroup.cpp + GraphicsModSystem/Config/GraphicsModGroup.h + GraphicsModSystem/Config/GraphicsTarget.cpp + GraphicsModSystem/Config/GraphicsTarget.h + GraphicsModSystem/Config/GraphicsTargetGroup.cpp + GraphicsModSystem/Config/GraphicsTargetGroup.h + GraphicsModSystem/Constants.h + GraphicsModSystem/Runtime/Actions/MoveAction.cpp + GraphicsModSystem/Runtime/Actions/MoveAction.h + GraphicsModSystem/Runtime/Actions/PrintAction.cpp + GraphicsModSystem/Runtime/Actions/PrintAction.h + GraphicsModSystem/Runtime/Actions/ScaleAction.cpp + GraphicsModSystem/Runtime/Actions/ScaleAction.h + GraphicsModSystem/Runtime/Actions/SkipAction.cpp + GraphicsModSystem/Runtime/Actions/SkipAction.h + GraphicsModSystem/Runtime/FBInfo.cpp + GraphicsModSystem/Runtime/FBInfo.h + GraphicsModSystem/Runtime/GraphicsModAction.h + GraphicsModSystem/Runtime/GraphicsModActionFactory.cpp + GraphicsModSystem/Runtime/GraphicsModActionFactory.h + GraphicsModSystem/Runtime/GraphicsModManager.cpp + GraphicsModSystem/Runtime/GraphicsModManager.h HiresTextures.cpp HiresTextures.h HiresTextures_DDSLoader.cpp diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.cpp new file mode 100644 index 0000000000..c7dc5aab87 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.cpp @@ -0,0 +1,291 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModSystem/Config/GraphicsMod.h" + +#include + +#include "Common/CommonPaths.h" +#include "Common/FileUtil.h" +#include "Common/Logging/Log.h" +#include "Common/StringUtil.h" + +#include "VideoCommon/GraphicsModSystem/Constants.h" + +std::optional GraphicsModConfig::Create(const std::string& file_path, + Source source) +{ + std::string json_data; + if (!File::ReadFileToString(file_path, json_data)) + { + ERROR_LOG_FMT(VIDEO, "Failed to load graphics mod json file '{}'", file_path); + return std::nullopt; + } + + picojson::value root; + const auto error = picojson::parse(root, json_data); + + if (!error.empty()) + { + ERROR_LOG_FMT(VIDEO, "Failed to load graphics mod json file '{}' due to parse error: {}", + file_path, error); + return std::nullopt; + } + + GraphicsModConfig result; + if (!result.DeserializeFromConfig(root)) + { + return std::nullopt; + } + result.m_source = source; + if (source == Source::User) + { + const std::string base_path = File::GetUserPath(D_GRAPHICSMOD_IDX); + if (base_path.size() > file_path.size()) + { + ERROR_LOG_FMT( + VIDEO, + "Failed to load graphics mod json file '{}' due to it not matching the base path: {}", + file_path, base_path); + return std::nullopt; + } + result.m_relative_path = file_path.substr(base_path.size()); + } + else + { + const std::string base_path = File::GetSysDirectory() + DOLPHIN_SYSTEM_GRAPHICS_MOD_DIR; + if (base_path.size() > file_path.size()) + { + ERROR_LOG_FMT( + VIDEO, + "Failed to load graphics mod json file '{}' due to it not matching the base path: {}", + file_path, base_path); + return std::nullopt; + } + result.m_relative_path = file_path.substr(base_path.size()); + } + + return result; +} + +std::optional GraphicsModConfig::Create(const picojson::object* obj) +{ + if (!obj) + return std::nullopt; + + const auto source_it = obj->find("source"); + if (source_it == obj->end()) + { + return std::nullopt; + } + const std::string source_str = source_it->second.to_str(); + + const auto path_it = obj->find("path"); + if (path_it == obj->end()) + { + return std::nullopt; + } + const std::string relative_path = path_it->second.to_str(); + + if (source_str == "system") + { + return Create(fmt::format("{}{}{}", File::GetSysDirectory(), DOLPHIN_SYSTEM_GRAPHICS_MOD_DIR, + relative_path), + Source::System); + } + else + { + return Create(File::GetUserPath(D_GRAPHICSMOD_IDX) + relative_path, Source::User); + } +} + +std::string GraphicsModConfig::GetAbsolutePath() const +{ + if (m_source == Source::System) + { + return WithUnifiedPathSeparators(fmt::format("{}{}{}", File::GetSysDirectory(), + DOLPHIN_SYSTEM_GRAPHICS_MOD_DIR, m_relative_path)); + } + else + { + return WithUnifiedPathSeparators(File::GetUserPath(D_GRAPHICSMOD_IDX) + m_relative_path); + } +} + +bool GraphicsModConfig::DeserializeFromConfig(const picojson::value& value) +{ + const auto& meta = value.get("meta"); + if (meta.is()) + { + const auto& title = meta.get("title"); + if (title.is()) + { + m_title = title.to_str(); + } + + const auto& author = meta.get("author"); + if (author.is()) + { + m_author = author.to_str(); + } + + const auto& description = meta.get("description"); + if (description.is()) + { + m_description = description.to_str(); + } + } + + const auto& groups = value.get("groups"); + if (groups.is()) + { + for (const auto& group_val : groups.get()) + { + if (!group_val.is()) + { + ERROR_LOG_FMT( + VIDEO, "Failed to load mod configuration file, specified group is not a json object"); + return false; + } + GraphicsTargetGroupConfig group; + if (!group.DeserializeFromConfig(group_val.get())) + { + return false; + } + + m_groups.push_back(group); + } + } + + const auto& features = value.get("features"); + if (features.is()) + { + for (const auto& feature_val : features.get()) + { + if (!feature_val.is()) + { + ERROR_LOG_FMT( + VIDEO, "Failed to load mod configuration file, specified feature is not a json object"); + return false; + } + GraphicsModFeatureConfig feature; + if (!feature.DeserializeFromConfig(feature_val.get())) + { + return false; + } + + m_features.push_back(feature); + } + } + + return true; +} + +void GraphicsModConfig::SerializeToProfile(picojson::object* obj) const +{ + if (!obj) + return; + + auto& json_obj = *obj; + switch (m_source) + { + case Source::User: + { + json_obj["source"] = picojson::value{"user"}; + } + break; + case Source::System: + { + json_obj["source"] = picojson::value{"system"}; + } + break; + }; + + json_obj["path"] = picojson::value{m_relative_path}; + + picojson::array serialized_groups; + for (const auto& group : m_groups) + { + picojson::object serialized_group; + group.SerializeToProfile(&serialized_group); + serialized_groups.push_back(picojson::value{serialized_group}); + } + json_obj["groups"] = picojson::value{serialized_groups}; + + picojson::array serialized_features; + for (const auto& feature : m_features) + { + picojson::object serialized_feature; + feature.SerializeToProfile(&serialized_feature); + serialized_features.push_back(picojson::value{serialized_feature}); + } + json_obj["features"] = picojson::value{serialized_features}; + + json_obj["enabled"] = picojson::value{m_enabled}; + + json_obj["weight"] = picojson::value{static_cast(m_weight)}; +} + +void GraphicsModConfig::DeserializeFromProfile(const picojson::object& obj) +{ + if (const auto it = obj.find("groups"); it != obj.end()) + { + if (it->second.is()) + { + auto serialized_groups = it->second.get(); + if (serialized_groups.size() != m_groups.size()) + return; + + for (std::size_t i = 0; i < serialized_groups.size(); i++) + { + const auto& serialized_group_val = serialized_groups[i]; + if (serialized_group_val.is()) + { + const auto& serialized_group = serialized_group_val.get(); + m_groups[i].DeserializeFromProfile(serialized_group); + } + } + } + } + + if (const auto it = obj.find("features"); it != obj.end()) + { + if (it->second.is()) + { + auto serialized_features = it->second.get(); + if (serialized_features.size() != m_features.size()) + return; + + for (std::size_t i = 0; i < serialized_features.size(); i++) + { + const auto& serialized_feature_val = serialized_features[i]; + if (serialized_feature_val.is()) + { + const auto& serialized_feature = serialized_feature_val.get(); + m_features[i].DeserializeFromProfile(serialized_feature); + } + } + } + } + + if (const auto it = obj.find("enabled"); it != obj.end()) + { + if (it->second.is()) + { + m_enabled = it->second.get(); + } + } + + if (const auto it = obj.find("weight"); it != obj.end()) + { + if (it->second.is()) + { + m_weight = static_cast(it->second.get()); + } + } +} + +bool GraphicsModConfig::operator<(const GraphicsModConfig& other) const +{ + return m_weight < other.m_weight; +} diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.h b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.h new file mode 100644 index 0000000000..f4f6859cb3 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.h @@ -0,0 +1,45 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#include + +#include "VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.h" +#include "VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.h" + +struct GraphicsModConfig +{ + std::string m_title; + std::string m_author; + std::string m_description; + bool m_enabled = false; + u16 m_weight = 0; + std::string m_relative_path; + + enum class Source + { + User, + System + }; + Source m_source = Source::User; + + std::vector m_groups; + std::vector m_features; + + static std::optional Create(const std::string& file, Source source); + static std::optional Create(const picojson::object* obj); + + std::string GetAbsolutePath() const; + + bool DeserializeFromConfig(const picojson::value& value); + + void SerializeToProfile(picojson::object* value) const; + void DeserializeFromProfile(const picojson::object& value); + + bool operator<(const GraphicsModConfig& other) const; +}; diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.cpp new file mode 100644 index 0000000000..3fa75eceba --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.cpp @@ -0,0 +1,48 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.h" + +#include "Common/Logging/Log.h" + +bool GraphicsModFeatureConfig::DeserializeFromConfig(const picojson::object& obj) +{ + if (auto group_iter = obj.find("group"); group_iter != obj.end()) + { + if (!group_iter->second.is()) + { + ERROR_LOG_FMT( + VIDEO, + "Failed to load mod configuration file, specified feature's group is not a string"); + return false; + } + m_group = group_iter->second.get(); + } + + if (auto action_iter = obj.find("action"); action_iter != obj.end()) + { + if (!action_iter->second.is()) + { + ERROR_LOG_FMT( + VIDEO, + "Failed to load mod configuration file, specified feature's action is not a string"); + return false; + } + m_action = action_iter->second.get(); + } + + if (auto action_data_iter = obj.find("action_data"); action_data_iter != obj.end()) + { + m_action_data = action_data_iter->second; + } + + return true; +} + +void GraphicsModFeatureConfig::SerializeToProfile(picojson::object*) const +{ +} + +void GraphicsModFeatureConfig::DeserializeFromProfile(const picojson::object&) +{ +} diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.h b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.h new file mode 100644 index 0000000000..af71bf35a7 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.h @@ -0,0 +1,20 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include + +struct GraphicsModFeatureConfig +{ + std::string m_group; + std::string m_action; + picojson::value m_action_data; + + bool DeserializeFromConfig(const picojson::object& value); + + void SerializeToProfile(picojson::object* value) const; + void DeserializeFromProfile(const picojson::object& value); +}; diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.cpp new file mode 100644 index 0000000000..0d1ae30fb6 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.cpp @@ -0,0 +1,191 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.h" + +#include +#include +#include + +#include "Common/CommonPaths.h" +#include "Common/FileSearch.h" +#include "Common/FileUtil.h" +#include "Common/Logging/Log.h" +#include "Common/StringUtil.h" +#include "Core/ConfigManager.h" + +#include "VideoCommon/GraphicsModSystem/Config/GraphicsMod.h" +#include "VideoCommon/GraphicsModSystem/Constants.h" +#include "VideoCommon/HiresTextures.h" + +GraphicsModGroupConfig::GraphicsModGroupConfig(const std::string& game_id) : m_game_id(game_id) +{ +} + +GraphicsModGroupConfig::~GraphicsModGroupConfig() = default; + +GraphicsModGroupConfig::GraphicsModGroupConfig(const GraphicsModGroupConfig&) = default; + +GraphicsModGroupConfig::GraphicsModGroupConfig(GraphicsModGroupConfig&&) = default; + +GraphicsModGroupConfig& GraphicsModGroupConfig::operator=(const GraphicsModGroupConfig&) = default; + +GraphicsModGroupConfig& GraphicsModGroupConfig::operator=(GraphicsModGroupConfig&&) = default; + +void GraphicsModGroupConfig::Load() +{ + const std::string file_path = GetPath(); + + std::set known_paths; + if (File::Exists(file_path)) + { + std::string json_data; + if (!File::ReadFileToString(file_path, json_data)) + { + ERROR_LOG_FMT(VIDEO, "Failed to load graphics mod group json file '{}'", file_path); + return; + } + + picojson::value root; + const auto error = picojson::parse(root, json_data); + + if (!error.empty()) + { + ERROR_LOG_FMT(VIDEO, + "Failed to load graphics mod group json file '{}' due to parse error: {}", + file_path, error); + return; + } + if (!root.is()) + { + ERROR_LOG_FMT( + VIDEO, + "Failed to load graphics mod group json file '{}' due to root not being an object!", + file_path); + return; + } + + const auto& mods = root.get("mods"); + if (mods.is()) + { + for (const auto& mod_json : mods.get()) + { + if (mod_json.is()) + { + const auto& mod_json_obj = mod_json.get(); + auto graphics_mod = GraphicsModConfig::Create(&mod_json_obj); + if (!graphics_mod) + { + continue; + } + graphics_mod->DeserializeFromProfile(mod_json_obj); + + auto mod_full_path = graphics_mod->GetAbsolutePath(); + known_paths.insert(std::move(mod_full_path)); + m_graphics_mods.push_back(*graphics_mod); + } + } + } + } + + const auto try_add_mod = [&known_paths, this](const std::string& dir, + GraphicsModConfig::Source source) { + auto file = dir + DIR_SEP + "metadata.json"; + UnifyPathSeparators(file); + if (known_paths.find(file) != known_paths.end()) + { + return; + } + const auto mod = GraphicsModConfig::Create(file, source); + if (mod) + { + m_graphics_mods.push_back(*mod); + } + }; + + const std::set graphics_mod_user_directories = + GetTextureDirectoriesWithGameId(File::GetUserPath(D_GRAPHICSMOD_IDX), m_game_id); + + for (const auto& graphics_mod_directory : graphics_mod_user_directories) + { + try_add_mod(graphics_mod_directory, GraphicsModConfig::Source::User); + } + + const std::set graphics_mod_system_directories = GetTextureDirectoriesWithGameId( + File::GetSysDirectory() + DOLPHIN_SYSTEM_GRAPHICS_MOD_DIR, m_game_id); + + for (const auto& graphics_mod_directory : graphics_mod_system_directories) + { + try_add_mod(graphics_mod_directory, GraphicsModConfig::Source::System); + } + + std::sort(m_graphics_mods.begin(), m_graphics_mods.end()); + for (auto& mod : m_graphics_mods) + { + m_path_to_graphics_mod[mod.GetAbsolutePath()] = &mod; + } + + m_change_count++; +} + +void GraphicsModGroupConfig::Save() const +{ + const std::string file_path = GetPath(); + std::ofstream json_stream; + File::OpenFStream(json_stream, file_path, std::ios_base::out); + if (!json_stream.is_open()) + { + ERROR_LOG_FMT(VIDEO, "Failed to open graphics mod group json file '{}' for writing", file_path); + return; + } + + picojson::object serialized_root; + picojson::array serialized_mods; + for (const auto& mod : m_graphics_mods) + { + picojson::object serialized_mod; + mod.SerializeToProfile(&serialized_mod); + serialized_mods.push_back(picojson::value{serialized_mod}); + } + serialized_root["mods"] = picojson::value{serialized_mods}; + + const auto output = picojson::value{serialized_root}.serialize(true); + json_stream << output; +} + +void GraphicsModGroupConfig::SetChangeCount(u32 change_count) +{ + m_change_count = change_count; +} + +u32 GraphicsModGroupConfig::GetChangeCount() const +{ + return m_change_count; +} + +const std::vector& GraphicsModGroupConfig::GetMods() const +{ + return m_graphics_mods; +} + +GraphicsModConfig* GraphicsModGroupConfig::GetMod(const std::string& absolute_path) const +{ + if (const auto iter = m_path_to_graphics_mod.find(absolute_path); + iter != m_path_to_graphics_mod.end()) + { + return iter->second; + } + + return nullptr; +} + +const std::string& GraphicsModGroupConfig::GetGameID() const +{ + return m_game_id; +} + +std::string GraphicsModGroupConfig::GetPath() const +{ + const std::string game_mod_root = File::GetUserPath(D_CONFIG_IDX) + GRAPHICSMOD_CONFIG_DIR; + return fmt::format("{}/{}.json", game_mod_root, m_game_id); +} diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.h b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.h new file mode 100644 index 0000000000..ace5127c58 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.h @@ -0,0 +1,46 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#include + +#include "Common/CommonTypes.h" + +struct GraphicsModConfig; + +class GraphicsModGroupConfig +{ +public: + explicit GraphicsModGroupConfig(const std::string& game_id); + ~GraphicsModGroupConfig(); + + GraphicsModGroupConfig(const GraphicsModGroupConfig&); + GraphicsModGroupConfig(GraphicsModGroupConfig&&); + + GraphicsModGroupConfig& operator=(const GraphicsModGroupConfig&); + GraphicsModGroupConfig& operator=(GraphicsModGroupConfig&&); + + void Load(); + void Save() const; + + void SetChangeCount(u32 change_count); + u32 GetChangeCount() const; + + const std::vector& GetMods() const; + + GraphicsModConfig* GetMod(const std::string& absolute_path) const; + + const std::string& GetGameID() const; + +private: + std::string GetPath() const; + std::string m_game_id; + std::vector m_graphics_mods; + std::map m_path_to_graphics_mod; + u32 m_change_count = 0; +}; diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTarget.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTarget.cpp new file mode 100644 index 0000000000..f55f184520 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTarget.cpp @@ -0,0 +1,254 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModSystem/Config/GraphicsTarget.h" + +#include "Common/Logging/Log.h" +#include "Common/StringUtil.h" +#include "VideoCommon/TextureCacheBase.h" + +namespace +{ +template , int> = 0> +std::optional DeserializeFBTargetFromConfig(const picojson::object& obj, std::string_view prefix) +{ + T fb; + const auto texture_filename_iter = obj.find("texture_filename"); + if (texture_filename_iter == obj.end()) + { + ERROR_LOG_FMT(VIDEO, + "Failed to load mod configuration file, option 'texture_filename' not found"); + return std::nullopt; + } + if (!texture_filename_iter->second.is()) + { + ERROR_LOG_FMT( + VIDEO, + "Failed to load mod configuration file, option 'texture_filename' is not a string type"); + return std::nullopt; + } + const auto texture_filename = texture_filename_iter->second.get(); + const auto texture_filename_without_prefix = texture_filename.substr(prefix.size() + 1); + const auto split_str_values = SplitString(texture_filename_without_prefix, '_'); + if (split_str_values.size() == 1) + { + ERROR_LOG_FMT( + VIDEO, "Failed to load mod configuration file, value in 'texture_filename' is not valid"); + return std::nullopt; + } + const auto split_width_height_values = SplitString(texture_filename_without_prefix, 'x'); + if (split_width_height_values.size() != 2) + { + ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' is " + "not valid, width and height separator found more matches than expected"); + return std::nullopt; + } + + const std::size_t width_underscore_pos = split_width_height_values[0].find_last_of('_'); + std::string width_str; + if (width_underscore_pos == std::string::npos) + { + width_str = split_width_height_values[0]; + } + else + { + width_str = split_width_height_values[0].substr(width_underscore_pos + 1); + } + if (!TryParse(width_str, &fb.m_width)) + { + ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' is " + "not valid, width not a number"); + return std::nullopt; + } + + const std::size_t height_underscore_pos = split_width_height_values[1].find_first_of('_'); + if (height_underscore_pos == std::string::npos || + height_underscore_pos == split_width_height_values[1].size() - 1) + { + ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' is " + "not valid, underscore after height is missing or incomplete"); + return std::nullopt; + } + const std::string height_str = split_width_height_values[1].substr(0, height_underscore_pos); + if (!TryParse(height_str, &fb.m_height)) + { + ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' is " + "not valid, height not a number"); + return std::nullopt; + } + + const std::size_t format_underscore_pos = + split_width_height_values[1].find_first_of('_', height_underscore_pos + 1); + + std::string format_str; + if (format_underscore_pos == std::string::npos) + { + format_str = split_width_height_values[1].substr(height_underscore_pos + 1); + } + else + { + format_str = split_width_height_values[1].substr( + height_underscore_pos + 1, (format_underscore_pos - height_underscore_pos) - 1); + } + u32 format; + if (!TryParse(format_str, &format)) + { + ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' is " + "not valid, texture format is not a number"); + return std::nullopt; + } + if (!IsValidTextureFormat(static_cast(format))) + { + ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' is " + "not valid, texture format is not valid"); + return std::nullopt; + } + fb.m_texture_format = static_cast(format); + + return fb; +} +std::optional ExtractTextureFilenameForConfig(const picojson::object& obj) +{ + const auto texture_filename_iter = obj.find("texture_filename"); + if (texture_filename_iter == obj.end()) + { + ERROR_LOG_FMT(VIDEO, + "Failed to load mod configuration file, option 'texture_filename' not found"); + return std::nullopt; + } + if (!texture_filename_iter->second.is()) + { + ERROR_LOG_FMT( + VIDEO, + "Failed to load mod configuration file, option 'texture_filename' is not a string type"); + return std::nullopt; + } + std::string texture_info = texture_filename_iter->second.get(); + if (texture_info.find(EFB_DUMP_PREFIX) != std::string::npos) + { + const auto letter_c_pos = texture_info.find_first_of('n'); + if (letter_c_pos == std::string::npos) + { + ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' " + "is an efb without a count"); + return std::nullopt; + } + texture_info = + texture_info.substr(letter_c_pos - 1, texture_info.find_first_of("_", letter_c_pos)); + } + else if (texture_info.find(XFB_DUMP_PREFIX) != std::string::npos) + { + const auto letter_c_pos = texture_info.find_first_of('n'); + if (letter_c_pos == std::string::npos) + { + ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' " + "is an xfb without a count"); + return std::nullopt; + } + texture_info = + texture_info.substr(letter_c_pos - 1, texture_info.find_first_of("_", letter_c_pos)); + } + return texture_info; +} +} // namespace + +std::optional DeserializeTargetFromConfig(const picojson::object& obj) +{ + const auto type_iter = obj.find("type"); + if (type_iter == obj.end()) + { + ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, option 'type' not found"); + return std::nullopt; + } + if (!type_iter->second.is()) + { + ERROR_LOG_FMT(VIDEO, + "Failed to load mod configuration file, option 'type' is not a string type"); + return std::nullopt; + } + const std::string& type = type_iter->second.get(); + if (type == "draw_started") + { + std::optional texture_info = ExtractTextureFilenameForConfig(obj); + if (!texture_info.has_value()) + return std::nullopt; + + DrawStartedTextureTarget target; + target.m_texture_info_string = texture_info.value(); + return target; + } + else if (type == "load_texture") + { + std::optional texture_info = ExtractTextureFilenameForConfig(obj); + if (!texture_info.has_value()) + return std::nullopt; + + LoadTextureTarget target; + target.m_texture_info_string = texture_info.value(); + return target; + } + else if (type == "efb") + { + return DeserializeFBTargetFromConfig(obj, EFB_DUMP_PREFIX); + } + else if (type == "xfb") + { + return DeserializeFBTargetFromConfig(obj, EFB_DUMP_PREFIX); + } + else if (type == "projection") + { + ProjectionTarget target; + const auto texture_iter = obj.find("texture_filename"); + if (texture_iter != obj.end()) + { + std::optional texture_info = ExtractTextureFilenameForConfig(obj); + if (!texture_info.has_value()) + return std::nullopt; + target.m_texture_info_string = texture_info; + } + const auto value_iter = obj.find("value"); + if (value_iter == obj.end()) + { + ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, option 'value' not found"); + return std::nullopt; + } + if (!value_iter->second.is()) + { + ERROR_LOG_FMT(VIDEO, + "Failed to load mod configuration file, option 'value' is not a string type"); + return std::nullopt; + } + const auto& value_str = value_iter->second.get(); + if (value_str == "2d") + { + target.m_projection_type = ProjectionType::Orthographic; + } + else if (value_str == "3d") + { + target.m_projection_type = ProjectionType::Perspective; + } + else + { + ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, option 'value' is not a valid " + "value, valid values are: 2d, 3d"); + return std::nullopt; + } + return target; + } + else + { + ERROR_LOG_FMT(VIDEO, + "Failed to load mod configuration file, option 'type' is not a valid value"); + } + return std::nullopt; +} + +void SerializeTargetToProfile(picojson::object*, const GraphicsTargetConfig&) +{ + // Added for consistency, no functionality as of now +} + +void DeserializeTargetFromProfile(const picojson::object&, GraphicsTargetConfig*) +{ + // Added for consistency, no functionality as of now +} diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTarget.h b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTarget.h new file mode 100644 index 0000000000..67a349e78f --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTarget.h @@ -0,0 +1,56 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#include + +#include "Common/CommonTypes.h" +#include "VideoCommon/TextureDecoder.h" +#include "VideoCommon/XFMemory.h" + +struct TextureTarget +{ + std::string m_texture_info_string; +}; + +struct DrawStartedTextureTarget final : public TextureTarget +{ +}; + +struct LoadTextureTarget final : public TextureTarget +{ +}; + +struct FBTarget +{ + u32 m_height = 0; + u32 m_width = 0; + TextureFormat m_texture_format = TextureFormat::I4; +}; + +struct EFBTarget final : public FBTarget +{ +}; + +struct XFBTarget final : public FBTarget +{ +}; + +struct ProjectionTarget +{ + std::optional m_texture_info_string; + ProjectionType m_projection_type = ProjectionType::Perspective; +}; + +using GraphicsTargetConfig = std::variant; + +std::optional DeserializeTargetFromConfig(const picojson::object& obj); + +void SerializeTargetToProfile(picojson::object* obj, const GraphicsTargetConfig& target); +void DeserializeTargetFromProfile(const picojson::object& obj, GraphicsTargetConfig* target); diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.cpp new file mode 100644 index 0000000000..753f304cb5 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.cpp @@ -0,0 +1,88 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.h" + +#include "Common/Logging/Log.h" + +bool GraphicsTargetGroupConfig::DeserializeFromConfig(const picojson::object& obj) +{ + if (auto name_iter = obj.find("name"); name_iter != obj.end()) + { + if (!name_iter->second.is()) + { + ERROR_LOG_FMT( + VIDEO, "Failed to load mod configuration file, specified group's name is not a string"); + return false; + } + m_name = name_iter->second.get(); + } + + if (auto targets_iter = obj.find("targets"); targets_iter != obj.end()) + { + if (!targets_iter->second.is()) + { + ERROR_LOG_FMT( + VIDEO, + "Failed to load mod configuration file, specified group's targets is not an array"); + return false; + } + for (const auto& target_val : targets_iter->second.get()) + { + if (!target_val.is()) + { + ERROR_LOG_FMT( + VIDEO, + "Failed to load shader configuration file, specified target is not a json object"); + return false; + } + const auto target = DeserializeTargetFromConfig(target_val.get()); + if (!target) + { + return false; + } + + m_targets.push_back(*target); + } + } + + return true; +} + +void GraphicsTargetGroupConfig::SerializeToProfile(picojson::object* obj) const +{ + if (!obj) + return; + auto& json_obj = *obj; + picojson::array serialized_targets; + for (const auto& target : m_targets) + { + picojson::object serialized_target; + SerializeTargetToProfile(&serialized_target, target); + serialized_targets.push_back(picojson::value{serialized_target}); + } + json_obj["targets"] = picojson::value{serialized_targets}; +} + +void GraphicsTargetGroupConfig::DeserializeFromProfile(const picojson::object& obj) +{ + if (const auto it = obj.find("targets"); it != obj.end()) + { + if (it->second.is()) + { + auto serialized_targets = it->second.get(); + if (serialized_targets.size() != m_targets.size()) + return; + + for (std::size_t i = 0; i < serialized_targets.size(); i++) + { + const auto& serialized_target_val = serialized_targets[i]; + if (serialized_target_val.is()) + { + const auto& serialized_target = serialized_target_val.get(); + DeserializeTargetFromProfile(serialized_target, &m_targets[i]); + } + } + } + } +} diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.h b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.h new file mode 100644 index 0000000000..2db2517175 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.h @@ -0,0 +1,22 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include + +#include "VideoCommon/GraphicsModSystem/Config/GraphicsTarget.h" + +struct GraphicsTargetGroupConfig +{ + std::string m_name; + std::vector m_targets; + + bool DeserializeFromConfig(const picojson::object& obj); + + void SerializeToProfile(picojson::object* obj) const; + void DeserializeFromProfile(const picojson::object& obj); +}; diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Constants.h b/Source/Core/VideoCommon/GraphicsModSystem/Constants.h new file mode 100644 index 0000000000..5c73c3e962 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Constants.h @@ -0,0 +1,11 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "Common/CommonPaths.h" + +static const inline std::string DOLPHIN_SYSTEM_GRAPHICS_MOD_DIR = + LOAD_DIR DIR_SEP GRAPHICSMOD_DIR DIR_SEP; diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/MoveAction.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/MoveAction.cpp new file mode 100644 index 0000000000..cc724c0480 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/MoveAction.cpp @@ -0,0 +1,47 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModSystem/Runtime/Actions/MoveAction.h" + +std::unique_ptr MoveAction::Create(const picojson::value& json_data) +{ + Common::Vec3 position_offset; + const auto& x = json_data.get("X"); + if (x.is()) + { + position_offset.x = static_cast(x.get()); + } + + const auto& y = json_data.get("Y"); + if (y.is()) + { + position_offset.y = static_cast(y.get()); + } + + const auto& z = json_data.get("Z"); + if (z.is()) + { + position_offset.z = static_cast(z.get()); + } + return std::make_unique(position_offset); +} + +MoveAction::MoveAction(Common::Vec3 position_offset) : m_position_offset(position_offset) +{ +} + +void MoveAction::OnProjection(Common::Matrix44* matrix) +{ + if (!matrix) + return; + + *matrix *= Common::Matrix44::Translate(m_position_offset); +} + +void MoveAction::OnProjectionAndTexture(Common::Matrix44* matrix) +{ + if (!matrix) + return; + + *matrix *= Common::Matrix44::Translate(m_position_offset); +} diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/MoveAction.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/MoveAction.h new file mode 100644 index 0000000000..768d6a9f38 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/MoveAction.h @@ -0,0 +1,22 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include + +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h" + +class MoveAction final : public GraphicsModAction +{ +public: + static std::unique_ptr Create(const picojson::value& json_data); + explicit MoveAction(Common::Vec3 position_offset); + void OnProjection(Common::Matrix44* matrix) override; + void OnProjectionAndTexture(Common::Matrix44* matrix) override; + +private: + Common::Vec3 m_position_offset; +}; diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/PrintAction.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/PrintAction.cpp new file mode 100644 index 0000000000..e6f7d6b659 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/PrintAction.cpp @@ -0,0 +1,36 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModSystem/Runtime/Actions/PrintAction.h" + +#include "Common/Logging/Log.h" + +void PrintAction::OnDrawStarted(bool*) +{ + INFO_LOG_FMT(VIDEO, "OnDrawStarted Called"); +} + +void PrintAction::OnEFB(bool*, u32 texture_width, u32 texture_height, u32* scaled_width, + u32* scaled_height) +{ + if (!scaled_width || !scaled_height) + return; + + INFO_LOG_FMT(VIDEO, "OnEFB Called. Original [{}, {}], Scaled [{}, {}]", texture_width, + texture_height, *scaled_width, *scaled_height); +} + +void PrintAction::OnProjection(Common::Matrix44*) +{ + INFO_LOG_FMT(VIDEO, "OnProjection Called"); +} + +void PrintAction::OnProjectionAndTexture(Common::Matrix44*) +{ + INFO_LOG_FMT(VIDEO, "OnProjectionAndTexture Called"); +} + +void PrintAction::OnTextureLoad() +{ + INFO_LOG_FMT(VIDEO, "OnTextureLoad Called"); +} diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/PrintAction.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/PrintAction.h new file mode 100644 index 0000000000..6d8c123b68 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/PrintAction.h @@ -0,0 +1,17 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h" + +class PrintAction final : public GraphicsModAction +{ +public: + void OnDrawStarted(bool* skip) override; + void OnEFB(bool* skip, u32 texture_width, u32 texture_height, u32* scaled_width, + u32* scaled_height) override; + void OnProjection(Common::Matrix44* matrix) override; + void OnProjectionAndTexture(Common::Matrix44* matrix) override; + void OnTextureLoad() override; +}; diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/ScaleAction.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/ScaleAction.cpp new file mode 100644 index 0000000000..d0cac24acb --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/ScaleAction.cpp @@ -0,0 +1,59 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModSystem/Runtime/Actions/ScaleAction.h" + +std::unique_ptr ScaleAction::Create(const picojson::value& json_data) +{ + Common::Vec3 scale; + const auto& x = json_data.get("X"); + if (x.is()) + { + scale.x = static_cast(x.get()); + } + + const auto& y = json_data.get("Y"); + if (y.is()) + { + scale.y = static_cast(y.get()); + } + + const auto& z = json_data.get("Z"); + if (z.is()) + { + scale.z = static_cast(z.get()); + } + return std::make_unique(scale); +} + +ScaleAction::ScaleAction(Common::Vec3 scale) : m_scale(scale) +{ +} + +void ScaleAction::OnEFB(bool*, u32 texture_width, u32 texture_height, u32* scaled_width, + u32* scaled_height) +{ + if (scaled_width && m_scale.x > 0) + *scaled_width = texture_width * m_scale.x; + + if (scaled_height && m_scale.y > 0) + *scaled_height = texture_height * m_scale.y; +} + +void ScaleAction::OnProjection(Common::Matrix44* matrix) +{ + if (!matrix) + return; + auto& the_matrix = *matrix; + the_matrix.data[0] = the_matrix.data[0] * m_scale.x; + the_matrix.data[5] = the_matrix.data[5] * m_scale.y; +} + +void ScaleAction::OnProjectionAndTexture(Common::Matrix44* matrix) +{ + if (!matrix) + return; + auto& the_matrix = *matrix; + the_matrix.data[0] = the_matrix.data[0] * m_scale.x; + the_matrix.data[5] = the_matrix.data[5] * m_scale.y; +} diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/ScaleAction.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/ScaleAction.h new file mode 100644 index 0000000000..cbe8744941 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/ScaleAction.h @@ -0,0 +1,24 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include + +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h" + +class ScaleAction final : public GraphicsModAction +{ +public: + static std::unique_ptr Create(const picojson::value& json_data); + explicit ScaleAction(Common::Vec3 scale); + void OnEFB(bool* skip, u32 texture_width, u32 texture_height, u32* scaled_width, + u32* scaled_height) override; + void OnProjection(Common::Matrix44* matrix) override; + void OnProjectionAndTexture(Common::Matrix44* matrix) override; + +private: + Common::Vec3 m_scale; +}; diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/SkipAction.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/SkipAction.cpp new file mode 100644 index 0000000000..ae3551497f --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/SkipAction.cpp @@ -0,0 +1,20 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModSystem/Runtime/Actions/SkipAction.h" + +void SkipAction::OnDrawStarted(bool* skip) +{ + if (!skip) + return; + + *skip = true; +} + +void SkipAction::OnEFB(bool* skip, u32, u32, u32*, u32*) +{ + if (!skip) + return; + + *skip = true; +} diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/SkipAction.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/SkipAction.h new file mode 100644 index 0000000000..5cd204a89d --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/SkipAction.h @@ -0,0 +1,14 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h" + +class SkipAction final : public GraphicsModAction +{ +public: + void OnDrawStarted(bool* skip) override; + void OnEFB(bool* skip, u32 texture_width, u32 texture_height, u32* scaled_width, + u32* scaled_height) override; +}; diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/FBInfo.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/FBInfo.cpp new file mode 100644 index 0000000000..5921c4a3e5 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/FBInfo.cpp @@ -0,0 +1,22 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModSystem/Runtime/FBInfo.h" + +#include "Common/Hash.h" + +u32 FBInfo::CalculateHash() const +{ + return Common::HashAdler32(reinterpret_cast(this), sizeof(FBInfo)); +} + +bool FBInfo::operator==(const FBInfo& other) const +{ + return m_height == other.m_height && m_width == other.m_width && + m_texture_format == other.m_texture_format; +} + +bool FBInfo::operator!=(const FBInfo& other) const +{ + return !(*this == other); +} diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/FBInfo.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/FBInfo.h new file mode 100644 index 0000000000..48106e3a2a --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/FBInfo.h @@ -0,0 +1,25 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "Common/CommonTypes.h" +#include "VideoCommon/TextureDecoder.h" + +struct FBInfo +{ + u32 m_height = 0; + u32 m_width = 0; + TextureFormat m_texture_format = TextureFormat::I4; + u32 CalculateHash() const; + bool operator==(const FBInfo& other) const; + bool operator!=(const FBInfo& other) const; +}; + +struct FBInfoHasher +{ + std::size_t operator()(const FBInfo& fb_info) const noexcept + { + return static_cast(fb_info.CalculateHash()); + } +}; diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h new file mode 100644 index 0000000000..54ea086a29 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h @@ -0,0 +1,29 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "Common/CommonTypes.h" +#include "Common/Matrix.h" + +class GraphicsModAction +{ +public: + GraphicsModAction() = default; + virtual ~GraphicsModAction() = default; + GraphicsModAction(const GraphicsModAction&) = default; + GraphicsModAction(GraphicsModAction&&) = default; + GraphicsModAction& operator=(const GraphicsModAction&) = default; + GraphicsModAction& operator=(GraphicsModAction&&) = default; + + virtual void OnDrawStarted(bool* skip) {} + virtual void OnEFB(bool* skip, u32 texture_width, u32 texture_height, u32* scaled_width, + u32* scaled_height) + { + } + virtual void OnXFB() {} + virtual void OnProjection(Common::Matrix44* matrix) {} + virtual void OnProjectionAndTexture(Common::Matrix44* matrix) {} + virtual void OnTextureLoad() {} + virtual void OnFrameEnd() {} +}; diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.cpp new file mode 100644 index 0000000000..5eaac88c25 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.cpp @@ -0,0 +1,34 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.h" + +#include "VideoCommon/GraphicsModSystem/Runtime/Actions/MoveAction.h" +#include "VideoCommon/GraphicsModSystem/Runtime/Actions/PrintAction.h" +#include "VideoCommon/GraphicsModSystem/Runtime/Actions/ScaleAction.h" +#include "VideoCommon/GraphicsModSystem/Runtime/Actions/SkipAction.h" + +namespace GraphicsModActionFactory +{ +std::unique_ptr Create(std::string_view name, const picojson::value& json_data) +{ + if (name == "print") + { + return std::make_unique(); + } + else if (name == "skip") + { + return std::make_unique(); + } + else if (name == "move") + { + return MoveAction::Create(json_data); + } + else if (name == "scale") + { + return ScaleAction::Create(json_data); + } + + return nullptr; +} +} // namespace GraphicsModActionFactory diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.h new file mode 100644 index 0000000000..3c9cc6d6a7 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.h @@ -0,0 +1,16 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include + +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h" + +namespace GraphicsModActionFactory +{ +std::unique_ptr Create(std::string_view name, const picojson::value& json_data); +} diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.cpp new file mode 100644 index 0000000000..9e3d3e9e99 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.cpp @@ -0,0 +1,279 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h" + +#include +#include +#include + +#include "Common/Logging/Log.h" +#include "Common/VariantUtil.h" + +#include "VideoCommon/GraphicsModSystem/Config/GraphicsMod.h" +#include "VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.h" +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.h" +#include "VideoCommon/TextureInfo.h" + +class GraphicsModManager::DecoratedAction final : public GraphicsModAction +{ +public: + DecoratedAction(std::unique_ptr action, GraphicsModConfig mod) + : m_action_impl(std::move(action)), m_mod(std::move(mod)) + { + } + void OnDrawStarted(bool* skip) override + { + if (!m_mod.m_enabled) + return; + m_action_impl->OnDrawStarted(skip); + } + void OnEFB(bool* skip, u32 texture_width, u32 texture_height, u32* scaled_width, + u32* scaled_height) override + { + if (!m_mod.m_enabled) + return; + m_action_impl->OnEFB(skip, texture_width, texture_height, scaled_width, scaled_height); + } + void OnProjection(Common::Matrix44* matrix) override + { + if (!m_mod.m_enabled) + return; + m_action_impl->OnProjection(matrix); + } + void OnProjectionAndTexture(Common::Matrix44* matrix) override + { + if (!m_mod.m_enabled) + return; + m_action_impl->OnProjectionAndTexture(matrix); + } + void OnTextureLoad() override + { + if (!m_mod.m_enabled) + return; + m_action_impl->OnTextureLoad(); + } + void OnFrameEnd() override + { + if (!m_mod.m_enabled) + return; + m_action_impl->OnFrameEnd(); + } + +private: + GraphicsModConfig m_mod; + std::unique_ptr m_action_impl; +}; + +const std::vector& +GraphicsModManager::GetProjectionActions(ProjectionType projection_type) const +{ + if (const auto it = m_projection_target_to_actions.find(projection_type); + it != m_projection_target_to_actions.end()) + { + return it->second; + } + + return m_default; +} + +const std::vector& +GraphicsModManager::GetProjectionTextureActions(ProjectionType projection_type, + const std::string& texture_name) const +{ + const auto lookup = fmt::format("{}_{}", texture_name, static_cast(projection_type)); + if (const auto it = m_projection_texture_target_to_actions.find(lookup); + it != m_projection_texture_target_to_actions.end()) + { + return it->second; + } + + return m_default; +} + +const std::vector& +GraphicsModManager::GetDrawStartedActions(const std::string& texture_name) const +{ + if (const auto it = m_draw_started_target_to_actions.find(texture_name); + it != m_draw_started_target_to_actions.end()) + { + return it->second; + } + + return m_default; +} + +const std::vector& +GraphicsModManager::GetTextureLoadActions(const std::string& texture_name) const +{ + if (const auto it = m_load_target_to_actions.find(texture_name); + it != m_load_target_to_actions.end()) + { + return it->second; + } + + return m_default; +} + +const std::vector& GraphicsModManager::GetEFBActions(const FBInfo& efb) const +{ + if (const auto it = m_efb_target_to_actions.find(efb); it != m_efb_target_to_actions.end()) + { + return it->second; + } + + return m_default; +} + +const std::vector& GraphicsModManager::GetXFBActions(const FBInfo& xfb) const +{ + if (const auto it = m_efb_target_to_actions.find(xfb); it != m_efb_target_to_actions.end()) + { + return it->second; + } + + return m_default; +} + +void GraphicsModManager::Load(const GraphicsModGroupConfig& config) +{ + Reset(); + + const auto& mods = config.GetMods(); + + std::map> group_to_targets; + for (const auto& mod : mods) + { + for (const GraphicsTargetGroupConfig& group : mod.m_groups) + { + if (m_groups.find(group.m_name) != m_groups.end()) + { + WARN_LOG_FMT( + VIDEO, + "Specified graphics mod group '{}' for mod '{}' is already specified by another mod.", + group.m_name, mod.m_title); + } + m_groups.insert(group.m_name); + + const auto internal_group = fmt::format("{}.{}", mod.m_title, group.m_name); + for (const GraphicsTargetConfig& target : group.m_targets) + { + group_to_targets[group.m_name].push_back(target); + group_to_targets[internal_group].push_back(target); + } + } + } + + for (const auto& mod : mods) + { + for (const GraphicsModFeatureConfig& feature : mod.m_features) + { + const auto create_action = [](const std::string_view& action_name, + const picojson::value& json_data, + GraphicsModConfig mod) -> std::unique_ptr { + auto action = GraphicsModActionFactory::Create(action_name, json_data); + if (action == nullptr) + { + return nullptr; + } + return std::make_unique(std::move(action), std::move(mod)); + }; + + const auto internal_group = fmt::format("{}.{}", mod.m_title, feature.m_group); + + const auto add_target = [&](const GraphicsTargetConfig& target, GraphicsModConfig mod) { + auto action = create_action(feature.m_action, feature.m_action_data, std::move(mod)); + if (action == nullptr) + { + WARN_LOG_FMT(VIDEO, "Failed to create action '{}' for group '{}'.", feature.m_action, + feature.m_group); + return; + } + m_actions.push_back(std::move(action)); + std::visit( + overloaded{ + [&](const DrawStartedTextureTarget& the_target) { + m_draw_started_target_to_actions[the_target.m_texture_info_string].push_back( + m_actions.back().get()); + }, + [&](const LoadTextureTarget& the_target) { + m_load_target_to_actions[the_target.m_texture_info_string].push_back( + m_actions.back().get()); + }, + [&](const EFBTarget& the_target) { + FBInfo info; + info.m_height = the_target.m_height; + info.m_width = the_target.m_width; + info.m_texture_format = the_target.m_texture_format; + m_efb_target_to_actions[info].push_back(m_actions.back().get()); + }, + [&](const XFBTarget& the_target) { + FBInfo info; + info.m_height = the_target.m_height; + info.m_width = the_target.m_width; + info.m_texture_format = the_target.m_texture_format; + m_xfb_target_to_actions[info].push_back(m_actions.back().get()); + }, + [&](const ProjectionTarget& the_target) { + if (the_target.m_texture_info_string) + { + const auto lookup = fmt::format("{}_{}", *the_target.m_texture_info_string, + static_cast(the_target.m_projection_type)); + m_projection_texture_target_to_actions[lookup].push_back( + m_actions.back().get()); + } + else + { + m_projection_target_to_actions[the_target.m_projection_type].push_back( + m_actions.back().get()); + } + }, + }, + target); + }; + + // Prefer groups in the pack over groups from another pack + if (const auto local_it = group_to_targets.find(internal_group); + local_it != group_to_targets.end()) + { + for (const GraphicsTargetConfig& target : local_it->second) + { + add_target(target, mod); + } + } + else if (const auto global_it = group_to_targets.find(feature.m_group); + global_it != group_to_targets.end()) + { + for (const GraphicsTargetConfig& target : global_it->second) + { + add_target(target, mod); + } + } + else + { + WARN_LOG_FMT(VIDEO, "Specified graphics mod group '{}' was not found for mod '{}'", + feature.m_group, mod.m_title); + } + } + } +} + +void GraphicsModManager::EndOfFrame() +{ + for (auto&& action : m_actions) + { + action->OnFrameEnd(); + } +} + +void GraphicsModManager::Reset() +{ + m_actions.clear(); + m_groups.clear(); + m_projection_target_to_actions.clear(); + m_projection_texture_target_to_actions.clear(); + m_draw_started_target_to_actions.clear(); + m_load_target_to_actions.clear(); + m_efb_target_to_actions.clear(); + m_xfb_target_to_actions.clear(); +} diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h new file mode 100644 index 0000000000..5151b85dbd --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h @@ -0,0 +1,54 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "VideoCommon/GraphicsModSystem/Runtime/FBInfo.h" +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h" +#include "VideoCommon/TextureInfo.h" +#include "VideoCommon/XFMemory.h" + +class GraphicsModGroupConfig; +class GraphicsModManager +{ +public: + const std::vector& GetProjectionActions(ProjectionType projection_type) const; + const std::vector& + GetProjectionTextureActions(ProjectionType projection_type, + const std::string& texture_name) const; + const std::vector& + GetDrawStartedActions(const std::string& texture_name) const; + const std::vector& + GetTextureLoadActions(const std::string& texture_name) const; + const std::vector& GetEFBActions(const FBInfo& efb) const; + const std::vector& GetXFBActions(const FBInfo& xfb) const; + + void Load(const GraphicsModGroupConfig& config); + + void EndOfFrame(); + +private: + void Reset(); + + class DecoratedAction; + + static inline const std::vector m_default = {}; + std::list> m_actions; + std::unordered_map> + m_projection_target_to_actions; + std::unordered_map> + m_projection_texture_target_to_actions; + std::unordered_map> m_draw_started_target_to_actions; + std::unordered_map> m_load_target_to_actions; + std::unordered_map, FBInfoHasher> m_efb_target_to_actions; + std::unordered_map, FBInfoHasher> m_xfb_target_to_actions; + + std::unordered_set m_groups; +}; diff --git a/Source/Core/VideoCommon/HiresTextures.cpp b/Source/Core/VideoCommon/HiresTextures.cpp index 85c9cfc238..9636bfe651 100644 --- a/Source/Core/VideoCommon/HiresTextures.cpp +++ b/Source/Core/VideoCommon/HiresTextures.cpp @@ -213,7 +213,7 @@ void HiresTexture::Prefetch() 10000); } -std::string HiresTexture::GenBaseName(TextureInfo& texture_info, bool dump) +std::string HiresTexture::GenBaseName(const TextureInfo& texture_info, bool dump) { if (!dump && s_textureMap.empty()) return ""; @@ -261,7 +261,7 @@ u32 HiresTexture::CalculateMipCount(u32 width, u32 height) return mip_count; } -std::shared_ptr HiresTexture::Search(TextureInfo& texture_info) +std::shared_ptr HiresTexture::Search(const TextureInfo& texture_info) { const std::string base_filename = GenBaseName(texture_info); @@ -433,17 +433,17 @@ std::set GetTextureDirectoriesWithGameId(const std::string& root_di } } - const auto match_gameid = [game_id](const std::string& filename) { + const auto match_gameid_or_all = [game_id](const std::string& filename) { std::string basename; SplitPath(filename, nullptr, &basename, nullptr); - return basename == game_id || basename == game_id.substr(0, 3); + return basename == game_id || basename == game_id.substr(0, 3) || basename == "all"; }; // Look for any other directories that might be specific to the given gameid const auto files = Common::DoFileSearch({root_directory}, {".txt"}, true); for (const auto& file : files) { - if (match_gameid(file)) + if (match_gameid_or_all(file)) { // The following code is used to calculate the top directory // of a found gameid.txt file diff --git a/Source/Core/VideoCommon/HiresTextures.h b/Source/Core/VideoCommon/HiresTextures.h index 78e40460db..bcd889e18c 100644 --- a/Source/Core/VideoCommon/HiresTextures.h +++ b/Source/Core/VideoCommon/HiresTextures.h @@ -25,9 +25,9 @@ public: static void Clear(); static void Shutdown(); - static std::shared_ptr Search(TextureInfo& texture_info); + static std::shared_ptr Search(const TextureInfo& texture_info); - static std::string GenBaseName(TextureInfo& texture_info, bool dump = false); + static std::string GenBaseName(const TextureInfo& texture_info, bool dump = false); static u32 CalculateMipCount(u32 width, u32 height); diff --git a/Source/Core/VideoCommon/RenderBase.cpp b/Source/Core/VideoCommon/RenderBase.cpp index dbad9711f1..412461d409 100644 --- a/Source/Core/VideoCommon/RenderBase.cpp +++ b/Source/Core/VideoCommon/RenderBase.cpp @@ -67,6 +67,7 @@ #include "VideoCommon/FramebufferManager.h" #include "VideoCommon/FramebufferShaderGen.h" #include "VideoCommon/FreeLookCamera.h" +#include "VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.h" #include "VideoCommon/NetPlayChatUI.h" #include "VideoCommon/NetPlayGolfUI.h" #include "VideoCommon/OnScreenDisplay.h" @@ -135,6 +136,22 @@ bool Renderer::Initialize() return false; } + if (g_ActiveConfig.bGraphicMods) + { + // If a config change occurred in a previous session, + // remember the old change count value. By setting + // our current change count to the old value, we + // avoid loading the stale data when we + // check for config changes. + const u32 old_game_mod_changes = g_ActiveConfig.graphics_mod_config ? + g_ActiveConfig.graphics_mod_config->GetChangeCount() : + 0; + g_ActiveConfig.graphics_mod_config = GraphicsModGroupConfig(SConfig::GetInstance().GetGameID()); + g_ActiveConfig.graphics_mod_config->Load(); + g_ActiveConfig.graphics_mod_config->SetChangeCount(old_game_mod_changes); + m_graphics_mod_manager.Load(*g_ActiveConfig.graphics_mod_config); + } + return true; } @@ -465,12 +482,27 @@ void Renderer::CheckForConfigChanges() const bool old_force_filtering = g_ActiveConfig.bForceFiltering; const bool old_vsync = g_ActiveConfig.bVSyncActive; const bool old_bbox = g_ActiveConfig.bBBoxEnable; + const u32 old_game_mod_changes = + g_ActiveConfig.graphics_mod_config ? g_ActiveConfig.graphics_mod_config->GetChangeCount() : 0; + const bool old_graphics_mods_enabled = g_ActiveConfig.bGraphicMods; UpdateActiveConfig(); FreeLook::UpdateActiveConfig(); g_freelook_camera.SetControlType(FreeLook::GetActiveConfig().camera_config.control_type); + if (g_ActiveConfig.bGraphicMods && !old_graphics_mods_enabled) + { + g_ActiveConfig.graphics_mod_config = GraphicsModGroupConfig(SConfig::GetInstance().GetGameID()); + g_ActiveConfig.graphics_mod_config->Load(); + } + + if (g_ActiveConfig.graphics_mod_config && + (old_game_mod_changes != g_ActiveConfig.graphics_mod_config->GetChangeCount())) + { + m_graphics_mod_manager.Load(*g_ActiveConfig.graphics_mod_config); + } + // Update texture cache settings with any changed options. g_texture_cache->OnConfigChanged(g_ActiveConfig); @@ -1309,6 +1341,11 @@ void Renderer::Swap(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, u6 // behind the renderer. FlushFrameDump(); + if (g_ActiveConfig.bGraphicMods) + { + m_graphics_mod_manager.EndOfFrame(); + } + if (xfb_addr && fb_width && fb_stride && fb_height) { // Get the current XFB from texture cache @@ -1830,3 +1867,8 @@ std::unique_ptr Renderer::CreateAsyncShaderCom { return std::make_unique(); } + +const GraphicsModManager& Renderer::GetGraphicsModManager() const +{ + return m_graphics_mod_manager; +} diff --git a/Source/Core/VideoCommon/RenderBase.h b/Source/Core/VideoCommon/RenderBase.h index 380225b133..403d028525 100644 --- a/Source/Core/VideoCommon/RenderBase.h +++ b/Source/Core/VideoCommon/RenderBase.h @@ -30,6 +30,7 @@ #include "VideoCommon/BPMemory.h" #include "VideoCommon/FPSCounter.h" #include "VideoCommon/FrameDump.h" +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h" #include "VideoCommon/RenderState.h" #include "VideoCommon/TextureConfig.h" @@ -268,6 +269,8 @@ public: // Will forcibly reload all textures on the next swap void ForceReloadTextures(); + const GraphicsModManager& GetGraphicsModManager() const; + protected: // Bitmask containing information about which configuration has changed for the backend. enum ConfigChangeBits : u32 @@ -447,6 +450,8 @@ private: std::unique_ptr m_netplay_chat_ui; Common::Flag m_force_reload_textures; + + GraphicsModManager m_graphics_mod_manager; }; extern std::unique_ptr g_renderer; diff --git a/Source/Core/VideoCommon/TextureCacheBase.cpp b/Source/Core/VideoCommon/TextureCacheBase.cpp index 0ed5ffe014..9eb6e09792 100644 --- a/Source/Core/VideoCommon/TextureCacheBase.cpp +++ b/Source/Core/VideoCommon/TextureCacheBase.cpp @@ -36,6 +36,7 @@ #include "VideoCommon/AbstractStagingTexture.h" #include "VideoCommon/BPMemory.h" #include "VideoCommon/FramebufferManager.h" +#include "VideoCommon/GraphicsModSystem/Runtime/FBInfo.h" #include "VideoCommon/HiresTextures.h" #include "VideoCommon/OpcodeDecoding.h" #include "VideoCommon/PixelShaderManager.h" @@ -55,6 +56,8 @@ static const u64 TEXHASH_INVALID = 0; static const int TEXTURE_KILL_THRESHOLD = 64; static const int TEXTURE_POOL_KILL_THRESHOLD = 3; +static int xfb_count = 0; + std::unique_ptr g_texture_cache; TextureCacheBase::TCacheEntry::TCacheEntry(std::unique_ptr tex, @@ -153,6 +156,9 @@ void TextureCacheBase::OnConfigChanged(const VideoConfig& config) HiresTexture::Update(); } + const u32 change_count = + config.graphics_mod_config ? config.graphics_mod_config->GetChangeCount() : 0; + // TODO: Invalidating texcache is really stupid in some of these cases if (config.iSafeTextureCache_ColorSamples != backup_config.color_samples || config.bTexFmtOverlayEnable != backup_config.texfmt_overlay || @@ -160,7 +166,9 @@ void TextureCacheBase::OnConfigChanged(const VideoConfig& config) config.bHiresTextures != backup_config.hires_textures || config.bEnableGPUTextureDecoding != backup_config.gpu_texture_decoding || config.bDisableCopyToVRAM != backup_config.disable_vram_copies || - config.bArbitraryMipmapDetection != backup_config.arbitrary_mipmap_detection) + config.bArbitraryMipmapDetection != backup_config.arbitrary_mipmap_detection || + config.bGraphicMods != backup_config.graphics_mods || + change_count != backup_config.graphics_mod_change_count) { Invalidate(); TexDecoder_SetTexFmtOverlayOptions(config.bTexFmtOverlayEnable, config.bTexFmtOverlayCenter); @@ -255,6 +263,9 @@ void TextureCacheBase::SetBackupConfig(const VideoConfig& config) backup_config.gpu_texture_decoding = config.bEnableGPUTextureDecoding; backup_config.disable_vram_copies = config.bDisableCopyToVRAM; backup_config.arbitrary_mipmap_detection = config.bArbitraryMipmapDetection; + backup_config.graphics_mods = config.bGraphicMods; + backup_config.graphics_mod_change_count = + config.graphics_mod_config ? config.graphics_mod_config->GetChangeCount() : 0; } TextureCacheBase::TCacheEntry* @@ -1205,15 +1216,15 @@ private: std::vector levels; }; -TextureCacheBase::TCacheEntry* TextureCacheBase::Load(const u32 stage) +TextureCacheBase::TCacheEntry* TextureCacheBase::Load(const TextureInfo& texture_info) { // if this stage was not invalidated by changes to texture registers, keep the current texture - if (TMEM::IsValid(stage) && bound_textures[stage]) + if (TMEM::IsValid(texture_info.GetStage()) && bound_textures[texture_info.GetStage()]) { - TCacheEntry* entry = bound_textures[stage]; + TCacheEntry* entry = bound_textures[texture_info.GetStage()]; // If the TMEM configuration is such that this texture is more or less guaranteed to still // be in TMEM, then we know we can reuse the old entry without even hashing the memory - if (TMEM::IsCached(stage)) + if (TMEM::IsCached(texture_info.GetStage())) { return entry; } @@ -1226,26 +1237,29 @@ TextureCacheBase::TCacheEntry* TextureCacheBase::Load(const u32 stage) } } - TextureInfo texture_info = TextureInfo::FromStage(stage); - auto entry = GetTexture(g_ActiveConfig.iSafeTextureCache_ColorSamples, texture_info); if (!entry) return nullptr; entry->frameCount = FRAMECOUNT_INVALID; - bound_textures[stage] = entry; + if (entry->texture_info_name.empty() && g_ActiveConfig.bGraphicMods) + { + entry->texture_info_name = texture_info.CalculateTextureName().GetFullName(); + } + bound_textures[texture_info.GetStage()] = entry; // We need to keep track of invalided textures until they have actually been replaced or // re-loaded - TMEM::Bind(stage, entry->NumBlocksX(), entry->NumBlocksY(), entry->GetNumLevels() > 1, - entry->format == TextureFormat::RGBA8); + TMEM::Bind(texture_info.GetStage(), entry->NumBlocksX(), entry->NumBlocksY(), + entry->GetNumLevels() > 1, entry->format == TextureFormat::RGBA8); return entry; } TextureCacheBase::TCacheEntry* -TextureCacheBase::GetTexture(const int textureCacheSafetyColorSampleSize, TextureInfo& texture_info) +TextureCacheBase::GetTexture(const int textureCacheSafetyColorSampleSize, + const TextureInfo& texture_info) { u32 expanded_width = texture_info.GetExpandedWidth(); u32 expanded_height = texture_info.GetExpandedHeight(); @@ -1764,12 +1778,20 @@ TextureCacheBase::GetXFBTexture(u32 address, u32 width, u32 height, u32 stride, SETSTAT(g_stats.num_textures_alive, static_cast(textures_by_address.size())); INCSTAT(g_stats.num_textures_uploaded); - if (g_ActiveConfig.bDumpXFBTarget) + if (g_ActiveConfig.bDumpXFBTarget || g_ActiveConfig.bGraphicMods) { - // While this isn't really an xfb copy, we can treat it as such for dumping purposes - static int xfb_count = 0; - entry->texture->Save( - fmt::format("{}xfb_loaded_{}.png", File::GetUserPath(D_DUMPTEXTURES_IDX), xfb_count++), 0); + const std::string id = fmt::format("{}x{}", width, height); + if (g_ActiveConfig.bGraphicMods) + { + entry->texture_info_name = fmt::format("{}_{}", XFB_DUMP_PREFIX, id); + } + + if (g_ActiveConfig.bDumpXFBTarget) + { + entry->texture->Save(fmt::format("{}{}_n{:06}_{}.png", File::GetUserPath(D_DUMPTEXTURES_IDX), + XFB_DUMP_PREFIX, xfb_count++, id), + 0); + } } GetDisplayRectForXFBEntry(entry, width, height, display_rect); @@ -2119,6 +2141,35 @@ void TextureCacheBase::CopyRenderTargetToTexture( const u32 bytes_per_row = num_blocks_x * bytes_per_block; const u32 covered_range = num_blocks_y * dstStride; + if (g_ActiveConfig.bGraphicMods) + { + FBInfo info; + info.m_width = tex_w; + info.m_height = tex_h; + info.m_texture_format = baseFormat; + if (is_xfb_copy) + { + for (const auto action : g_renderer->GetGraphicsModManager().GetXFBActions(info)) + { + action->OnXFB(); + } + } + else + { + bool skip = false; + for (const auto action : g_renderer->GetGraphicsModManager().GetEFBActions(info)) + { + action->OnEFB(&skip, tex_w, tex_h, &scaled_tex_w, &scaled_tex_h); + } + if (skip == true) + { + if (copy_to_ram) + UninitializeEFBMemory(dst, dstStride, bytes_per_row, num_blocks_y); + return; + } + } + } + if (dstStride < bytes_per_row) { // This kind of efb copy results in a scrambled image. @@ -2168,20 +2219,38 @@ void TextureCacheBase::CopyRenderTargetToTexture( isIntensity, gamma, clamp_top, clamp_bottom, GetVRAMCopyFilterCoefficients(filter_coefficients)); - if (g_ActiveConfig.bDumpEFBTarget && !is_xfb_copy) + if (is_xfb_copy && (g_ActiveConfig.bDumpXFBTarget || g_ActiveConfig.bGraphicMods)) { - static int efb_count = 0; - entry->texture->Save( - fmt::format("{}efb_frame_{}.png", File::GetUserPath(D_DUMPTEXTURES_IDX), efb_count++), - 0); - } + const std::string id = fmt::format("{}x{}", tex_w, tex_h); + if (g_ActiveConfig.bGraphicMods) + { + entry->texture_info_name = fmt::format("{}_{}", XFB_DUMP_PREFIX, id); + } - if (g_ActiveConfig.bDumpXFBTarget && is_xfb_copy) + if (g_ActiveConfig.bDumpXFBTarget) + { + entry->texture->Save(fmt::format("{}{}_n{:06}_{}.png", + File::GetUserPath(D_DUMPTEXTURES_IDX), XFB_DUMP_PREFIX, + xfb_count++, id), + 0); + } + } + else if (g_ActiveConfig.bDumpEFBTarget || g_ActiveConfig.bGraphicMods) { - static int xfb_count = 0; - entry->texture->Save( - fmt::format("{}xfb_copy_{}.png", File::GetUserPath(D_DUMPTEXTURES_IDX), xfb_count++), - 0); + const std::string id = fmt::format("{}x{}_{}", tex_w, tex_h, static_cast(baseFormat)); + if (g_ActiveConfig.bGraphicMods) + { + entry->texture_info_name = fmt::format("{}_{}", EFB_DUMP_PREFIX, id); + } + + if (g_ActiveConfig.bDumpEFBTarget) + { + static int efb_count = 0; + entry->texture->Save(fmt::format("{}{}_n{:06}_{}.png", + File::GetUserPath(D_DUMPTEXTURES_IDX), EFB_DUMP_PREFIX, + efb_count++, id), + 0); + } } } } @@ -2225,15 +2294,7 @@ void TextureCacheBase::CopyRenderTargetToTexture( } else { - // Hack: Most games don't actually need the correct texture data in RAM - // and we can just keep a copy in VRAM. We zero the memory so we - // can check it hasn't changed before using our copy in VRAM. - u8* ptr = dst; - for (u32 i = 0; i < num_blocks_y; i++) - { - std::memset(ptr, 0, bytes_per_row); - ptr += dstStride; - } + UninitializeEFBMemory(dst, dstStride, bytes_per_row, num_blocks_y); } } @@ -2403,6 +2464,20 @@ void TextureCacheBase::ReleaseEFBCopyStagingTexture(std::unique_ptr #include #include +#include #include #include #include @@ -29,6 +30,9 @@ class AbstractStagingTexture; class PointerWrap; struct VideoConfig; +constexpr std::string_view EFB_DUMP_PREFIX = "efb1"; +constexpr std::string_view XFB_DUMP_PREFIX = "xfb1"; + struct TextureAndTLUTFormat { TextureAndTLUTFormat(TextureFormat texfmt_ = TextureFormat::I4, @@ -151,6 +155,8 @@ public: u32 pending_efb_copy_height = 0; bool pending_efb_copy_invalidated = false; + std::string texture_info_name = ""; + explicit TCacheEntry(std::unique_ptr tex, std::unique_ptr fb); @@ -235,8 +241,9 @@ public: void Invalidate(); - TCacheEntry* Load(const u32 stage); - TCacheEntry* GetTexture(const int textureCacheSafetyColorSampleSize, TextureInfo& texture_info); + TCacheEntry* Load(const TextureInfo& texture_info); + TCacheEntry* GetTexture(const int textureCacheSafetyColorSampleSize, + const TextureInfo& texture_info); TCacheEntry* GetXFBTexture(u32 address, u32 width, u32 height, u32 stride, MathUtil::Rectangle* display_rect); @@ -327,6 +334,7 @@ private: TexAddrCache::iterator InvalidateTexture(TexAddrCache::iterator t_iter, bool discard_pending_efb_copy = false); + void UninitializeEFBMemory(u8* dst, u32 stride, u32 bytes_per_row, u32 num_blocks_y); void UninitializeXFBMemory(u8* dst, u32 stride, u32 bytes_per_row, u32 num_blocks_y); // Precomputing the coefficients for the previous, current, and next lines for the copy filter. @@ -369,6 +377,8 @@ private: bool gpu_texture_decoding; bool disable_vram_copies; bool arbitrary_mipmap_detection; + bool graphics_mods; + u32 graphics_mod_change_count; }; BackupConfig backup_config = {}; diff --git a/Source/Core/VideoCommon/TextureDecoder.h b/Source/Core/VideoCommon/TextureDecoder.h index a00e95d8f2..dcc7513d43 100644 --- a/Source/Core/VideoCommon/TextureDecoder.h +++ b/Source/Core/VideoCommon/TextureDecoder.h @@ -50,6 +50,11 @@ static inline bool IsColorIndexed(TextureFormat format) format == TextureFormat::C14X2; } +static inline bool IsValidTextureFormat(TextureFormat format) +{ + return format <= TextureFormat::RGBA8 || IsColorIndexed(format) || format == TextureFormat::CMPR; +} + // The EFB Copy pipeline looks like: // // 1. Read EFB -> 2. Select color/depth -> 3. Downscale (optional) diff --git a/Source/Core/VideoCommon/TextureInfo.cpp b/Source/Core/VideoCommon/TextureInfo.cpp index 696f5b904c..ff117c6926 100644 --- a/Source/Core/VideoCommon/TextureInfo.cpp +++ b/Source/Core/VideoCommon/TextureInfo.cpp @@ -39,20 +39,20 @@ TextureInfo TextureInfo::FromStage(u32 stage) if (from_tmem) { - return TextureInfo(&texMem[tmem_address_even], tlut_ptr, address, texture_format, tlut_format, - width, height, true, &texMem[tmem_address_odd], &texMem[tmem_address_even], - mip_count); + return TextureInfo(stage, &texMem[tmem_address_even], tlut_ptr, address, texture_format, + tlut_format, width, height, true, &texMem[tmem_address_odd], + &texMem[tmem_address_even], mip_count); } - return TextureInfo(Memory::GetPointer(address), tlut_ptr, address, texture_format, tlut_format, - width, height, false, nullptr, nullptr, mip_count); + return TextureInfo(stage, Memory::GetPointer(address), tlut_ptr, address, texture_format, + tlut_format, width, height, false, nullptr, nullptr, mip_count); } -TextureInfo::TextureInfo(const u8* ptr, const u8* tlut_ptr, u32 address, +TextureInfo::TextureInfo(u32 stage, const u8* ptr, const u8* tlut_ptr, u32 address, TextureFormat texture_format, TLUTFormat tlut_format, u32 width, u32 height, bool from_tmem, const u8* tmem_odd, const u8* tmem_even, std::optional mip_count) - : m_ptr(ptr), m_tlut_ptr(tlut_ptr), m_address(address), m_from_tmem(from_tmem), + : m_stage(stage), m_ptr(ptr), m_tlut_ptr(tlut_ptr), m_address(address), m_from_tmem(from_tmem), m_tmem_odd(tmem_odd), m_texture_format(texture_format), m_tlut_format(tlut_format), m_raw_width(width), m_raw_height(height) { @@ -100,7 +100,7 @@ std::string TextureInfo::NameDetails::GetFullName() const return fmt::format("{}_{}{}_{}", base_name, texture_name, tlut_name, format_name); } -TextureInfo::NameDetails TextureInfo::CalculateTextureName() +TextureInfo::NameDetails TextureInfo::CalculateTextureName() const { if (!m_ptr) return NameDetails{}; @@ -240,6 +240,11 @@ u32 TextureInfo::GetRawHeight() const return m_raw_height; } +u32 TextureInfo::GetStage() const +{ + return m_stage; +} + bool TextureInfo::HasMipMaps() const { return !m_mip_levels.empty(); diff --git a/Source/Core/VideoCommon/TextureInfo.h b/Source/Core/VideoCommon/TextureInfo.h index 109715ba45..940b09b3cb 100644 --- a/Source/Core/VideoCommon/TextureInfo.h +++ b/Source/Core/VideoCommon/TextureInfo.h @@ -17,9 +17,10 @@ class TextureInfo { public: static TextureInfo FromStage(u32 stage); - TextureInfo(const u8* ptr, const u8* tlut_ptr, u32 address, TextureFormat texture_format, - TLUTFormat tlut_format, u32 width, u32 height, bool from_tmem, const u8* tmem_odd, - const u8* tmem_even, std::optional mip_count); + TextureInfo(u32 stage, const u8* ptr, const u8* tlut_ptr, u32 address, + TextureFormat texture_format, TLUTFormat tlut_format, u32 width, u32 height, + bool from_tmem, const u8* tmem_odd, const u8* tmem_even, + std::optional mip_count); struct NameDetails { @@ -30,7 +31,7 @@ public: std::string GetFullName() const; }; - NameDetails CalculateTextureName(); + NameDetails CalculateTextureName() const; const u8* GetData() const; const u8* GetTlutAddress() const; @@ -55,6 +56,8 @@ public: u32 GetRawWidth() const; u32 GetRawHeight() const; + u32 GetStage() const; + class MipLevel { public: @@ -115,4 +118,6 @@ private: u32 m_block_height; u32 m_expanded_height; u32 m_raw_height; + + u32 m_stage; }; diff --git a/Source/Core/VideoCommon/VertexManagerBase.cpp b/Source/Core/VideoCommon/VertexManagerBase.cpp index 72f02927b5..b3d6f86686 100644 --- a/Source/Core/VideoCommon/VertexManagerBase.cpp +++ b/Source/Core/VideoCommon/VertexManagerBase.cpp @@ -7,7 +7,6 @@ #include #include -#include "Common/BitSet.h" #include "Common/ChunkFile.h" #include "Common/CommonTypes.h" #include "Common/EnumMap.h" @@ -30,6 +29,7 @@ #include "VideoCommon/RenderBase.h" #include "VideoCommon/Statistics.h" #include "VideoCommon/TextureCacheBase.h" +#include "VideoCommon/TextureInfo.h" #include "VideoCommon/VertexLoaderManager.h" #include "VideoCommon/VertexShaderManager.h" #include "VideoCommon/VideoBackendBase.h" @@ -337,7 +337,7 @@ bool VertexManagerBase::UploadTexelBuffer(const void* data, u32 data_size, Texel return false; } -void VertexManagerBase::LoadTextures() +BitSet32 VertexManagerBase::UsedTextures() const { BitSet32 usedtextures; for (u32 i = 0; i < bpmem.genMode.numtevstages + 1u; ++i) @@ -349,10 +349,7 @@ void VertexManagerBase::LoadTextures() if (bpmem.tevind[i].IsActive() && bpmem.tevind[i].bt < bpmem.genMode.numindstages) usedtextures[bpmem.tevindref.getTexMap(bpmem.tevind[i].bt)] = true; - for (unsigned int i : usedtextures) - g_texture_cache->Load(i); - - g_texture_cache->BindTextures(usedtextures); + return usedtextures; } void VertexManagerBase::Flush() @@ -455,7 +452,30 @@ void VertexManagerBase::Flush() CalculateBinormals(VertexLoaderManager::GetCurrentVertexFormat()); // Calculate ZSlope for zfreeze - VertexShaderManager::SetConstants(); + const auto used_textures = UsedTextures(); + std::vector texture_names; + if (!m_cull_all) + { + if (!g_ActiveConfig.bGraphicMods) + { + for (const u32 i : used_textures) + { + g_texture_cache->Load(TextureInfo::FromStage(i)); + } + } + else + { + for (const u32 i : used_textures) + { + const auto cache_entry = g_texture_cache->Load(TextureInfo::FromStage(i)); + if (cache_entry) + { + texture_names.push_back(cache_entry->texture_info_name); + } + } + } + } + VertexShaderManager::SetConstants(texture_names); if (!bpmem.genMode.zfreeze) { // Must be done after VertexShaderManager::SetConstants() @@ -469,6 +489,18 @@ void VertexManagerBase::Flush() if (!m_cull_all) { + for (const auto& texture_name : texture_names) + { + bool skip = false; + for (const auto action : + g_renderer->GetGraphicsModManager().GetDrawStartedActions(texture_name)) + { + action->OnDrawStarted(&skip); + } + if (skip == true) + return; + } + // Now the vertices can be flushed to the GPU. Everything following the CommitBuffer() call // must be careful to not upload any utility vertices, as the binding will be lost otherwise. const u32 num_indices = m_index_generator.GetIndexLen(); @@ -480,7 +512,7 @@ void VertexManagerBase::Flush() // Texture loading can cause palettes to be applied (-> uniforms -> draws). // Palette application does not use vertices, only a full-screen quad, so this is okay. // Same with GPU texture decoding, which uses compute shaders. - LoadTextures(); + g_texture_cache->BindTextures(used_textures); // Now we can upload uniforms, as nothing else will override them. GeometryShaderManager::SetConstants(); diff --git a/Source/Core/VideoCommon/VertexManagerBase.h b/Source/Core/VideoCommon/VertexManagerBase.h index e4d029e9c7..7efdec2ebd 100644 --- a/Source/Core/VideoCommon/VertexManagerBase.h +++ b/Source/Core/VideoCommon/VertexManagerBase.h @@ -6,6 +6,7 @@ #include #include +#include "Common/BitSet.h" #include "Common/CommonTypes.h" #include "Common/MathUtil.h" #include "VideoCommon/IndexGenerator.h" @@ -173,7 +174,8 @@ protected: void CalculateZSlope(NativeVertexFormat* format); void CalculateBinormals(NativeVertexFormat* format); - void LoadTextures(); + + BitSet32 UsedTextures() const; u8* m_cur_buffer_pointer = nullptr; u8* m_base_buffer_pointer = nullptr; diff --git a/Source/Core/VideoCommon/VertexShaderManager.cpp b/Source/Core/VideoCommon/VertexShaderManager.cpp index 6a7636d587..680d1ad3ee 100644 --- a/Source/Core/VideoCommon/VertexShaderManager.cpp +++ b/Source/Core/VideoCommon/VertexShaderManager.cpp @@ -85,7 +85,7 @@ void VertexShaderManager::Dirty() // Syncs the shader constant buffers with xfmem // TODO: A cleaner way to control the matrices without making a mess in the parameters field -void VertexShaderManager::SetConstants() +void VertexShaderManager::SetConstants(const std::vector& textures) { if (constants.missing_color_hex != g_ActiveConfig.iMissingColorValue) { @@ -302,7 +302,27 @@ void VertexShaderManager::SetConstants() g_stats.AddScissorRect(); } - if (bProjectionChanged || g_freelook_camera.GetController()->IsDirty()) + std::vector projection_actions; + if (g_ActiveConfig.bGraphicMods) + { + for (const auto action : + g_renderer->GetGraphicsModManager().GetProjectionActions(xfmem.projection.type)) + { + projection_actions.push_back(action); + } + + for (const auto& texture : textures) + { + for (const auto action : g_renderer->GetGraphicsModManager().GetProjectionTextureActions( + xfmem.projection.type, texture)) + { + projection_actions.push_back(action); + } + } + } + + if (bProjectionChanged || g_freelook_camera.GetController()->IsDirty() || + !projection_actions.empty()) { bProjectionChanged = false; @@ -384,6 +404,11 @@ void VertexShaderManager::SetConstants() if (g_freelook_camera.IsActive() && xfmem.projection.type == ProjectionType::Perspective) corrected_matrix *= g_freelook_camera.GetView(); + for (auto action : projection_actions) + { + action->OnProjection(&corrected_matrix); + } + memcpy(constants.projection.data(), corrected_matrix.data.data(), 4 * sizeof(float4)); g_freelook_camera.GetController()->SetClean(); diff --git a/Source/Core/VideoCommon/VertexShaderManager.h b/Source/Core/VideoCommon/VertexShaderManager.h index 90df8722ac..3bddf28fb0 100644 --- a/Source/Core/VideoCommon/VertexShaderManager.h +++ b/Source/Core/VideoCommon/VertexShaderManager.h @@ -4,6 +4,7 @@ #pragma once #include +#include #include "Common/CommonTypes.h" #include "VideoCommon/ConstantManager.h" @@ -19,7 +20,7 @@ public: static void DoState(PointerWrap& p); // constant management - static void SetConstants(); + static void SetConstants(const std::vector& textures); static void InvalidateXFRange(int start, int end); static void SetTexMatrixChangedA(u32 value); diff --git a/Source/Core/VideoCommon/VideoConfig.cpp b/Source/Core/VideoCommon/VideoConfig.cpp index 58893c9267..c1ff163f9c 100644 --- a/Source/Core/VideoCommon/VideoConfig.cpp +++ b/Source/Core/VideoCommon/VideoConfig.cpp @@ -145,6 +145,8 @@ void VideoConfig::Refresh() bFastTextureSampling = Config::Get(Config::GFX_HACK_FAST_TEXTURE_SAMPLING); bPerfQueriesEnable = Config::Get(Config::GFX_PERF_QUERIES_ENABLE); + + bGraphicMods = Config::Get(Config::GFX_MODS_ENABLE); } void VideoConfig::VerifyValidity() diff --git a/Source/Core/VideoCommon/VideoConfig.h b/Source/Core/VideoCommon/VideoConfig.h index f557f7bfe7..6ce7bed379 100644 --- a/Source/Core/VideoCommon/VideoConfig.h +++ b/Source/Core/VideoCommon/VideoConfig.h @@ -9,10 +9,12 @@ #pragma once +#include #include #include #include "Common/CommonTypes.h" +#include "VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.h" #include "VideoCommon/VideoCommon.h" // Log in two categories, and save three other options in the same byte @@ -111,6 +113,8 @@ struct VideoConfig final bool bBorderlessFullscreen = false; bool bEnableGPUTextureDecoding = false; int iBitrateKbps = 0; + bool bGraphicMods = false; + std::optional graphics_mod_config; // Hacks bool bEFBAccessEnable = false;