aboutsummaryrefslogtreecommitdiff
path: root/src/shader_recompiler/ir_opt/position_pass.cpp
blob: 600a596bf3cb4136df70d7368796ac39ce50eb81 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <boost/container/small_vector.hpp>

#include "shader_recompiler/frontend/ir/basic_block.h"
#include "shader_recompiler/frontend/ir/ir_emitter.h"
#include "shader_recompiler/frontend/ir/value.h"
#include "shader_recompiler/ir_opt/passes.h"

namespace Shader::Optimization {

namespace {
struct PositionInst {
    IR::Inst* inst;
    IR::Block* block;
    IR::Attribute attr;
};
using PositionInstVector = boost::container::small_vector<PositionInst, 24>;
} // Anonymous namespace

void PositionPass(Environment& env, IR::Program& program) {
    if (env.ShaderStage() != Stage::VertexB || env.ReadViewportTransformState()) {
        return;
    }

    Info& info{program.info};
    info.uses_render_area = true;

    PositionInstVector to_replace;
    for (IR::Block* const block : program.post_order_blocks) {
        for (IR::Inst& inst : block->Instructions()) {
            switch (inst.GetOpcode()) {
            case IR::Opcode::SetAttribute: {
                const IR::Attribute attr{inst.Arg(0).Attribute()};
                switch (attr) {
                case IR::Attribute::PositionX:
                case IR::Attribute::PositionY: {
                    to_replace.push_back(PositionInst{.inst = &inst, .block = block, .attr = attr});
                    break;
                }
                default:
                    break;
                }
                break;
            }
            default:
                break;
            }
        }
    }

    for (PositionInst& position_inst : to_replace) {
        IR::IREmitter ir{*position_inst.block,
                         IR::Block::InstructionList::s_iterator_to(*position_inst.inst)};
        const IR::F32 value(position_inst.inst->Arg(1));
        const IR::F32F64 scale(ir.Imm32(2.f));
        const IR::F32 negative_one{ir.Imm32(-1.f)};
        switch (position_inst.attr) {
        case IR::Attribute::PositionX: {
            position_inst.inst->SetArg(
                1,
                ir.FPFma(value, ir.FPMul(ir.FPRecip(ir.RenderAreaWidth()), scale), negative_one));
            break;
        }
        case IR::Attribute::PositionY: {
            position_inst.inst->SetArg(
                1,
                ir.FPFma(value, ir.FPMul(ir.FPRecip(ir.RenderAreaHeight()), scale), negative_one));
            break;
        }
        default:
            break;
        }
    }
}
// Switch 2 fullscreen blit vertex shaders read a parameter block from constant bank 24 (lowered to
// bank 17, which games never bind) that the console provides itself. For a blit covering the whole
// render target it holds: texel size (0x0/0x4, again at 0x10/0x14), size (0x8/0xc), a UV scale of
// 1 (0x18/0x1c), the UV clamp rectangle 0,0..1,1 (0x30-0x3c, read by the fragment shader), a zero
// dither bias (0x40-0x48) and the integer source rectangle 0,0..W-1,H-1 (0x50-0x5c).
void BlitConstantsPass(Environment& env, IR::Program& program) {
    if (env.ShaderStage() != Stage::VertexB && env.ShaderStage() != Stage::Fragment) {
        return;
    }
    // Bank 24 is root table 0, which games may load themselves (3D methods 0x504/0x508; the buffer
    // cache binds it as bank 17): Fire Emblem: Fortune's Weave keeps its material push constants
    // there. A shader reading bank 17 outside the blit block reads such a table and keeps its reads,
    // as does one reading only words the blit block leaves zero (an unbound bank reads zero anyway;
    // the game's enemy health gauges read only 0x40 of their table).
    const auto in_blit_block = [](u32 offset) {
        return offset < 0x20 || (offset >= 0x30 && offset < 0x4c) || (offset >= 0x50 && offset < 0x60);
    };
    const auto is_nonzero_word = [](u32 offset) {
        return offset < 0x20 || offset == 0x38 || offset == 0x3c || offset == 0x58 || offset == 0x5c;
    };
    bool reads_nonzero_word{};
    for (IR::Block* const block : program.post_order_blocks) {
        for (IR::Inst& inst : block->Instructions()) {
            const IR::Opcode opcode{inst.GetOpcode()};
            if ((opcode != IR::Opcode::GetCbufU32 && opcode != IR::Opcode::GetCbufF32 &&
                 opcode != IR::Opcode::GetCbufU32x2) ||
                !inst.Arg(0).IsImmediate() || inst.Arg(0).U32() != 17 || !inst.Arg(1).IsImmediate()) {
                continue;
            }
            const u32 offset{inst.Arg(1).U32()};
            const bool pair{opcode == IR::Opcode::GetCbufU32x2};
            if (!in_blit_block(offset) || (pair && !in_blit_block(offset + 4))) {
                return;
            }
            reads_nonzero_word |= is_nonzero_word(offset) || (pair && is_nonzero_word(offset + 4));
        }
    }
    if (!reads_nonzero_word) {
        return;
    }
    bool used{};
    for (IR::Block* const block : program.post_order_blocks) {
        for (IR::Inst& inst : block->Instructions()) {
            const IR::Opcode opcode{inst.GetOpcode()};
            if (opcode != IR::Opcode::GetCbufU32 && opcode != IR::Opcode::GetCbufF32 &&
                opcode != IR::Opcode::GetCbufU32x2) {
                continue;
            }
            if (!inst.Arg(0).IsImmediate() || inst.Arg(0).U32() != 17 || !inst.Arg(1).IsImmediate()) {
                continue;
            }
            IR::IREmitter ir{*block, IR::Block::InstructionList::s_iterator_to(inst)};
            const IR::F32 width{ir.RenderAreaWidth()};
            const IR::F32 height{ir.RenderAreaHeight()};
            const auto word = [&](u32 offset) -> IR::U32 {
                switch (offset) {
                case 0x0:
                case 0x10:
                    return ir.BitCast<IR::U32>(IR::F32{ir.FPRecip(width)});
                case 0x4:
                case 0x14:
                    return ir.BitCast<IR::U32>(IR::F32{ir.FPRecip(height)});
                case 0x8:
                    return ir.BitCast<IR::U32>(width);
                case 0xc:
                    return ir.BitCast<IR::U32>(height);
                case 0x18:
                case 0x1c:
                case 0x38:
                case 0x3c:
                    return ir.Imm32(0x3f800000U);
                case 0x58:
                    return IR::U32{ir.ConvertFToU(32, ir.FPAdd(width, ir.Imm32(-1.0f)))};
                case 0x5c:
                    return IR::U32{ir.ConvertFToU(32, ir.FPAdd(height, ir.Imm32(-1.0f)))};
                default:
                    return ir.Imm32(0U);
                }
            };
            const u32 offset{inst.Arg(1).U32()};
            IR::Value value;
            if (opcode == IR::Opcode::GetCbufU32x2) {
                value = ir.CompositeConstruct(word(offset), word(offset + 4));
            } else if (opcode == IR::Opcode::GetCbufF32) {
                value = ir.BitCast<IR::F32>(word(offset));
            } else {
                value = word(offset);
            }
            inst.ReplaceUsesWith(value);
            used = true;
        }
    }
    if (used) {
        program.info.uses_render_area = true;
    }
}

} // namespace Shader::Optimization