yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
metal_renderer.mm
Go to the documentation of this file.
2
3#if defined(__APPLE__)
4#include <TargetConditionals.h>
5#import <CoreFoundation/CoreFoundation.h>
6#endif
7
8#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
9#import <Metal/Metal.h>
10#import <MetalKit/MetalKit.h>
11#endif
12
13#include "app/gfx/core/bitmap.h"
15#include "util/log.h"
16#include "util/sdl_deleter.h"
17#include <algorithm>
18
19namespace yaze {
20namespace gfx {
21
22namespace {
23
24#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
25MTLPixelFormat GetMetalPixelFormatForSDL(uint32_t format) {
26 switch (format) {
27 case SDL_PIXELFORMAT_ARGB8888:
28 case SDL_PIXELFORMAT_BGRA8888:
29 return MTLPixelFormatBGRA8Unorm;
30 case SDL_PIXELFORMAT_RGBA8888:
31 case SDL_PIXELFORMAT_ABGR8888:
32 return MTLPixelFormatRGBA8Unorm;
33 default:
34 return MTLPixelFormatRGBA8Unorm;
35 }
36}
37
38int BytesPerPixel(MTLPixelFormat format) {
39 switch (format) {
40 case MTLPixelFormatBGRA8Unorm:
41 case MTLPixelFormatRGBA8Unorm:
42 return 4;
43 default:
44 return 4;
45 }
46}
47#endif
48
49} // namespace
50
54
55bool MetalRenderer::Initialize(SDL_Window* window) {
56 (void)window;
57#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
58 if (!metal_view_) {
59 LOG_WARN("MetalRenderer", "Metal view not attached");
60 return false;
61 }
62 auto* view = static_cast<MTKView*>(metal_view_);
63 id<MTLDevice> device = view.device;
64 if (!device) {
65 device = MTLCreateSystemDefaultDevice();
66 view.device = device;
67 }
68 if (!device) {
69 LOG_WARN("MetalRenderer", "Failed to create Metal device");
70 return false;
71 }
72 if (!command_queue_) {
73 id<MTLCommandQueue> queue = [device newCommandQueue];
74 command_queue_ = (__bridge_retained void*)queue;
75 }
76 return true;
77#else
78 return false;
79#endif
80}
81
83 if (command_queue_) {
84 CFRelease(command_queue_);
85 command_queue_ = nullptr;
86 }
87 render_target_ = nullptr;
88}
89
91#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
92 if (!metal_view_) {
93 return nullptr;
94 }
95
96 auto* view = static_cast<MTKView*>(metal_view_);
97 id<MTLDevice> device = view.device;
98 if (!device) {
99 device = MTLCreateSystemDefaultDevice();
100 view.device = device;
101 }
102 if (!device) {
103 return nullptr;
104 }
105
106#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
107 MTLPixelFormat default_format = MTLPixelFormatRGBA8Unorm;
108#else
109 MTLPixelFormat default_format = MTLPixelFormatBGRA8Unorm;
110#endif
111 MTLTextureDescriptor* descriptor =
112 [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:default_format
113 width:width
114 height:height
115 mipmapped:NO];
116 descriptor.usage = MTLTextureUsageShaderRead;
117 descriptor.storageMode = MTLStorageModeShared;
118
119 id<MTLTexture> texture = [device newTextureWithDescriptor:descriptor];
120 return texture ? (__bridge_retained void*)texture : nullptr;
121#else
122 (void)width;
123 (void)height;
124 return nullptr;
125#endif
126}
127
129#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
130 if (!metal_view_) {
131 return nullptr;
132 }
133
134 auto* view = static_cast<MTKView*>(metal_view_);
135 id<MTLDevice> device = view.device;
136 if (!device) {
137 device = MTLCreateSystemDefaultDevice();
138 view.device = device;
139 }
140 if (!device) {
141 return nullptr;
142 }
143
144#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
145 MTLPixelFormat default_format = MTLPixelFormatRGBA8Unorm;
146#else
147 MTLPixelFormat default_format = MTLPixelFormatBGRA8Unorm;
148#endif
149 MTLTextureDescriptor* descriptor =
150 [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:default_format
151 width:width
152 height:height
153 mipmapped:NO];
154 descriptor.usage = MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget;
155 descriptor.storageMode = MTLStorageModeShared;
156
157 id<MTLTexture> texture = [device newTextureWithDescriptor:descriptor];
158 return texture ? (__bridge_retained void*)texture : nullptr;
159#else
160 (void)width;
161 (void)height;
162 return nullptr;
163#endif
164}
165
167 uint32_t format,
168 int access) {
169#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
170 if (!metal_view_) {
171 return nullptr;
172 }
173
174 auto* view = static_cast<MTKView*>(metal_view_);
175 id<MTLDevice> device = view.device;
176 if (!device) {
177 device = MTLCreateSystemDefaultDevice();
178 view.device = device;
179 }
180 if (!device) {
181 return nullptr;
182 }
183
184 MTLPixelFormat pixel_format = GetMetalPixelFormatForSDL(format);
185 MTLTextureDescriptor* descriptor =
186 [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:pixel_format
187 width:width
188 height:height
189 mipmapped:NO];
190 descriptor.usage = MTLTextureUsageShaderRead;
191 if (access == SDL_TEXTUREACCESS_TARGET) {
192 descriptor.usage |= MTLTextureUsageRenderTarget;
193 }
194 descriptor.storageMode = MTLStorageModeShared;
195
196 id<MTLTexture> texture = [device newTextureWithDescriptor:descriptor];
197 return texture ? (__bridge_retained void*)texture : nullptr;
198#else
199 (void)format;
200 (void)access;
201 return CreateTexture(width, height);
202#endif
203}
204
206#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
207 if (!texture) {
208 return;
209 }
210
211 SDL_Surface* surface = bitmap.surface();
212 if (!surface || !surface->pixels || surface->w <= 0 || surface->h <= 0) {
213 return;
214 }
215
216 id<MTLTexture> metal_texture = (__bridge id<MTLTexture>)texture;
217 const MTLPixelFormat pixel_format = metal_texture.pixelFormat;
218 // SDL_PIXELFORMAT_*8888 names are bit-order, not byte-order on little-endian.
219 // Use *_32 aliases so byte order matches Metal's expected RGBA/BGRA layout.
220 uint32_t target_format = SDL_PIXELFORMAT_RGBA32;
221 if (pixel_format == MTLPixelFormatBGRA8Unorm) {
222 target_format = SDL_PIXELFORMAT_BGRA32;
223 }
224
225 auto converted_surface =
226 std::unique_ptr<SDL_Surface, util::SDL_Surface_Deleter>(
227 platform::ConvertSurfaceFormat(surface, target_format));
228 if (!converted_surface || !converted_surface->pixels) {
229 return;
230 }
231
232 MTLRegion region = {
233 {0, 0, 0},
234 {static_cast<NSUInteger>(converted_surface->w),
235 static_cast<NSUInteger>(converted_surface->h),
236 1}};
237 [metal_texture replaceRegion:region
238 mipmapLevel:0
239 withBytes:converted_surface->pixels
240 bytesPerRow:converted_surface->pitch];
241#else
242 (void)texture;
243 (void)bitmap;
244#endif
245}
246
248 if (!texture) {
249 return;
250 }
251 if (render_target_ == texture) {
252 render_target_ = nullptr;
253 }
254 staging_buffers_.erase(texture);
255#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
256 CFRelease(texture);
257#else
258 (void)texture;
259#endif
260}
261
262bool MetalRenderer::LockTexture(TextureHandle texture, SDL_Rect* rect,
263 void** pixels, int* pitch) {
264#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
265 if (!texture || !pixels || !pitch) {
266 return false;
267 }
268
269 id<MTLTexture> metal_texture = (__bridge id<MTLTexture>)texture;
270 const int width = static_cast<int>(metal_texture.width);
271 const int height = static_cast<int>(metal_texture.height);
272 const int bytes_per_pixel = BytesPerPixel(metal_texture.pixelFormat);
273 const int row_pitch = width * bytes_per_pixel;
274 if (row_pitch <= 0 || height <= 0) {
275 return false;
276 }
277
278 auto& staging = staging_buffers_[texture];
279 staging.width = width;
280 staging.height = height;
281 staging.pitch = row_pitch;
282 staging.data.resize(static_cast<size_t>(row_pitch) *
283 static_cast<size_t>(height));
284
285 if (rect) {
286 rect->x = 0;
287 rect->y = 0;
288 rect->w = width;
289 rect->h = height;
290 }
291
292 *pixels = staging.data.data();
293 *pitch = row_pitch;
294 return true;
295#else
296 (void)texture;
297 (void)rect;
298 (void)pixels;
299 (void)pitch;
300 return false;
301#endif
302}
303
305#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
306 if (!texture) {
307 return;
308 }
309 auto it = staging_buffers_.find(texture);
310 if (it == staging_buffers_.end()) {
311 return;
312 }
313
314 id<MTLTexture> metal_texture = (__bridge id<MTLTexture>)texture;
315 auto& staging = it->second;
316 if (staging.data.empty()) {
317 return;
318 }
319
320 MTLRegion region = {
321 {0, 0, 0},
322 {static_cast<NSUInteger>(staging.width),
323 static_cast<NSUInteger>(staging.height),
324 1}};
325 [metal_texture replaceRegion:region
326 mipmapLevel:0
327 withBytes:staging.data.data()
328 bytesPerRow:staging.pitch];
329#else
330 (void)texture;
331#endif
332}
333
336
339
340void MetalRenderer::RenderCopy(TextureHandle texture, const SDL_Rect* srcrect,
341 const SDL_Rect* dstrect) {
342#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
343 if (!texture || !render_target_ || !command_queue_) {
344 return;
345 }
346
347 id<MTLTexture> source = (__bridge id<MTLTexture>)texture;
348 id<MTLTexture> dest = (__bridge id<MTLTexture>)render_target_;
349
350 int src_x = srcrect ? srcrect->x : 0;
351 int src_y = srcrect ? srcrect->y : 0;
352 int src_w = srcrect ? srcrect->w : static_cast<int>(source.width);
353 int src_h = srcrect ? srcrect->h : static_cast<int>(source.height);
354
355 int dst_x = dstrect ? dstrect->x : 0;
356 int dst_y = dstrect ? dstrect->y : 0;
357
358 src_w = std::min(src_w, static_cast<int>(source.width) - src_x);
359 src_h = std::min(src_h, static_cast<int>(source.height) - src_y);
360
361 if (src_w <= 0 || src_h <= 0) {
362 return;
363 }
364
365 MTLOrigin src_origin = {static_cast<NSUInteger>(src_x),
366 static_cast<NSUInteger>(src_y),
367 0};
368 MTLSize src_size = {static_cast<NSUInteger>(src_w),
369 static_cast<NSUInteger>(src_h),
370 1};
371 MTLOrigin dst_origin = {static_cast<NSUInteger>(dst_x),
372 static_cast<NSUInteger>(dst_y),
373 0};
374
375 id<MTLCommandQueue> queue = (__bridge id<MTLCommandQueue>)command_queue_;
376 id<MTLCommandBuffer> command_buffer = [queue commandBuffer];
377 id<MTLBlitCommandEncoder> blit = [command_buffer blitCommandEncoder];
378 [blit copyFromTexture:source
379 sourceSlice:0
380 sourceLevel:0
381 sourceOrigin:src_origin
382 sourceSize:src_size
383 toTexture:dest
384 destinationSlice:0
385 destinationLevel:0
386 destinationOrigin:dst_origin];
387 [blit endEncoding];
388 [command_buffer commit];
389 [command_buffer waitUntilCompleted];
390#else
391 (void)texture;
392 (void)srcrect;
393 (void)dstrect;
394#endif
395}
396
400
401void MetalRenderer::SetDrawColor(SDL_Color color) {
402 (void)color;
403}
404
408
410 metal_view_ = view;
411}
412
413} // namespace gfx
414} // namespace yaze
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:67
SDL_Surface * surface() const
Definition bitmap.h:379
void RenderCopy(TextureHandle texture, const SDL_Rect *srcrect, const SDL_Rect *dstrect) override
Copies a portion of a texture to the current render target.
void Shutdown() override
Shuts down the renderer and releases all associated resources.
std::unordered_map< TextureHandle, StagingBuffer > staging_buffers_
TextureHandle CreateTexture(int width, int height) override
Creates a new, empty texture.
TextureHandle CreateRenderTargetTexture(int width, int height) override
Creates a texture intended to be used as a render target.
bool Initialize(SDL_Window *window) override
Initializes the renderer with a given window.
void SetMetalView(void *view)
void SetRenderTarget(TextureHandle texture) override
Sets the render target for subsequent drawing operations.
TextureHandle CreateTextureWithFormat(int width, int height, uint32_t format, int access) override
Creates a new texture with a specific pixel format.
void DestroyTexture(TextureHandle texture) override
Destroys a texture and frees its associated resources.
void UpdateTexture(TextureHandle texture, const Bitmap &bitmap) override
Updates a texture with the pixel data from a Bitmap.
void Present() override
Presents the back buffer to the screen, making the rendered content visible.
void UnlockTexture(TextureHandle texture) override
void * GetBackendRenderer() override
Provides an escape hatch to get the underlying, concrete renderer object.
void SetDrawColor(SDL_Color color) override
Sets the color used for drawing operations (e.g., Clear).
void Clear() override
Clears the entire render target with the current draw color.
bool LockTexture(TextureHandle texture, SDL_Rect *rect, void **pixels, int *pitch) override
#define LOG_WARN(category, format,...)
Definition log.h:107
void * TextureHandle
An abstract handle representing a texture.
Definition irenderer.h:47
SDL_Surface * ConvertSurfaceFormat(SDL_Surface *surface, uint32_t format, uint32_t flags=0)
Convert a surface to a specific pixel format.
Definition sdl_compat.h:361
SDL2/SDL3 compatibility layer.