[2.1] [Metal] Sending arbitrary values to a shader

Discussion area about developing with Ogre-Next (2.1, 2.2 and beyond)


Post Reply
Nucleartree
Kobold
Posts: 28
Joined: Tue Apr 04, 2017 9:10 pm
Location: Cardiff, UK
x 16

[2.1] [Metal] Sending arbitrary values to a shader

Post by Nucleartree »

Hi everyone.

I've been trying to do a port job to metal of the imgui implementation described at viewtopic.php?t=89081.
I have the original port working and tested on opengl on linux also using the pso changes mentioned here viewtopic.php?t=93889.
My next job is going to be porting what I have to metal (macos), however I've hit a snag.

The imgui port relies on being able to send data to the shaders, for example it sends its own projection matrix to the shader like this:

Code: Select all

Ogre::Matrix4 projMatrix(2.0f / io.DisplaySize.x, 0.0f, 0.0f, -1.0f,
		0.0f, -2.0f / io.DisplaySize.y, 0.0f, 1.0f,
		0.0f, 0.0f, -1.0f, 0.0f,
		0.0f, 0.0f, 0.0f, 1.0f);
mPass->getVertexProgramParameters()->setNamedConstant("ProjectionMatrix", projMatrix);
I didn't know much about metal starting this, so I read up a bit on it and found that you can talk to shaders with the command buffer.
Then looking through some of the ogre example shaders I found things like:

Code: Select all

struct Params
{
	float4x4 worldViewProj;
	float3x4 worldScaledMatrix;
	float3 probeCameraPosScaled;
};

vertex PS_INPUT main_metal
(
	VS_INPUT input [[stage_in]],
	constant Params &p	[[buffer(PARAMETER_SLOT)]]
)
{

}
So clearly ogre is sending stuff to the shader, and it seems to land in the form of the Params struct.

I've been able to send things like the world view projection matrix with stuff like this:

Code: Select all

fragment_program Example_ps_metal metal
{
  source example.metal
  default_params
  {
    param_named_auto worldViewProj worldviewproj_matrix
  }
}
I've also tried setting up a render_quad pass with the compositor just to test some shaders, and things like this work to render something simple:

Code: Select all

#include <metal_stdlib>
using namespace metal;

struct PS_INPUT
{
	float2 uv0;
};

fragment float4 main_metal
(
	PS_INPUT inPs [[stage_in]]
)
{
  if(inPs.uv0.x < 0.5){
    return float4( 1.0, 1.0, 1.0, 1.0);
  }else{
    return float4( 1.0, 0.0, 1.0, 1.0);
  }

}
However I've so far only been able to send auto named parameters or simple things like the uv coordinates.
What I'd like is to be able to send arbitrary types of data to the shader, like the custom projection matrix above.

Does anyone know how this is achieved with the ogre api for a metal shader?
My assumption would be that there is some way to specify values at indexes in the buffer, but I haven't been able to find anything like this in the samples or api docs.

I've noticed a few references to
constant Params &p [[buffer(PARAMETER_SLOT)]]
in the samples. Does PARAMETER_SLOT have anything to do with what I might want?
I just can't seem to find anything that sends over values from the c++.
What I have found makes me think this is some way for the hlms to send values.

I noticed there was a bit at the end of the porting manual about buffers, but this mostly went over my head.
Would I be right in thinking that this has nothing to do with what I'm trying to do?

Does setNamedConstant() even work for metal?
Whenever I tried to use it it was telling me that the constant in the shader couldn't be found.
I might have set it up wrong, but to me that approach seems very glsl, so maybe this is the case?

I feel like I'm just missing something obvious.
As a next step I'd like to be able to do something like render a triangle with a colour set as a constant from c++.
If anyone knows anything about Ogre's metal implementation I'd greatly appreciate your expertise!
User avatar
dark_sylinc
OGRE Team Member
OGRE Team Member
Posts: 5296
Joined: Sat Jul 21, 2007 4:55 pm
Location: Buenos Aires, Argentina
x 1278
Contact:

Re: [2.1] [Metal] Sending arbitrary values to a shader

Post by dark_sylinc »

Nucleartree wrote: Thu Apr 04, 2019 11:56 pm Does setNamedConstant() even work for metal?
Whenever I tried to use it it was telling me that the constant in the shader couldn't be found.
I might have set it up wrong, but to me that approach seems very glsl, so maybe this is the case?
Yes. In the example you posted, to fill probeCameraPosScaled you would call:

Code: Select all

vertexProgramParams->setNamedConstant( "probeCameraPosScaled", Vector3( 0, 1, 2 ) );
Likewise, you can set "worldViewProj" via setNamedConstant instead of relying on setNamedAutoConstant (the C++ equivalent of using param_named_auto).

In this sense, parameters are set the same way they are in our GL3+ and D3D11 RenderSystems.

Checkout HdrUtils.cpp from the samples, which sets parameters from C++ to our HDR shaders and has all 3 variants (GL, D3D, Metal).

Code: Select all

I've noticed a few references to 
constant Params &p	[[buffer(PARAMETER_SLOT)]]
in the samples. Does PARAMETER_SLOT have anything to do with what I might want?
Yes, without the PARAMETER_SLOT, setNamedConstant & setNamedAutoConstant won't work on p.probeCameraPosScaled and co.
Note that only one struct can be bound to PARAMETER_SLOT.

So basically where GLSL would type:

Code: Select all

uniform mat worldViewProj;
uniform mat3x4 worldScaledMatrix;
uniform vec3 probeCameraPosScaled;
In Metal you would have to instead type:

Code: Select all

struct Params
{
	float4x4 worldViewProj;
	float3x4 worldScaledMatrix;
	float3 probeCameraPosScaled;
};

vertex blahblah main_metal( constant Params &p	[[buffer(PARAMETER_SLOT)]] )
Nucleartree
Kobold
Posts: 28
Joined: Tue Apr 04, 2017 9:10 pm
Location: Cardiff, UK
x 16

Re: [2.1] [Metal] Sending arbitrary values to a shader

Post by Nucleartree »

Hey.

After Dark_sylinc's advice I managed to figure it out.
The cause seemed to stem from the fact that only one struct can be bound to the PARAMETER_SLOT.

With the quad I was rendering I had just used the Quad_vs.metal vertex shader from the samples, with my own fragment shader.
However the vertex shader was actually mapping its own matrix to the parameter slot:

Code: Select all

#include <metal_stdlib>
using namespace metal;

struct VS_INPUT
{
	float4 position [[attribute(VES_POSITION)]];
	float2 uv0 [[attribute(VES_TEXTURE_COORDINATES0)]];
};

struct PS_INPUT
{
	float2 uv0;
	float4 gl_Position [[position]];
};

vertex PS_INPUT main_metal
(
	VS_INPUT input [[stage_in]],
	constant float4x4 &worldViewProj [[buffer(PARAMETER_SLOT)]] //The vertex shader binds a projection matrix to the parameter slot.
)
{
	PS_INPUT outVs;

	outVs.gl_Position	= ( worldViewProj * input.position ).xyzw;
	outVs.uv0			= input.uv0;

	return outVs;
}
This meant that as soon as I went to map my own stuff in the fragment shader:

Code: Select all

#include <metal_stdlib>
using namespace metal;

struct PS_INPUT
{
    float2 uv0;
    float4 gl_Position [[position]];
};

struct Params
{
    float4 value;
};

fragment float4 main_metal
(
 PS_INPUT inPs [[stage_in]],
 constant Params &p [[buffer(PARAMETER_SLOT)]] //The fragment shader tries to bind something to the same buffer.
 )
{
    
    return p.value;
    
}
This:

Code: Select all

psParams->setNamedConstant( "value", Ogre::Vector4(0, 1, 0, 1));
would fail with:

Code: Select all

Ogre::ItemIdentityException: OGRE EXCEPTION(5:ItemIdentityException): Parameter called value does not exist.
The vertex shader still needed to accept the world view matrix, so the solution was to change it like this:

Code: Select all

#include <metal_stdlib>
using namespace metal;

struct VS_INPUT
{
	float4 position [[attribute(VES_POSITION)]];
	float2 uv0 [[attribute(VES_TEXTURE_COORDINATES0)]];
};

struct PS_INPUT
{
	float2 uv0;
	float4 gl_Position [[position]];
 	float4 value; //The value is provided as part of the input to the fragment shader.
};

struct Params{
    float4x4 worldViewProj;
    float4 value; //My custom value. This is received by the vertex function and passed onto the fragment function.
};

vertex PS_INPUT main_metal
(
	VS_INPUT input [[stage_in]],
	constant Params &p [[buffer(PARAMETER_SLOT)]] //The parameter slot is now bound to the struct, which takes the projection matrix and my custom value.
)
{
	PS_INPUT outVs;

	outVs.gl_Position	= ( p.worldViewProj * input.position ).xyzw;
	outVs.uv0			= input.uv0;
	outVs.value =  p.value; //Here I setup that value to go to the fragment shader.

	return outVs;
}
My fragment shader now looks like this:

Code: Select all

#include <metal_stdlib>
using namespace metal;

struct PS_INPUT
{
    float2 uv0;
    float4 gl_Position [[position]];
    float4 value;
};

fragment float4 main_metal
(
 PS_INPUT inPs [[stage_in]]
 )
{
    
    return inPs.value; //Value arrives as part of the PS_INPUT struct.
    
}
Once these changes are made I can set constant values from c++!
Thanks for your help!
User avatar
dark_sylinc
OGRE Team Member
OGRE Team Member
Posts: 5296
Joined: Sat Jul 21, 2007 4:55 pm
Location: Buenos Aires, Argentina
x 1278
Contact:

Re: [2.1] [Metal] Sending arbitrary values to a shader

Post by dark_sylinc »

I'm glad that it worked out for you.

Just a note: If you're only passing the value "as is" from the Vertex Shader to the Pixel Shader, then you should consider declaring a struct/parameter in the Pixel Shader, and setting the param from C++ to the pixel shader.
This is a performance optimization.
Post Reply