// Generate Apple's implementation once in this executable, not in the bridge. #define NS_PRIVATE_IMPLEMENTATION #define MTL_PRIVATE_IMPLEMENTATION #define CA_PRIVATE_IMPLEMENTATION #include #include #include #include "window_renderer.hpp" #include #include #include #include #include #include namespace { // Own exactly one Cocoa +1 reference. unique_ptr calls this deleter only for // non-null pointers; no C++ member call is made through a null pointer. // The pointee may still be retained by Metal or other framework owners. template struct Release { void operator()(T* object) const noexcept { object->release(); } }; template using MetalOwner = std::unique_ptr>; template MetalOwner retain(T* borrowed) noexcept { if (borrowed) borrowed->retain(); return MetalOwner(borrowed); } } struct OrchardRenderer::Impl { struct Slot { MetalOwner commands; MetalOwner readback; }; MetalOwner device; MetalOwner queue; std::array slots; std::size_t frames{}; std::size_t verified_frames{}; bool verify{}; Impl(MTL::Device* borrowed_device, bool verify_pixels) : device(retain(borrowed_device)), verify(verify_pixels) { if (!device) throw std::runtime_error("No Metal device"); queue.reset(device->newCommandQueue()); // Adopt the new-returned +1 reference. if (!queue) throw std::runtime_error("newCommandQueue failed"); if (verify) { for (auto& slot : slots) { // One pixel with an explicitly aligned row; kept until completion. slot.readback.reset(device->newBuffer(256, MTL::ResourceStorageModeShared)); if (!slot.readback) throw std::runtime_error("readback allocation failed"); } } std::cout << "Metal device: " << device->name()->utf8String() << '\n'; } ~Impl() { // Covers early exceptions as well as normal exit. No callbacks capture us. // Normal rendering polls completed slots instead of blocking each frame. for (auto& slot : slots) { if (slot.commands) slot.commands->waitUntilCompleted(); } } bool reclaim(Slot& slot, bool wait) { if (!slot.commands) return true; if (wait) slot.commands->waitUntilCompleted(); const auto status = slot.commands->status(); if (status == MTL::CommandBufferStatusError) { const auto* error = slot.commands->error(); throw std::runtime_error(std::string("Metal command failed: ") + (error ? error->localizedDescription()->utf8String() : "no diagnostic")); } if (status != MTL::CommandBufferStatusCompleted) return false; if (verify) { const auto* pixel = static_cast(slot.readback->contents()); // BGRA8Unorm, clear RGBA=(.125,.25,.5,1). Allow one quantization unit. constexpr std::array expected{128, 64, 32, 255}; for (std::size_t channel = 0; channel < expected.size(); ++channel) { const int delta = static_cast(pixel[channel]) - expected[channel]; if (delta < -1 || delta > 1) throw std::runtime_error("clear-color pixel oracle failed"); } ++verified_frames; } slot.commands.reset(); return true; } }; OrchardRenderer::OrchardRenderer(MTL::Device* device, bool verify_pixels) : impl_(std::make_unique(device, verify_pixels)) {} OrchardRenderer::~OrchardRenderer() = default; bool OrchardRenderer::has_capacity() { return impl_->reclaim(impl_->slots[impl_->frames % impl_->slots.size()], false); } void OrchardRenderer::draw(MTL::RenderPassDescriptor* pass, CA::MetalDrawable* drawable) { if (!pass || !drawable) return; // Drawable scarcity is an ordinary skipped frame. if (!has_capacity()) return; auto& slot = impl_->slots[impl_->frames % impl_->slots.size()]; auto commands = retain(impl_->queue->commandBuffer()); // Acquire +1 from an autoreleased result. if (!commands) throw std::runtime_error("commandBuffer failed"); auto* attachment = pass->colorAttachments()->object(0); attachment->setLoadAction(MTL::LoadActionClear); attachment->setStoreAction(MTL::StoreActionStore); attachment->setClearColor(MTL::ClearColor::Make(0.125, 0.25, 0.5, 1.0)); auto* encoder = commands->renderCommandEncoder(pass); if (!encoder) throw std::runtime_error("renderCommandEncoder failed"); encoder->endEncoding(); // Clearing attachments does not require a shader pipeline. if (impl_->verify) { auto* blit = commands->blitCommandEncoder(); if (!blit) throw std::runtime_error("blitCommandEncoder failed"); blit->copyFromTexture(drawable->texture(), 0, 0, MTL::Origin(0, 0, 0), MTL::Size(1, 1, 1), slot.readback.get(), 0, 256, 256); blit->endEncoding(); } commands->presentDrawable(drawable); slot.commands = std::move(commands); // Transfer the owned reference before commit. slot.commands->commit(); // No throwing operations between commit and recording the submission. ++impl_->frames; } void OrchardRenderer::finish() { for (auto& slot : impl_->slots) static_cast(impl_->reclaim(slot, true)); } std::size_t OrchardRenderer::submitted() const noexcept { return impl_->frames; } std::size_t OrchardRenderer::verified() const noexcept { return impl_->verified_frames; }