游戏开发图形学【免费下载链接】PiccoloPiccolo (formerly Pilot) – mini game engine for games104项目地址https://gitcode.com/gh_mirrors/pi/Piccolo点击查看免费下载本篇技术指南以 example_apple_metal/README.md 为骨架结合 Piccolo 游戏引擎仓库内嵌的 Dear ImGui 源码与完整示例工程深入讲解如何在 iOS/OSX 上通过 Metal 原生渲染 Dear ImGui 界面包括基于 Xcode 9「cross-platform」模板的工程结构、MTKViewDelegate驱动的主循环、输入事件桥接以及imgui_impl_metal渲染后端的底层实现原理。读完本文你将掌握一套可直接对照main.mm移植的 Metal Dear ImGui 集成方案并能理解官方 README 中「推荐改用 GLFW/SDL 示例」这一结论背后的工程原因。一、示例定位理解官方 README 的第一句话原 README 开篇即明确了本示例的性质它展示的是Dear ImGui 与 Metal 的集成方式代码基础是 Xcode 9 自带的「cross-platform」游戏模板。而仓库中该目录的实际组成也与之吻合engine/3rdparty/imgui/examples/example_apple_metal/ ├── example_apple_metal.xcodeproj/project.pbxproj # 同时包含 iOS / macOS 两个 target ├── iOS/ │ ├── Info-iOS.plist │ └── LaunchScreen.storyboard ├── macOS/ │ ├── Info-macOS.plist │ └── MainMenu.storyboard └── main.mm # 唯一的源码文件从 project.pbxproj 可以看到两个独立 targetimguiex iOSSDKROOT iphoneos、IPHONEOS_DEPLOYMENT_TARGET 10.0、TARGETED_DEVICE_FAMILY 1,2和imguiex macOSSDKROOT macosx、MACOSX_DEPLOYMENT_TARGET 10.12并链接了Metal.framework、MetalKit.framework与GameController.framework后者服务于 iOS 手柄输入。README 紧接着给出的两条关键建议是阅读本示例前必须理解的前提Consider basing your work off the example_glfw_metal/ or example_sdl_metal/ examples. They are better supported and will be portable unlike this one.即官方更推荐以 example_glfw_metal 或 example_sdl_metal 为基础进行二次开发——它们由 GLFW/SDL 提供跨平台窗口与输入抽象可移植性更好、维护更活跃而本示例的窗口生命周期、输入事件分发完全绑定 AppKit/UIKit属于演示如何把 ImGui 塞进 Apple 原生应用的参考实现。这一点也可以从后端头文件得到印证imgui_impl_osx.h自述为[ALPHA] Early backend, not well tested。二、核心源码逐段解析main.mm整个示例的核心是 main.mm它用#if TARGET_OS_OSX / #else宏实现了同一份代码同时编译为 macOS 与 iOS 应用。2.1 头文件与条件编译#import Foundation/Foundation.h #if TARGET_OS_OSX #import Cocoa/Cocoa.h #else #import UIKit/UIKit.h #endif #import Metal/Metal.h #import MetalKit/MetalKit.h #include imgui.h #include imgui_impl_metal.h #if TARGET_OS_OSX #include imgui_impl_osx.h interface AppViewController : NSViewController #else interface AppViewController : UIViewController #endif关键点AppViewController在 macOS 上继承NSViewController在 iOS 上继承UIViewController且都遵守MTKViewDelegate协议——这是 Apple「cross-platform」模板的典型做法渲染后端统一使用imgui_impl_metal.h平台后端只在 macOS 分支引入imgui_impl_osx.hiOS 分支没有独立平台后端触摸输入由AppViewController自己桥接见 2.5 节。2.2 初始化流程initWithNibName:bundle:初始化顺序与 Dear ImGui 官方推荐流程完全一致_device MTLCreateSystemDefaultDevice(); // 1. 创建 MTLDevice _commandQueue [_device newCommandQueue]; // 2. 创建命令队列 if (!self.device) { NSLog(Metal is not supported); abort(); } IMGUI_CHECKVERSION(); // 3. 版本校验宏 ImGui::CreateContext(); // 4. 创建 ImGui 上下文 ImGuiIO io ImGui::GetIO(); (void)io; // io.ConfigFlags | ImGuiConfigFlags_NavEnableKeyboard; // 可开启键盘导航 // io.ConfigFlags | ImGuiConfigFlags_NavEnableGamepad; // 可开启手柄导航 ImGui::StyleColorsDark(); // 5. 设置深色风格 // ImGui::StyleColorsClassic(); ImGui_ImplMetal_Init(_device); // 6. 初始化 Metal 渲染后端若MTLCreateSystemDefaultDevice()返回 nil模拟器或无 Metal 支持的设备示例直接abort()退出字体加载部分全部被注释默认使用内嵌字体如需加载 TTF 字体可参考被注释的io.Fonts-AddFontFromFileTTF(../../misc/fonts/Roboto-Medium.ttf, 16.0f)等写法——注意 C/C 字符串字面量中反斜杠需写成\\字体光栅化与上传到纹理的时机在ImGui_ImplMetal_NewFrame首次调用时由ImFontAtlas::Build()触发底层对应ImGui_ImplMetal_CreateFontsTexture()。2.3 视图装载loadView与viewDidLoad-(void)loadView { self.view [[MTKView alloc] initWithFrame:CGRectMake(0, 0, 1200, 720)]; } -(void)viewDidLoad { [super viewDidLoad]; self.mtkView.device self.device; self.mtkView.delegate self; #if TARGET_OS_OSX NSTrackingArea *trackingArea [[NSTrackingArea alloc] initWithRect:NSZeroRect options:NSTrackingMouseMoved | NSTrackingInVisibleRect | NSTrackingActiveAlways owner:self userInfo:nil]; [self.view addTrackingArea:trackingArea]; ImGui_ImplOSX_Init(self.view); #endif }MTKView是 MetalKit 提供的便捷视图内置CAMetalLayer自动提供currentDrawable与currentRenderPassDescriptor并回调MTKViewDelegatemacOS 分支必须添加NSTrackingArea选项为NSTrackingMouseMoved | NSTrackingInVisibleRect | NSTrackingActiveAlways否则鼠标悬停移动事件mouseMoved不会被投递到视图ImGui_ImplOSX_Init(self.view)完成 macOS 平台后端初始化。2.4 主循环drawInMTKView:drawInMTKView:由 MetalKit 在每帧自动调用相当于渲染循环回调完整覆盖了 Dear ImGui 的新帧 → 构建 UI → 渲染三段式-(void)drawInMTKView:(MTKView*)view { ImGuiIO io ImGui::GetIO(); io.DisplaySize.x view.bounds.size.width; io.DisplaySize.y view.bounds.size.height; // Retina 缩放系数 CGFloat framebufferScale view.window.screen.backingScaleFactor ?: NSScreen.mainScreen.backingScaleFactor; // macOS // 或 view.window.screen.scale ?: UIScreen.mainScreen.scale; // iOS io.DisplayFramebufferScale ImVec2(framebufferScale, framebufferScale); io.DeltaTime 1 / float(view.preferredFramesPerSecond ?: 60); idMTLCommandBuffer commandBuffer [self.commandQueue commandBuffer]; MTLRenderPassDescriptor* renderPassDescriptor view.currentRenderPassDescriptor; if (renderPassDescriptor nil) { [commandBuffer commit]; return; } ImGui_ImplMetal_NewFrame(renderPassDescriptor); ImGui_ImplOSX_NewFrame(view); // 仅 macOS ImGui::NewFrame(); // ... 构建 UI演示窗口 / 自定义窗口 ... ImGui::Render(); ImDrawData* draw_data ImGui::GetDrawData(); renderPassDescriptor.colorAttachments[0].clearColor MTLClearColorMake(clear_color.x*clear_color.w, clear_color.y*clear_color.w, clear_color.z*clear_color.w, clear_color.w); idMTLRenderCommandEncoder renderEncoder [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor]; [renderEncoder pushDebugGroup:Dear ImGui rendering]; ImGui_ImplMetal_RenderDrawData(draw_data, commandBuffer, renderEncoder); [renderEncoder popDebugGroup]; [renderEncoder endEncoding]; [commandBuffer presentDrawable:view.currentDrawable]; [commandBuffer commit]; }几个值得注意的实现细节帧率与 DeltaTimeio.DeltaTime 1 / float(view.preferredFramesPerSecond ?: 60)即直接以 MTKView 的 preferred 帧率倒数作为两帧间隔preferredFramesPerSecond为 0 时回退为 60 FPS空渲染通道保护view.currentRenderPassDescriptor为 nil如窗口最小化、drawable 不可用时示例仍提交 commandBuffer 后提前 return避免崩溃clearColor 预乘 alpha清屏颜色使用MTLClearColorMake(r*a, g*a, b*a, a)即 RGB 分量乘以 alpha这与 ImGui 着色器输出 alpha 预乘的约定一致RenderPassDescriptor 复用ImGui_ImplMetal_NewFrame(renderPassDescriptor)会把当前帧的 framebuffer 配置sampleCount、color/depth/stencil 像素格式记录到后端的FramebufferDescriptor供管线状态缓存查表使用。2.5 输入事件桥接这是本示例最Apple 原生的部分——输入不经过任何跨平台抽象层而是直接覆写视图控制器的事件方法。macOS 分支使用imgui_impl_osx平台后端-(void)mouseDown:(NSEvent *)event { ImGui_ImplOSX_HandleEvent(event, self.view); } -(void)mouseDragged:(NSEvent *)event { ImGui_ImplOSX_HandleEvent(event, self.view); } -(void)scrollWheel:(NSEvent *)event { ImGui_ImplOSX_HandleEvent(event, self.view); } // ... 以及 rightMouse/otherMouse 系列、mouseMoved 等全部转发iOS 分支无独立平台后端直接手写触摸→鼠标映射-(void)updateIOWithTouchEvent:(UIEvent *)event { UITouch *anyTouch event.allTouches.anyObject; CGPoint touchLocation [anyTouch locationInView:self.view]; ImGuiIO io ImGui::GetIO(); io.MousePos ImVec2(touchLocation.x, touchLocation.y); BOOL hasActiveTouch NO; for (UITouch *touch in event.allTouches) if (touch.phase ! UITouchPhaseEnded touch.phase ! UITouchPhaseCancelled) { hasActiveTouch YES; break; } io.MouseDown[0] hasActiveTouch; } -(void)touchesBegan:withEvent: { [self updateIOWithTouchEvent:event]; } -(void)touchesMoved:withEvent: { [self updateIOWithTouchEvent:event]; } // touchesCancelled / touchesEnded 同理源码注释直言这套触摸映射 super cheesy/hacky把屏幕上任意触摸当作左键按下不处理多点触控多指同时触摸时光标行为会非常不稳定仅单指操作时表现出乎意料地可用。2.6 AppDelegate 与 main 函数macOS 分支的AppDelegate手动创建无 nib 的NSWindowNSWindowStyleMaskTitled | Closable | Resizable | MiniaturizableNSBackingStoreBuffered将其contentViewController设为AppViewControllermain()调用NSApplicationMainiOS 分支则走UIApplicationMaindidFinishLaunchingWithOptions的标准路径。值得留意的是initWithNibName:bundle:中的注释// FIXME: This example doesnt have proper cleanup...——本示例没有实现ImGui_ImplMetal_Shutdown()/ImGui::DestroyContext()等清理逻辑README 建议以此为蓝本时要自行补齐。三、渲染后端底层imgui_impl_metal 的支撑原理示例的 UI 渲染完全由 backends/imgui_impl_metal.mm 承担公共接口见 backends/imgui_impl_metal.h。理解它才能明白示例代码中几个调用的真实作用。3.1 公共 API 与特性矩阵bool ImGui_ImplMetal_Init(idMTLDevice device); void ImGui_ImplMetal_NewFrame(MTLRenderPassDescriptor* renderPassDescriptor); void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, idMTLCommandBuffer commandBuffer, idMTLRenderCommandEncoder commandEncoder);后端头文件声明的特性已支持用户纹理绑定以MTLTexture作为ImTextureID16 位索引的大网格64k 顶点缺失多视口 / 平台窗口ImGuiConfigFlags_ViewportsEnable不可用ImGui_ImplMetal_Init会设置io.BackendRendererName imgui_impl_metal并声明ImGuiBackendFlags_RendererHasVtxOffset支持大网格另提供 Metal C 绑定支持在imconfig.h中定义IMGUI_IMPL_METAL_CPP后可通过MTL::Device/MTL::CommandBuffer等 C 对象调用同一套 API。3.2 三大核心机制1渲染管线状态缓存MetalContextFramebufferDescriptor每帧NewFrame时把MTLRenderPassDescriptor的 sampleCount、color/depth/stencil 像素格式打包为FramebufferDescriptor实现了NSCopying、hash、isEqual作为 key 在renderPipelineStateCache字典中查找/创建MTLRenderPipelineState。由于同一应用内 framebuffer 配置极少变化缓存命中率应接近 100%源码注释原话。2运行时编译内嵌 MSL 着色器管线所需的 vertex/fragment 函数通过[device newLibraryWithSource:...]在运行时编译内嵌的 Metal Shading Language 源码见_renderPipelineStateForFramebufferDescriptor:顶点着色器用projectionMatrix正交投影变换ImDrawVertposition/texCoords/color片元着色器用线性采样器对字体/用户纹理采样并与顶点色相乘。3环形缓冲复用MetalBuffer缓存每帧把ImDrawVert顶点与ImDrawIdx索引 memcpy 进共享存储的MTLBufferbuffer 用完后通过[commandBuffer addCompletedHandler:]回收到bufferCache供下一帧复用且每超过 1 秒清理一次长时间未复用的 buffer避免无限增长。3.3 渲染状态与正交投影setupRenderState:中视口尺寸为DisplaySize × FramebufferScaleRetina 屏上即像素尺寸znear0, zfar1顶点着色器的 uniform 是手工构造的正交投影矩阵将DisplayPos..DisplayPosDisplaySize的屏幕空间映射到 NDC关闭背面剔除MTLCullModeNone设置depthWriteEnabledNO、depthCompareFunctionAlways的深度模板状态颜色混合使用标准的 alpha 混合srcAlpha/OneMinusSrcAlphaalpha 通道为One/OneMinusSrcAlpha保证输出缓冲 alpha 保留。3.4 绘制命令遍历renderDrawData:遍历每个ImDrawList的ImDrawCmd处理用户回调ImDrawCallback_ResetRenderState会重新调用setupRenderState:将裁剪矩形投影到 framebuffer 空间并钳制到视口范围内setScissorRect:不接受越界值空裁剪区直接continueElemCount 0的 draw call 也被跳过drawIndexedPrimitives:校验不接受绑定pcmd-GetTexID()对应的MTLTexture到texture(0)依据sizeof(ImDrawIdx) 2选择MTLIndexTypeUInt16/MTLIndexTypeUInt32调用drawIndexedPrimitives:MTLPrimitiveTypeTriangle。四、为什么官方建议改用 GLFW / SDL 示例对照仓库中另外两个示例可直观看出 README 建议的工程考量维度example_apple_metalexample_glfw_metalexample_sdl_metal窗口/输入抽象AppKit / UIKit 原生GLFWglfwGetCocoaWindow取 NSWindowSDL2SDL_RenderGetMetalLayer取 CAMetalLayer平台后端macOS 用imgui_impl_osxALPHA 状态iOS 手写触摸映射imgui_impl_glfw跨平台、成熟imgui_impl_sdl跨平台、成熟可移植性仅 Apple 平台macOS/Linux/Windows 通用macOS/Linux/Windows 通用工程形态Xcode 工程 storyboard/plist单一main.mm纯代码单一main.mm纯代码GLFW 版本的核心流程example_glfw_metal/main.mm与 SDL 版本example_sdl_metal/main.mm结构高度一致MTLCreateSystemDefaultDevice()创建设备 →ImGui_ImplMetal_Init(device)→ 用CAMetalLayer挂到原生窗口 → 主循环中glfwPollEvents()/SDL_PollEvent()→ImGui_ImplXXX_NewFrame()→ImGui_ImplMetal_RenderDrawData()。其中 SDL 版需要先SDL_SetHint(SDL_HINT_RENDER_DRIVER, metal)并创建 Metal renderer再通过SDL_RenderGetMetalLayer(renderer)取得CAMetalLayer并设置layer.pixelFormat MTLPixelFormatBGRA8Unorm。两版都具备完整的 Shutdown 清理ImGui_ImplMetal_Shutdown()、ImGui::DestroyContext()这也是 README 评价其better supported的直接体现。五、集成要点总结把本示例移植到自有 Apple 应用时需按以下顺序落实工程配置链接Metal.framework、MetalKit.frameworkiOS 侧按需链接GameController.framework可参考本示例的SDKROOT、MACOSX_DEPLOYMENT_TARGET 10.12、IPHONEOS_DEPLOYMENT_TARGET 10.0设定初始化IMGUI_CHECKVERSION()→ImGui::CreateContext()→StyleColorsDark()→ImGui_ImplMetal_Init(_device)macOS 追加ImGui_ImplOSX_Init(view)每帧更新DisplaySize/DisplayFramebufferScale/DeltaTime→ImGui_ImplMetal_NewFrame(renderPassDescriptor)→ImGui_ImplXXX_NewFrame()→ImGui::NewFrame()→ 构建 UI →ImGui::Render()→ 设置 clearColor 与 render encoder →ImGui_ImplMetal_RenderDrawData()→presentDrawablecommit输入macOS 将NSEvent全量转发给ImGui_ImplOSX_HandleEvent务必添加NSTrackingArea以收到mouseMovediOS 参照updateIOWithTouchEvent:实现触摸→鼠标映射或自行接入更完善的触摸后端清理本示例缺省正式工程应在销毁时调用ImGui_ImplMetal_Shutdown()、ImGui_ImplOSX_Shutdown()、ImGui::DestroyContext()方案选型若目标是多平台或长期维护直接以 GLFW/SDL 两个 Metal 示例为起点更符合官方推荐。关联代码路径速查示例源码 · 工程配置 · Metal 渲染后端 · Metal 后端接口 · OSX 平台后端 · GLFWMetal 示例 · SDLMetal 示例赞分享游戏开发图形学【免费下载链接】PiccoloPiccolo (formerly Pilot) – mini game engine for games104项目地址https://gitcode.com/gh_mirrors/pi/Piccolo点击查看免费下载相关推荐OBS Studio libobs-metal 深度解析:面向 Apple Silicon 的 Swift Metal 渲染后端OBS Studio libobs metal 深度解析:面向 Apple Silicon 的 Swift Metal 渲染后端 libobs metal 是音视频直播屏幕录制桌面应用视频Dear ImGui Metal后端开发苹果生态系统下的原生界面解决方案Dear ImGui Metal后端开发苹果生态系统下的原生界面解决方案 引言为什么选择Metal后端 在苹果生态系统中开发图形界面应用时开发者常常面临UI组件前端桌面应用图形学Native SDK gpu-surface 示例深度解析在原生视图树中集成 Metal GPU 表面Native SDK gpu surface 示例深度解析在原生视图树中集成 Metal GPU 表面 本文围绕 examples/gpu surface/R桌面应用跨平台创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考