New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement Load/Store Local/Shared and Atomic shared using new instructions #5241
Conversation
Download the artifacts for this pull request: Experimental GUI (Avalonia)GUI-less (SDL2)Only for Developers
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
minor comments/observations
@@ -68,7 +68,7 @@ private static string GetExpression(CodeGenContext context, AstOperation operati | |||
|
|||
string args = string.Empty; | |||
|
|||
if (atomic && operation.StorageKind == StorageKind.StorageBuffer) | |||
if (atomic && (operation.StorageKind == StorageKind.StorageBuffer || operation.StorageKind == StorageKind.SharedMemory)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
super nit: might be more readable with is
and or
if it works in this context
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not a big fan of is
, or
, and
etc expressions. It is easier to understand like this for me.
if (!(operation.GetSource(0) is AstOperand bindingId) || bindingId.Type != OperandType.Constant) | ||
{ | ||
throw new InvalidOperationException($"First input of {operation.Inst} with {operation.StorageKind} storage must be a constant operand."); | ||
} | ||
|
||
MemoryDefinition memory = operation.StorageKind == StorageKind.LocalMemory | ||
? context.Config.Properties.LocalMemories[bindingId.Value] | ||
: context.Config.Properties.SharedMemories[bindingId.Value]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
worth making a helper function that takes a source index and returns a memory definition so it can be reused here and in InstGenMemory
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I plan to delete that code soon-ish, so I didn't worry too much about de-duplicating it.
public string Name { get; } | ||
public AggregateType Type { get; } | ||
public int ArrayLength { get; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
worth marking these as init?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it might imply the constructor too and you could default ArrayLength to 1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the benefit of using init
here? AFAIK with init
the property can only be set on the constructor, but get-only properties also behave like this already, they can only be initialized on the constructor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
my bad, force of habit and I'm partial to object initializer syntax. I also thought by making it a record the compiler would generate the constructor for you
|
||
Operand result = GenerateSharedAtomicCasLoop(context, wordOffset, id, (memValue) => | ||
{ | ||
return isMin |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
worth making this a local function? There's an implication with the closures but I don't recall offhand
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I could extract it into a separate function. Both are fine for me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's fine. I feel like @marco-carvalho might be interested in benchmarking this vs a local function?
|
||
namespace Ryujinx.Graphics.Shader.StructuredIr | ||
{ | ||
readonly struct MemoryDefinition |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
worth making it a record to get pass by reference semantics?
Needs a rebase |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good, sorry for the late review.
This is the last PR in the series of PRs to migrate memory related shader operations to the new
Load
andStore
IR instructions that also carry aStorageKind
, which I implemented a while ago. This one does the local and shared memory types, which are the only ones remaining. Those are not really buffer backed, rather they use some private memory. Shared memory is only valid for compute shaders, and is shared by all invocations on the workgroup, but is not externally visible. Meanwhile, local memory is only visible within a single invocation,There are 2 helper functions, one for storing small integer (8-bit and 16-bit) values on shared memory, and another for doing atomic operations on signed integers. The latter is only needed on GLSL since SPIR-V has separate instructions for signed and unsigned atomic operations, regardless of the variable type. Both were moved out of the backend and are now generated at the IR level, like what I did on the previous changes.
Unlike the old implementation, this one allows multiple local memories and shared memories to exist. The old one only allowed one. This can be useful if we combine shader stages in the future for example. It is also good for symmetry: We have the first operand of all
Load
andStore
operations reserved for the buffer binding number, which must be constant. For local and shared memory, they represent an ID that identifies the memory used.Advantages are the same of the previous PRs: More code sharing between GLSL and SPIR-V, makes new backends easier to implement, and simpler code (this code manages to remove almost 100 lines of code which is quite nice). It also makes the IR more flexible as we can have multiple separate memory storages now if we need them for different purposes.
This also fixes a regression from #4993, but not a user facing one: The
set
index value was not being set when generating GLSL code, which made GLSL shaders to not work properly on Vulkan. Currently there's no way for users to enable that, it must be enabled manually by changing the code.After that, there's still work to be done on the shader refactoring:
ShaderProperties
like buffers and memory.TranslatorContext
and deleteShaderConfig
.Operation
, stop depending onInstructionInfo
on GLSL generation and deleteInstructionInfo
.Index
field fromOperation
.IGpuAccessor
on the backends and store all required information onShaderProperties
.Testing is welcome.