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 | MTLTextureUsageRenderTarget;
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 uint32_t format,
130 int access) {
131#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
132 if (!metal_view_) {
133 return nullptr;
134 }
135 (void)access;
136
137 auto* view = static_cast<MTKView*>(metal_view_);
138 id<MTLDevice> device = view.device;
139 if (!device) {
140 device = MTLCreateSystemDefaultDevice();
141 view.device = device;
142 }
143 if (!device) {
144 return nullptr;
145 }
146
147 MTLPixelFormat pixel_format = GetMetalPixelFormatForSDL(format);
148 MTLTextureDescriptor* descriptor =
149 [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:pixel_format
150 width:width
151 height:height
152 mipmapped:NO];
153 descriptor.usage = MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget;
154 descriptor.storageMode = MTLStorageModeShared;
155
156 id<MTLTexture> texture = [device newTextureWithDescriptor:descriptor];
157 return texture ? (__bridge_retained void*)texture : nullptr;
158#else
159 (void)format;
160 (void)access;
161 return CreateTexture(width, height);
162#endif
163}
164
166#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
167 if (!texture) {
168 return;
169 }
170
171 SDL_Surface* surface = bitmap.surface();
172 if (!surface || !surface->pixels || surface->w <= 0 || surface->h <= 0) {
173 return;
174 }
175
176 id<MTLTexture> metal_texture = (__bridge id<MTLTexture>)texture;
177 const MTLPixelFormat pixel_format = metal_texture.pixelFormat;
178 // SDL_PIXELFORMAT_*8888 names are bit-order, not byte-order on little-endian.
179 // Use *_32 aliases so byte order matches Metal's expected RGBA/BGRA layout.
180 uint32_t target_format = SDL_PIXELFORMAT_RGBA32;
181 if (pixel_format == MTLPixelFormatBGRA8Unorm) {
182 target_format = SDL_PIXELFORMAT_BGRA32;
183 }
184
185 auto converted_surface =
186 std::unique_ptr<SDL_Surface, util::SDL_Surface_Deleter>(
187 platform::ConvertSurfaceFormat(surface, target_format));
188 if (!converted_surface || !converted_surface->pixels) {
189 return;
190 }
191
192 MTLRegion region = {
193 {0, 0, 0},
194 {static_cast<NSUInteger>(converted_surface->w),
195 static_cast<NSUInteger>(converted_surface->h),
196 1}};
197 [metal_texture replaceRegion:region
198 mipmapLevel:0
199 withBytes:converted_surface->pixels
200 bytesPerRow:converted_surface->pitch];
201#else
202 (void)texture;
203 (void)bitmap;
204#endif
205}
206
208 if (!texture) {
209 return;
210 }
211 if (render_target_ == texture) {
212 render_target_ = nullptr;
213 }
214 staging_buffers_.erase(texture);
215#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
216 CFRelease(texture);
217#else
218 (void)texture;
219#endif
220}
221
222bool MetalRenderer::LockTexture(TextureHandle texture, SDL_Rect* rect,
223 void** pixels, int* pitch) {
224#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
225 if (!texture || !pixels || !pitch) {
226 return false;
227 }
228
229 id<MTLTexture> metal_texture = (__bridge id<MTLTexture>)texture;
230 const int width = static_cast<int>(metal_texture.width);
231 const int height = static_cast<int>(metal_texture.height);
232 const int bytes_per_pixel = BytesPerPixel(metal_texture.pixelFormat);
233 const int row_pitch = width * bytes_per_pixel;
234 if (row_pitch <= 0 || height <= 0) {
235 return false;
236 }
237
238 auto& staging = staging_buffers_[texture];
239 staging.width = width;
240 staging.height = height;
241 staging.pitch = row_pitch;
242 staging.data.resize(static_cast<size_t>(row_pitch) *
243 static_cast<size_t>(height));
244
245 if (rect) {
246 rect->x = 0;
247 rect->y = 0;
248 rect->w = width;
249 rect->h = height;
250 }
251
252 *pixels = staging.data.data();
253 *pitch = row_pitch;
254 return true;
255#else
256 (void)texture;
257 (void)rect;
258 (void)pixels;
259 (void)pitch;
260 return false;
261#endif
262}
263
265#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
266 if (!texture) {
267 return;
268 }
269 auto it = staging_buffers_.find(texture);
270 if (it == staging_buffers_.end()) {
271 return;
272 }
273
274 id<MTLTexture> metal_texture = (__bridge id<MTLTexture>)texture;
275 auto& staging = it->second;
276 if (staging.data.empty()) {
277 return;
278 }
279
280 MTLRegion region = {
281 {0, 0, 0},
282 {static_cast<NSUInteger>(staging.width),
283 static_cast<NSUInteger>(staging.height),
284 1}};
285 [metal_texture replaceRegion:region
286 mipmapLevel:0
287 withBytes:staging.data.data()
288 bytesPerRow:staging.pitch];
289#else
290 (void)texture;
291#endif
292}
293
296
299
300void MetalRenderer::RenderCopy(TextureHandle texture, const SDL_Rect* srcrect,
301 const SDL_Rect* dstrect) {
302#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
303 if (!texture || !render_target_ || !command_queue_) {
304 return;
305 }
306
307 id<MTLTexture> source = (__bridge id<MTLTexture>)texture;
308 id<MTLTexture> dest = (__bridge id<MTLTexture>)render_target_;
309
310 int src_x = srcrect ? srcrect->x : 0;
311 int src_y = srcrect ? srcrect->y : 0;
312 int src_w = srcrect ? srcrect->w : static_cast<int>(source.width);
313 int src_h = srcrect ? srcrect->h : static_cast<int>(source.height);
314
315 int dst_x = dstrect ? dstrect->x : 0;
316 int dst_y = dstrect ? dstrect->y : 0;
317
318 src_w = std::min(src_w, static_cast<int>(source.width) - src_x);
319 src_h = std::min(src_h, static_cast<int>(source.height) - src_y);
320
321 if (src_w <= 0 || src_h <= 0) {
322 return;
323 }
324
325 MTLOrigin src_origin = {static_cast<NSUInteger>(src_x),
326 static_cast<NSUInteger>(src_y),
327 0};
328 MTLSize src_size = {static_cast<NSUInteger>(src_w),
329 static_cast<NSUInteger>(src_h),
330 1};
331 MTLOrigin dst_origin = {static_cast<NSUInteger>(dst_x),
332 static_cast<NSUInteger>(dst_y),
333 0};
334
335 id<MTLCommandQueue> queue = (__bridge id<MTLCommandQueue>)command_queue_;
336 id<MTLCommandBuffer> command_buffer = [queue commandBuffer];
337 id<MTLBlitCommandEncoder> blit = [command_buffer blitCommandEncoder];
338 [blit copyFromTexture:source
339 sourceSlice:0
340 sourceLevel:0
341 sourceOrigin:src_origin
342 sourceSize:src_size
343 toTexture:dest
344 destinationSlice:0
345 destinationLevel:0
346 destinationOrigin:dst_origin];
347 [blit endEncoding];
348 [command_buffer commit];
349 [command_buffer waitUntilCompleted];
350#else
351 (void)texture;
352 (void)srcrect;
353 (void)dstrect;
354#endif
355}
356
360
361void MetalRenderer::SetDrawColor(SDL_Color color) {
362 (void)color;
363}
364
368
370 metal_view_ = view;
371}
372
373} // namespace gfx
374} // 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.
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.