Skip to content
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

Metal: Multiview support #98803

Merged
merged 1 commit into from
Nov 10, 2024
Merged

Conversation

stuartcarnie
Copy link
Contributor

@stuartcarnie stuartcarnie commented Nov 3, 2024

This PR adds multi-view support for the Metal rendering device driver.

Testing

GodotStereoDemo

A screenshot of XR enabled and rendering multiple views, thanks to @BastiaanOlij

CleanShot 2024-11-04 at 09 28 46@2x

Clearing attachments

It is not easy to test clearing a subset of an attachment when multi-view is enabled in Godot outside of XR, so I validated it by creating a multi-view frame buffer and clearing it and then validating the output in the Metal Debugger.

The following screenshot has the following properties:

  • A 1024x1024 frame buffer with a view count of 2.
  • Clear rect of { x=10, y=10, w=50, h=50 }
  • Clear colour of Color.AQUAMARINE
  • Clear depth of 0.5

CleanShot 2024-11-04 at 09 20 22@2x

In this screenshot, we can see the following:

  • Two views of a multi-view frame buffer, ❶ and ❷
  • The colour texture, ❸ and ❹, is cleared to aquamarine at the rectangle 10, 10, 60, 60
  • The depth texture, ❺ and ❻, is cleared to 0.5 at the rectangle 10, 10, 60, 60

The GDScript to set up the multi-view frame buffer:

var framebuffer2: RID
var clearColors2 := PackedColorArray([Color.AQUAMARINE])
var framebuf_texture
var d_framebuf_texture

func create_multiview():
	
	rd = RenderingServer.get_rendering_device()
	
	
	#  <<  --  FRAMEBUFFER  --  >>
	
	var attachments = []
	
	#  <<  -- COLOR  -- >>
	
	var tex_format := RDTextureFormat.new()
	tex_format.texture_type = RenderingDevice.TEXTURE_TYPE_2D_ARRAY
	tex_format.height = 1024
	tex_format.width = 1024
	tex_format.array_layers = 2
	tex_format.format = RenderingDevice.DATA_FORMAT_R8G8B8A8_UNORM
	tex_format.usage_bits = RenderingDevice.TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | RenderingDevice.TEXTURE_USAGE_CAN_COPY_FROM_BIT  

	var tex_view := RDTextureView.new()
	framebuf_texture = rd.texture_create(tex_format, tex_view)
	print("framebuffe texture : ", rd.texture_is_valid(framebuf_texture))
	
	var af := RDAttachmentFormat.new()
	af.set_format(tex_format.format)
	af.set_samples(RenderingDevice.TEXTURE_SAMPLES_1)
	af.usage_flags = tex_format.usage_bits
	attachments.push_back(af)
	
	#  WORKS, but is actually slower than with texture_get_data()
	
	#t2drd = Texture2DRD.new()
	#t2drd.texture_rd_rid = RID()
	#t2drd.texture_rd_rid = framebuf_texture	
	
	#  <<  --  DEPTH  --  >>
	
	var dtex_format := RDTextureFormat.new()
	var dtex_view := RDTextureView.new()
	dtex_format.texture_type = RenderingDevice.TEXTURE_TYPE_2D_ARRAY
	dtex_format.height = 1024
	dtex_format.width = 1024
	dtex_format.array_layers = 2
	dtex_format.format = RenderingDevice.DATA_FORMAT_D16_UNORM
	dtex_format.usage_bits = RenderingDevice.TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT
	d_framebuf_texture = rd.texture_create(dtex_format, dtex_view)
	print("depth buffer texture : ", rd.texture_is_valid(d_framebuf_texture))
	
	
	var daf := RDAttachmentFormat.new()
	daf.set_format(dtex_format.format)
	daf.set_samples(RenderingDevice.TEXTURE_SAMPLES_1)
	daf.usage_flags = dtex_format.usage_bits
	attachments.push_back(daf)
	
	# ---
	
	var framebuf_format := rd.framebuffer_format_create(attachments, 2)
	framebuffer2 = rd.framebuffer_create([framebuf_texture, d_framebuf_texture], framebuf_format, 2)
	print("framebuffer : ", rd.framebuffer_is_valid(framebuffer2))

and some code to clear it:

func draw_test():
	var dlist := rd.draw_list_begin(framebuffer2,
		RenderingDevice.INITIAL_ACTION_CLEAR, RenderingDevice.FINAL_ACTION_READ,
		RenderingDevice.INITIAL_ACTION_CLEAR, RenderingDevice.FINAL_ACTION_READ,
		clearColors2, 0.5, 3, Rect2(10, 10, 50, 50))
	rd.draw_list_end()

@stuartcarnie stuartcarnie requested a review from a team as a code owner November 3, 2024 22:18
@stuartcarnie
Copy link
Contributor Author

@BastiaanOlij and @RevoluPowered might be interested in this PR

@BastiaanOlij
Copy link
Contributor

Amazing stuart! I'm going to see how far I get with implementing XR_KHR_metal_enable ontop of this, that should be a great test for it.

@dsnopek
Copy link
Contributor

dsnopek commented Nov 4, 2024

Thanks!

I can't review the actual rendering code (it's beyond me) but I tested this with a simple XR project that I threw together using MobileVRInterface and it worked great in my testing on an M2 Mac mini!

However, with verbose output turned on, I'm getting this debug message spammed:

image

And in the console, it's spamming blank lines (probably one per frame?)

So, there's something wrong with that vformat(), but we probably shouldn't be printing something every frame anyway.

@stuartcarnie
Copy link
Contributor Author

Thanks @dsnopek it's now removed!

@@ -96,6 +96,9 @@
MDRenderPipeline *rp = (MDRenderPipeline *)p;

if (render.encoder == nil) {
// This error would happen if the render pass failed.
ERR_FAIL_NULL_MSG(render.desc, "Render pass descriptor is null");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ERR_FAIL_NULL_MSG(render.desc, "Render pass descriptor is null");
ERR_FAIL_NULL_MSG(render.desc, "Render pass descriptor is null.");

@@ -649,7 +671,9 @@

MDAttachment const &attachment = pass.attachments[idx];

id<MTLTexture> tex = fb.textures[idx];
id<MTLTexture> tex = fb.get_texture(idx);
ERR_FAIL_NULL_MSG(tex, "Frame buffer color texture is null");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ERR_FAIL_NULL_MSG(tex, "Frame buffer color texture is null");
ERR_FAIL_NULL_MSG(tex, "Frame buffer color texture is null.");

@@ -662,7 +686,8 @@
attachmentCount += 1;
uint32_t idx = subpass.depth_stencil_reference.attachment;
MDAttachment const &attachment = pass.attachments[idx];
id<MTLTexture> tex = fb.textures[idx];
id<MTLTexture> tex = fb.get_texture(idx);
ERR_FAIL_NULL_MSG(tex, "Frame buffer depth / stencil texture is null");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ERR_FAIL_NULL_MSG(tex, "Frame buffer depth / stencil texture is null");
ERR_FAIL_NULL_MSG(tex, "Frame buffer depth / stencil texture is null.");

@@ -702,8 +727,15 @@
uint32_t p_base_vertex,
uint32_t p_first_instance) {
DEV_ASSERT(type == MDCommandBufferStateType::Render);
ERR_FAIL_NULL_MSG(render.pipeline, "No pipeline set for render command buffer");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ERR_FAIL_NULL_MSG(render.pipeline, "No pipeline set for render command buffer");
ERR_FAIL_NULL_MSG(render.pipeline, "No pipeline set for render command buffer.");

@@ -751,8 +783,15 @@
int32_t p_vertex_offset,
uint32_t p_first_instance) {
DEV_ASSERT(type == MDCommandBufferStateType::Render);
ERR_FAIL_NULL_MSG(render.pipeline, "No pipeline set for render command buffer");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ERR_FAIL_NULL_MSG(render.pipeline, "No pipeline set for render command buffer");
ERR_FAIL_NULL_MSG(render.pipeline, "No pipeline set for render command buffer.");

@@ -770,6 +809,8 @@

void MDCommandBuffer::render_draw_indexed_indirect(RDD::BufferID p_indirect_buffer, uint64_t p_offset, uint32_t p_draw_count, uint32_t p_stride) {
DEV_ASSERT(type == MDCommandBufferStateType::Render);
ERR_FAIL_NULL_MSG(render.pipeline, "No pipeline set for render command buffer");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ERR_FAIL_NULL_MSG(render.pipeline, "No pipeline set for render command buffer");
ERR_FAIL_NULL_MSG(render.pipeline, "No pipeline set for render command buffer.");

@@ -794,6 +835,8 @@

void MDCommandBuffer::render_draw_indirect(RDD::BufferID p_indirect_buffer, uint64_t p_offset, uint32_t p_draw_count, uint32_t p_stride) {
DEV_ASSERT(type == MDCommandBufferStateType::Render);
ERR_FAIL_NULL_MSG(render.pipeline, "No pipeline set for render command buffer");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ERR_FAIL_NULL_MSG(render.pipeline, "No pipeline set for render command buffer");
ERR_FAIL_NULL_MSG(render.pipeline, "No pipeline set for render command buffer.");

@@ -1811,6 +1817,20 @@ void deserialize(BufReader &p_reader) {
}
}

for (auto f : resources.builtin_inputs) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs a proper value, auto is not allowed

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably be:

Suggested change
for (auto f : resources.builtin_inputs) {
for (const BuiltInResource &f : resources.builtin_inputs) {

From the files

}

if (!r_shader_meta.has_multiview) {
for (auto f : resources.builtin_outputs) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
for (auto f : resources.builtin_outputs) {
for (const BuiltInResource &f : resources.builtin_outputs) {

* Adds support for multiview
* Returns native handles for more driver resources
@stuartcarnie
Copy link
Contributor Author

Thanks @AThousandShips – resolved all your feedback

@Repiteo Repiteo merged commit 246e8e9 into godotengine:master Nov 10, 2024
20 checks passed
@Repiteo
Copy link
Contributor

Repiteo commented Nov 10, 2024

Thanks!

@stuartcarnie stuartcarnie deleted the metal_multiview branch November 12, 2024 21:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants