Vala プログラミング

WebGPU プログラミング

おなが@京都先端科学大

Swift Vulkan & GNUStepSwiftBridge

Swif言語を用いてVulkanによる描画にトライしました。
GNUStepSwiftBridgeと組合せて、NSWindow上のボタンから実行できるように
しています。Vulkanの描画は、glfwを使ってXWindowに描画します。

[ 実行結果 ]
Triangle

UniformBuffer

ネット検索すると、Swift言語で書かれたVulkanのライブラリやサンプルはいくつか
見つかりますが、エラー無しに実行できるものは少ししかありません。
その中で、
  henrybetts/swift-vulkan: A natural Swift interface for Vulkan
  https://github.com/henrybetts/swift-vulkan
を参考にしました。

[ サンプルプログラムの実行結果 ]
DemoClearColor

(/swift-vulkan/Demos/swift-vulkan-demosディレクトリで
 swift run DemoClearColorを実行)

DemoTriangle

(shaderのパスを "./Sources/DemoTriangle/shaders/vert.spv" のように変更)
glfwを使って、XWindowに表示しています。

今回は、このサンプルプログラムとGNUStepSwiftBridgeを組合せました。
また、UniformBufferによる描画を追加しました。

ディレクトリ配置

AppVulkan
   Package.swift
   /AppKit
   /Sources
       /CGLFW
       /CVulkan
       /Vulkan
       /SGLMath
       /Main
           /Resources
           /shaders
           /shaders_ubo
           CheckResource.swift
           main.swift
           AppDelegate.swift
           AppError.swift
           AppClearColor.swift
           AppTriangle.swift
           AppUniformBuffer.swift
           color.swift
           file.swift

AppKitの配置を変更しています。内容は前回同様です。

AppKit
   Package.swift
   /Sources
      /Headers
      /AppKit
      /AppKitGNUStep
      /FoubdationGNUStep
      /libobjc2
      /ObjCSwiftInterop

Package.swift
   // swift-tools-version: 5.8
   // The swift-tools-version declares the minimum version of Swift required to build this package.

   import PackageDescription

   let package = Package(
      name: "AppKit",
      products: [
         // Products define the executables and libraries a package produces, making them visible to other packages.
         .library(name: "libobjc", targets: ["libobjc2"]),
         .library(name: "AppKitGNUStep", targets: ["AppKitGNUStep"]),
	 .library(name: "FoundationGNUStep", targets: ["FoundationGNUStep"]),
	 .library(name: "ObjCSwiftInterop",  targets: ["ObjCSwiftInterop"]),
         .library(name: "AppKit", targets: ["AppKit"]),
      ],
      targets: [
         // Targets are the basic building blocks of a package, defining a module or a test suite.
         // Targets can depend on other targets in this package and products from dependencies.
         .target(name: "ObjCSwiftInterop"),
         .target(
            name: "AppKit",
	    dependencies: ["libobjc2", "AppKitGNUStep", "ObjCSwiftInterop", "FoundationGNUStep"]),
	 .systemLibrary(name: "libobjc2"),
	 .systemLibrary(name: "AppKitGNUStep"),
	 .systemLibrary(name: "FoundationGNUStep"),
      ]
   )

パッケージのPackage.swift

// swift-tools-version: 5.8
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
   name: "AppVulkan",
   products: [
      // Products define the executables and libraries a package produces, making them visible to other packages
      .library(name: "Vulkan", targets: ["Vulkan"]),
      .library(name: "SGLMath", targets: ["SGLMath"]),
   ],
   dependencies: [
      .package(path: "./AppKit")
   ],
   targets: [
      // Targets are the basic building blocks of a package, defining a module or a test suite.
      // Targets can depend on other targets in this package and products from dependencies.
      .executableTarget(
         name: "Main",
	 dependencies: ["AppKit", "CGLFW", "Vulkan", "SGLMath"]
      ),

      .target(name: "Vulkan",
         dependencies: ["CVulkan"]),
      .target(name: "SGLMath"),
		
      .systemLibrary(name: "CVulkan"),
      .systemLibrary(name: "CGLFW"),
   ]
)

/CGLFW, /Cvulkan, /Vulkan はVulkanのサンプルプログラムをそのまま利用して
います。
/SGLMathは、SwiftGL Math Library(https://github.com/SwiftGL/Math)を利用
します。

AppError.swift

// The Swift Programming Language
// https://docs.swift.org/swift-book

struct AppError: Error, CustomStringConvertible {
    let description: String
}

AppDelegate.swift

// The Swift Programming Language
// https://docs.swift.org/swift-book

import Foundation
import AppKit
import ObjCSwiftInterop

let AppWindowRect = CGRect(x: 250, y: 100, width: 600, height: 500)

let fileManager = FileManager.default

class AppDelegate: NSApplicationDelegate {
	lazy var window = NSWindow(CGRect(x: 0, y: 500, width: 200, height: 200))
	lazy var view = UIView()
	lazy var button  = NSButton()
	lazy var button2 = NSButton()
	lazy var button3 = NSButton()

	override func applicationDidFinishLaunching(notification: Any?) {
		window.orderFront(sender: self)
		window.setTitle(NSString(string: "AppKit Test"))

		button.setTitle(NSString(string: "ClearColor"))
		button.frame = .init(x: 30, y: 10, width: 140, height: 32)
		button.onAction = { button in
			AppClearColor().run()
		}
		button2.setTitle(NSString(string: "Triangle"))
		button2.frame = .init(x: 30, y: 50, width: 140, height: 32)
		button2.onAction = { button in
			AppTriangle().run()
		}
		button3.setTitle(NSString(string: "UniformBuffer"))
		button3.frame = .init(x: 30, y: 90, width: 140, height: 32)
		button3.onAction = { button in
			AppUniformBuffer().run()
		}

		view.frame = .init(x: 0, y: 0, width: 300, height: 300)
		view.addSubview(button)
		view.addSubview(button2)
		view.addSubview(button3)
		window.setContentView(view)
	}
}

AppUniformBuffer.swift

import Foundation
import CVulkan
import CGLFW
import Vulkan
import SGLMath

//struct Vector2
//struct Vector3
//struct Vertex
// defined in AppTriangle

class AppUniformBuffer {
    /// A GLFW window handle.
    var window: OpaquePointer!

    /// The size of the window.
    let windowSize = Extent2D(width: UInt32(AppWindowRect.size.width), height: UInt32(AppWindowRect.size.height))
    
    /// Initializes GLFW and creates a window.
    func createWindow() throws {
        guard glfwInit() == GLFW_TRUE else {
            throw AppError(description: "Failed to initialize GLFW.")
        }
        glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API)
        glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE)
        glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE)
        guard let window = glfwCreateWindow(Int32(windowSize.width), Int32(windowSize.height), "Vulkan Triangle", nil, nil) else {
            throw AppError(description: "Failed to create window.")
        }
        glfwSetWindowPos(window, Int32(AppWindowRect.origin.x), Int32(AppWindowRect.origin.y))
	    glfwShowWindow(window)

        self.window = window

        glfwSetKeyCallback(window, key_callback)
    }

    /// A Vulkan instance.
    var instance: Instance!
    /// Creates a Vulkan instance with the extensions required by GLFW.
    func createInstance() throws {
        var count: UInt32 = 0
        let extensionsPtr = glfwGetRequiredInstanceExtensions(&count)
        var extensions = UnsafeBufferPointer(start: extensionsPtr, count: Int(count)).map{ String(cString: $0!) }
        var layers: [String] = []
        
        #if DEBUG
        extensions.append(VK_EXT_DEBUG_UTILS_EXTENSION_NAME)
        layers.append("VK_LAYER_KHRONOS_validation")
        #endif

        let entry = try Entry()
        instance = try entry.createInstance(
            createInfo: .init(
                flags: [],
                applicationInfo: nil,
                enabledLayerNames: layers,
                enabledExtensionNames: extensions
            )
        )
    }

    /// A Vulkan surface.
    var surface: SurfaceKHR!
    /// Creates a surface, via GLFW.
    func createSurface() throws {
        var cSurface: VkSurfaceKHR!
        let result = instance.withHandle { handle in
            glfwCreateWindowSurface(handle, window, nil, &cSurface)
        }
        guard result.rawValue >= 0 else {
            throw Result(rawValue: result.rawValue)!
        }
        surface = SurfaceKHR(handle: cSurface, instance: instance)
    }

    /// A Vulkan physical device.
    var physicalDevice: PhysicalDevice!
    /// The index of a graphics queue family.
    var graphicsFamilyIndex: UInt32!
    /// The index of a presentation queue family.
    var presentationFamilyIndex: UInt32!
    /// Selects a suitable physical device, as well as the graphics and presentation queue families.
    func selectPhysicalDevice() throws {
        let physicalDevices = try instance.getPhysicalDevices()
        
        for physicalDevice in physicalDevices {
            // Device must support the VK_KHR_swapchain extension
            let extensions = try physicalDevice.getDeviceExtensionProperties(layerName: nil)
            guard extensions.contains(where: { $0.extensionName == VK_KHR_SWAPCHAIN_EXTENSION_NAME }) else {
                continue
            }
            
            // Device must support the b8g8r8a8Srgb / srgbNonLinear surface format.
            let surfaceFormats = try physicalDevice.getSurfaceFormatsKHR(surface: surface)
            guard surfaceFormats.contains(where: { $0.format == .b8g8r8a8Srgb && $0.colorSpace == .srgbNonlinear }) else {
                continue
            }
            
            // Device must have a graphics queue family.
            let queueFamilies = physicalDevice.getQueueFamilyProperties()
            guard let graphicsFamilyIndex = queueFamilies.firstIndex(where: { $0.queueFlags.contains(.graphics) }) else {
                continue
            }
            
            // Device must have a presentation queue family that is compatible with the surface.
            let queueFamilyIndices = 0..<UInt32(queueFamilies.count)
            guard let presentationFamilyIndex = try queueFamilyIndices.first(where: {
                try physicalDevice.getSurfaceSupportKHR(queueFamilyIndex: $0, surface: surface)
            }) else {
                continue
            }
            
            self.graphicsFamilyIndex = UInt32(graphicsFamilyIndex)
            self.presentationFamilyIndex = presentationFamilyIndex
            self.physicalDevice = physicalDevice
            break
        }
        
        guard physicalDevice != nil else {
            throw AppError(description: "Could not find a suitable device.")
        }
    }

    /// A Vulkan device.
    var device: Device!
    /// A Vulkan queue that supports graphics commands.
    var graphicsQueue: Queue!
    /// A Vulkan queue that supports presentation commands.
    var presentationQueue: Queue!
    /// Creates a logical device and retrieves its queues.
    func createDevice() throws {
        let families: Set = [graphicsFamilyIndex!, presentationFamilyIndex!]
        
        let queueInfos = families.map {
            DeviceQueueCreateInfo(
                flags: [],
                queueFamilyIndex: UInt32($0),
                queuePriorities: [1.0]
            )
        }
        
        device = try physicalDevice.createDevice(
            createInfo: .init(
                flags: [],
                queueCreateInfos: queueInfos,
                enabledLayerNames: [],
                enabledExtensionNames: [VK_KHR_SWAPCHAIN_EXTENSION_NAME],
                enabledFeatures: nil
            )
        )
        
        graphicsQueue = device.getQueue(queueFamilyIndex: graphicsFamilyIndex, queueIndex: 0)
        presentationQueue = device.getQueue(queueFamilyIndex: presentationFamilyIndex, queueIndex: 0)
    }

    /// A Vulkan swapchain.
    var swapchain: SwapchainKHR!
    /// The size of the swapchain images.
    var swapchainExtent: Extent2D!
    /// An array of Vulkan swapchain images.
    var swapchainImages: [Image] = []
    /// Creates a swapchain and retrieves its images.
    func createSwapchain() throws {
        let capabilities = try physicalDevice.getSurfaceCapabilitiesKHR(surface: surface)
        
        var imageCount = capabilities.minImageCount + 1
        if capabilities.maxImageCount > 0 && imageCount > capabilities.maxImageCount {
            imageCount = capabilities.maxImageCount
        }
        
        if capabilities.currentExtent.width != UInt32.max {
            swapchainExtent = capabilities.currentExtent
        } else {
            let width = max(capabilities.minImageExtent.width, min(capabilities.maxImageExtent.width, windowSize.width))
            let height = max(capabilities.minImageExtent.height, min(capabilities.maxImageExtent.height, windowSize.height))
            swapchainExtent = Extent2D(width: width, height: height)
        }
        
        swapchain = try device.createSwapchainKHR(
            createInfo: .init(
                flags: [],
                surface: surface,
                minImageCount: imageCount,
                imageFormat: .b8g8r8a8Srgb,
                imageColorSpace: .srgbNonlinear,
                imageExtent: swapchainExtent,
                imageArrayLayers: 1,
                imageUsage: .colorAttachment,
                imageSharingMode: graphicsFamilyIndex == presentationFamilyIndex ? .exclusive : .concurrent,
                queueFamilyIndices: [graphicsFamilyIndex, presentationFamilyIndex],
                preTransform: capabilities.currentTransform,
                compositeAlpha: .opaque,
                presentMode: .fifo,
                clipped: true,
                oldSwapchain: nil
            )
        )
        
        swapchainImages = try swapchain.getImagesKHR()
    }
    
    /// An array of Vulkan image views.
    var imageViews: [ImageView] = []
    /// Creates an image view for each of the swapchain images.
    func createImageViews() throws {
        for image in swapchainImages {
            let imageView = try device.createImageView(
                createInfo: .init(
                    flags: [],
                    image: image,
                    viewType: .type2d,
                    format: .b8g8r8a8Srgb,
                    components: .init(r: .r, g: .g, b: .b, a: .a),
                    subresourceRange: .init(
                        aspectMask: .color,
                        baseMipLevel: 0,
                        levelCount: 1,
                        baseArrayLayer: 0,
                        layerCount: 1
                    )
                )
            )
            imageViews.append(imageView)
        }
    }
    
    /// A Vulkan render pass.
    var renderPass: RenderPass!
    /// Creates a Vulkan render pass.
    func createRenderPass() throws {
        let colorAttachment = AttachmentDescription(
            flags: [],
            format: .b8g8r8a8Srgb,
            samples: .type1,
            loadOp: .clear,
            storeOp: .store,
            stencilLoadOp: .dontCare,
            stencilStoreOp: .dontCare,
            initialLayout: .undefined,
            finalLayout: .presentSrcKHR
        )
        
        let colorAttachmentRef = AttachmentReference(attachment: 0, layout: .colorAttachmentOptimal)
        
        let subpass = SubpassDescription(
            flags: [],
            pipelineBindPoint: .graphics,
            inputAttachments: [],
            colorAttachments: [colorAttachmentRef],
            resolveAttachments: nil,
            depthStencilAttachment: nil,
            preserveAttachments: []
        )
        
        let dependency = SubpassDependency(
            srcSubpass: VK_SUBPASS_EXTERNAL,
            dstSubpass: 0,
            srcStageMask: .colorAttachmentOutput,
            dstStageMask: .colorAttachmentOutput,
            srcAccessMask: [],
            dstAccessMask: .colorAttachmentWrite,
            dependencyFlags: []
        )
        
        renderPass = try device.createRenderPass(
            createInfo: .init(
                flags: [],
                attachments: [colorAttachment],
                subpasses: [subpass],
                dependencies: [dependency]
            )
        )
    }
    
    /// Creates a Vulkan shader module from the source file at the given path.
    func createShaderModule(path: String) throws -> ShaderModule {
        guard let buffer = readFile(path: path) else {
            throw AppError(description: "Failed to read file: \(path)")
        }
        
        return try buffer.withUnsafeBytes { bytes in
            try device.createShaderModule(
                createInfo: .init(
                    flags: [],
                    codeSize: buffer.count,
                    code: bytes.bindMemory(to: UInt32.self).baseAddress!
                )
            )
        }
    }
    
    /// DescriptorSetLayout
    var descriptorSetLayout: DescriptorSetLayout!
    func createDescriptorSetLayout() throws {
        let uboLayoutBinding = DescriptorSetLayoutBinding(
            binding: 0,
            descriptorType: .uniformBuffer,
            descriptorCount: 1,
            stageFlags: .vertex,
            immutableSamplers: []
        )

        descriptorSetLayout = try self.device.createDescriptorSetLayout(
            createInfo: .init(
                flags: [],
                bindings: [uboLayoutBinding]
            )
        )
    }

    /// A Vulkan pipeline layout.
    var pipelineLayout: PipelineLayout!
    /// A Vulkan graphics pipeline.
    var pipeline: Pipeline!
    /// Creates a Vulkan graphics pipeline.
    func createGraphicsPipeline() throws {
        let vertexShader = try createShaderModule(path: "./Sources/Main/shaders_ubo/vert.spv")
        let fragmentShader = try createShaderModule(path: "./Sources/Main/shaders_ubo/frag.spv")
        defer {
            vertexShader.destroy()
            fragmentShader.destroy()
        }
        
        let vertexStage = PipelineShaderStageCreateInfo(
            flags: [],
            stage: .vertex,
            module: vertexShader,
            name: "main",
            specializationInfo: nil
        )
        
        let fragmentStage = PipelineShaderStageCreateInfo(
            flags: [],
            stage: .fragment,
            module: fragmentShader,
            name: "main",
            specializationInfo: nil
        )
        
        let vertexBinding = VertexInputBindingDescription(
            binding: 0,
            stride: UInt32(MemoryLayout<Vertex>.stride),
            inputRate: .vertex
        )
        
        let positionAttribute = VertexInputAttributeDescription(
            location: 0,
            binding: 0,
            format: .r32g32Sfloat,
            offset: UInt32(MemoryLayout.offset(of: \Vertex.position)!)
        )
        
        let colorAttribute = VertexInputAttributeDescription(
            location: 1,
            binding: 0,
            format: .r32g32b32Sfloat,
            offset: UInt32(MemoryLayout.offset(of: \Vertex.color)!)
        )
        
        let vertexInputState = PipelineVertexInputStateCreateInfo(
            flags: [],
            vertexBindingDescriptions: [vertexBinding],
            vertexAttributeDescriptions: [positionAttribute, colorAttribute]
        )
        
        let inputAssemblyState = PipelineInputAssemblyStateCreateInfo(
            flags: [],
            topology: .triangleList,
            primitiveRestartEnable: false
        )
        
        let viewport = Viewport(
            x: 0,
            y: 0,
            width: Float(swapchainExtent.width),
            height: Float(swapchainExtent.height),
            minDepth: 0,
            maxDepth: 1
        )
        
        let scissor = Rect2D(offset: .init(x: 0, y: 0), extent: swapchainExtent)
        
        let viewportState = PipelineViewportStateCreateInfo(
            flags: [],
            viewports: [viewport],
            scissors: [scissor]
        )
        
        let rasterizationState = PipelineRasterizationStateCreateInfo(
            flags: [],
            depthClampEnable: false,
            rasterizerDiscardEnable: false,
            polygonMode: .fill,
            cullMode: .back,
            frontFace: .clockwise,
            depthBiasEnable: false,
            depthBiasConstantFactor: 0,
            depthBiasClamp: 0,
            depthBiasSlopeFactor: 0,
            lineWidth: 1
        )
        
        let multisampleState = PipelineMultisampleStateCreateInfo(
            flags: [],
            rasterizationSamples: .type1,
            sampleShadingEnable: false,
            minSampleShading: 0,
            sampleMask: nil,
            alphaToCoverageEnable: false,
            alphaToOneEnable: false
        )
        
        let colorBlendAttachmentState = PipelineColorBlendAttachmentState(
            blendEnable: false,
            srcColorBlendFactor: .zero,
            dstColorBlendFactor: .zero,
            colorBlendOp: .add,
            srcAlphaBlendFactor: .zero,
            dstAlphaBlendFactor: .zero,
            alphaBlendOp: .add,
            colorWriteMask: [.r, .g, .b, .a]
        )
        
        let colorBlendState = PipelineColorBlendStateCreateInfo(
            flags: [],
            logicOpEnable: false,
            logicOp: .copy,
            attachments: [colorBlendAttachmentState],
            blendConstants: (0, 0, 0, 0)
        )
        
        pipelineLayout = try device.createPipelineLayout(
            createInfo: .init(
                flags: [],
                setLayouts: [self.descriptorSetLayout],
                pushConstantRanges: []
            )
        )
        
        let pipelineInfo = GraphicsPipelineCreateInfo(
            flags: [],
            stages: [vertexStage, fragmentStage],
            vertexInputState: vertexInputState,
            inputAssemblyState: inputAssemblyState,
            tessellationState: nil,
            viewportState: viewportState,
            rasterizationState: rasterizationState,
            multisampleState: multisampleState,
            depthStencilState: nil,
            colorBlendState: colorBlendState,
            dynamicState: nil,
            layout: pipelineLayout,
            renderPass: renderPass,
            subpass: 0,
            basePipelineHandle: nil,
            basePipelineIndex: 0
        )
        
        pipeline = try device.createGraphicsPipelines(
            pipelineCache: nil,
            createInfos: [pipelineInfo]
        )[0]
    }
    
    /// An array of Vulkan framebuffers.
    var framebuffers: [Framebuffer] = []
    /// Creates a Vulkan framebuffer for each of the swapchain image views.
    func createFramebuffers() throws {
        for imageView in imageViews {
            let framebuffer = try device.createFramebuffer(
                createInfo: .init(
                    flags: [],
                    renderPass: renderPass,
                    attachments: [imageView],
                    width: swapchainExtent.width,
                    height: swapchainExtent.height,
                    layers: 1
                )
            )
            framebuffers.append(framebuffer)
        }
    }
    
    /// Creates a Vulkan buffer and allocates memory for it, with the given properties.
    func createBuffer(size: VkDeviceSize, usage: BufferUsageFlags, properties: MemoryPropertyFlags) throws -> (Buffer, DeviceMemory) {
        let buffer = try device.createBuffer(
            createInfo: .init(
                flags: [],
                size: size,
                usage: usage,
                sharingMode: .exclusive,
                queueFamilyIndices: []
            )
        )
        
        let memoryRequirements = buffer.getMemoryRequirements()
        let memoryProperties = physicalDevice.getMemoryProperties()
        
        guard let memoryType = memoryProperties.memoryTypes.enumerated().first(where: { index, type in
            (memoryRequirements.memoryTypeBits & (1 << index) != 0) &&
                type.propertyFlags.contains(properties)
        }) else {
            throw AppError(description: "Could not find suitable memory type.")
        }
        
        let memory = try device.allocateMemory(
            allocateInfo: .init(
                allocationSize: memoryRequirements.size,
                memoryTypeIndex: UInt32(memoryType.offset)
            )
        )
        
        try buffer.bindMemory(memory: memory, memoryOffset: 0)
        
        return (buffer, memory)
    }
    
    /// An array of vertices that describe a triangle.
    let vertices = [
        Vertex(position: .init(x: 0, y: -0.5), color: .init(x: 1, y: 0, z: 0)),
        Vertex(position: .init(x: 0.5, y: 0.5), color: .init(x: 0, y: 1, z: 0)),
        Vertex(position: .init(x: -0.5, y: 0.5), color: .init(x: 0, y: 0, z: 1))
    ]
    
    /// The size, in bytes, of the vertices.
    var bufferSize: VkDeviceSize!
    /// A Vulkan buffer used to stage the vertices before transfering them to the GPU.
    var stagingBuffer: Buffer!
    /// Host visible memory that is bound to the staging buffer.
    var stagingBufferMemory: DeviceMemory!
    /// A Vulkan buffer used to store the vertices that will be accessed by the GPU.
    var vertexBuffer: Buffer!
    /// Device local memory that is bound to the vertex buffer.
    var vertexBufferMemory: DeviceMemory!
    /// Creates the staging and vertex buffers.
    func createVertexBuffer() throws {
        bufferSize = VkDeviceSize(MemoryLayout<Vertex>.stride * vertices.count)
        
        (stagingBuffer, stagingBufferMemory) = try createBuffer(size: bufferSize, usage: .transferSrc, properties: [.hostVisible, .hostCoherent])
        
        (vertexBuffer, vertexBufferMemory) = try createBuffer(size: bufferSize, usage: [.vertexBuffer, .transferDst], properties: .deviceLocal)
    }

    /// create UniformData
    var model: mat4 = mat4(0.0)
    var view: mat4 = mat4(0.0)
    var proj: mat4 = mat4(0.0)
    func createUniformData() {
        self.proj = SGLMath.perspective(radians(45), Float(1.0), Float(0.1), Float(10))
        self.view = SGLMath.lookAt(
            vec3(2.0, 2.0, 1.0),
            vec3(0, 0, 0),
            vec3(0, 0, 1))

    //    let angle = Float.pi * 0.5
        self.model = SGLMath.rotate(mat4(1.0), 0.0, vec3(0, 0, 1))
    }

    /// add UniformBuffer DescriptorSet
    struct UniformBufferObject{
        var model: mat4
        var view: mat4
        var proj: mat4
    }

    var uboBufferSize: VkDeviceSize!
    var uniformBuffer: Buffer!
    var uniformBufferMemory: DeviceMemory!
    func createUniformBuffer() throws {
        //struct UniformBufferObject{
        //    var model: mat4
        //    var view: mat4
        //    var proj: mat4
        //}
        let ubo = UniformBufferObject(model: self.model, view: self.view, proj: self.proj)
        print("ubo", ubo)

        uboBufferSize = VkDeviceSize(MemoryLayout.size(ofValue: ubo))
        print("uniform bufferSize", uboBufferSize)
        (uniformBuffer, uniformBufferMemory) = try createBuffer(size: uboBufferSize, usage: .uniformBuffer, properties: [.hostVisible, .hostCoherent])
        //print("uniformBuffer", uniformBuffer)

        //let ptr = try uniformBufferMemory.mapMemory(offset: 0, size: uboBufferSize, flags: [])
        //withUnsafeBytes(of: ubo) { bytes in
        //    ptr.copyMemory(from: bytes.baseAddress!, byteCount: bytes.count)
        //}
        //uniformBufferMemory.unmapMemory()
    }

    var counter: Float = 0
    func updateUniformBuffer() throws {
        //struct UniformBufferObject{
        //    var model: mat4
        //    var view: mat4
        //    var proj: mat4
        //}

        //var model: mat4 = mat4(0.0)
        //var view: mat4 = mat4(0.0)
        //var proj: mat4 = mat4(0.0)
    
        //let proj = SGLMath.perspective(radians(45), Float(1.0), Float(0.1), Float(10))
        //let view = SGLMath.lookAt(
        //    vec3(2.0, 2.0, 2.0),
        //    vec3(0, 0, 0),
        //    vec3(0, 0, 1))

        counter += 0.1
        var angle = counter
        let model = SGLMath.rotate(mat4(1.0), angle, vec3(0, 0, 1))

        let ubo = UniformBufferObject(model: model, view: self.view, proj: self.proj)
        //print("ubo", ubo)

        //uboBufferSize = VkDeviceSize(MemoryLayout.size(ofValue: ubo))
        //print("uniform bufferSize", uboBufferSize)
        //(uniformBuffer, uniformBufferMemory) = try createBuffer(size: uboBufferSize, usage: .uniformBuffer, properties: [.hostVisible, .hostCoherent])
        //print("uniformBuffer", uniformBuffer)

        let ptr = try uniformBufferMemory.mapMemory(offset: 0, size: uboBufferSize, flags: [])
        withUnsafeBytes(of: ubo) { bytes in
            ptr.copyMemory(from: bytes.baseAddress!, byteCount: bytes.count)
        }
        uniformBufferMemory.unmapMemory()
    }

    /// DescriptorPool, DescriptorSet
    var descriptorPool: DescriptorPool!
    var descriptorSets: [DescriptorSet] = []
    func createDescriptorSet() throws {
        let poolSize = DescriptorPoolSize(
                type: .uniformBuffer,
                descriptorCount: 1
        )

        let info = DescriptorPoolCreateInfo(
                flags: [],
                maxSets: 1,
                poolSizes: [poolSize]
        )

        descriptorPool = try self.device.createDescriptorPool(createInfo: info)

        let allocInfo = DescriptorSetAllocateInfo(
            descriptorPool: descriptorPool,
            setLayouts: [self.descriptorSetLayout]
        )

        descriptorSets = try self.device.allocateDescriptorSets(allocateInfo: allocInfo)
        print("descriptorSets", descriptorSets)

        let bufferInfo = DescriptorBufferInfo(
            buffer: uniformBuffer,
            offset: 0,
            range: UInt64(self.uboBufferSize)
        )

        let wds = WriteDescriptorSet(
            dstSet: descriptorSets[0],
            dstBinding: 0,
            dstArrayElement: 0,
            descriptorType: .uniformBuffer,
            descriptorCount: 1,
            imageInfo: [],
            bufferInfo: [bufferInfo],
            texelBufferView: []
        )

        self.device.updateDescriptorSets(descriptorWrites: [wds], descriptorCopies: [])
    }

    /// A Vulkan command pool.
    var commandPool: CommandPool!
    /// Creates a command pool.
    func createCommandPool() throws {
        commandPool = try device.createCommandPool(
            createInfo: .init(
                flags: [.resetCommandBuffer],
                queueFamilyIndex: graphicsFamilyIndex
            )
        )
    }
    
    /// Copies the vertices to the staging buffer, then transfers the contents of the staging buffer to the vertex buffer.
    func copyVertexBuffer() throws {
        let ptr = try stagingBufferMemory.mapMemory(offset: 0, size: bufferSize, flags: [])
        vertices.withUnsafeBytes { bytes in
            ptr.copyMemory(from: bytes.baseAddress!, byteCount: bytes.count)
        }
        stagingBufferMemory.unmapMemory()
        
        let commandBuffer = try device.allocateCommandBuffers(
            allocateInfo: .init(
                commandPool: commandPool,
                level: .primary,
                commandBufferCount: 1
            )
        )[0]
        defer {
            commandPool.freeCommandBuffers(commandBuffers: [commandBuffer])
        }
        
        try commandBuffer.begin(
            beginInfo: .init(
                flags: .oneTimeSubmit,
                inheritanceInfo: nil
            )
        )
        
        let copyRegion = BufferCopy(srcOffset: 0, dstOffset: 0, size: bufferSize)
        commandBuffer.cmdCopyBuffer(srcBuffer: stagingBuffer, dstBuffer: vertexBuffer, regions: [copyRegion])
        
        try commandBuffer.end()
        
        let submitInfo = SubmitInfo(
            waitSemaphores: [],
            waitDstStageMask: [],
            commandBuffers: [commandBuffer],
            signalSemaphores: []
        )
        try graphicsQueue.submit(submits: [submitInfo], fence: nil)
        try graphicsQueue.waitIdle()
    }

    /// A Vulkan command buffer.
    var commandBuffers: [CommandBuffer] = []
    /// Allocates a command buffer for each of the swapchain images.
    func createCommandBuffers() throws {
        commandBuffers = try device.allocateCommandBuffers(
            allocateInfo: .init(
                commandPool: commandPool,
                level: .primary,
                commandBufferCount: UInt32(swapchainImages.count)
            )
        )
    }
    
    /// Records a set of commands for each command buffer.
    func recordCommandBuffers() throws {
        for (commandBuffer, framebuffer) in zip(commandBuffers, framebuffers) {
            try commandBuffer.begin(beginInfo: .init(flags: [], inheritanceInfo: nil))
            
            let clearColor = VkClearValue(color: .init(float32: (0, 0, 0, 1)))
            commandBuffer.cmdBeginRenderPass(
                renderPassBegin: .init(
                    renderPass: renderPass,
                    framebuffer: framebuffer,
                    renderArea: .init(offset: .init(x: 0, y: 0), extent: swapchainExtent),
                    clearValues: [clearColor]),
                contents: .inline
            )
            
            commandBuffer.cmdBindPipeline(pipelineBindPoint: .graphics, pipeline: pipeline)
            //
            commandBuffer.cmdBindDescriptorSets(pipelineBindPoint: .graphics, layout: self.pipelineLayout, firstSet: 0, descriptorSets: self.descriptorSets, dynamicOffsets: [])
            //
            commandBuffer.cmdBindVertexBuffers(firstBinding: 0, buffers: [vertexBuffer], offsets: [0])
            commandBuffer.cmdDraw(vertexCount: UInt32(vertices.count), instanceCount: 1, firstVertex: 0, firstInstance: 0)
            
            commandBuffer.cmdEndRenderPass()
            
            try commandBuffer.end()
        }
    }

    /// A semaphore to signal when an image is available for rendering.
    var imageAvailable: Vulkan.Semaphore!
    /// A semaphore to signal when rendering is complete.
    var renderComplete: Vulkan.Semaphore!
    /// Creates any objects required for synchronization.
    func createSync() throws {
        imageAvailable = try device.createSemaphore(createInfo: .init(flags: []))
        renderComplete = try device.createSemaphore(createInfo: .init(flags: []))
    }
    
    /// Aquires an image from the swapchain, submits the associated command buffer, and finally presents the image.
    func drawFrame() throws {
        try updateUniformBuffer()

        let index = try swapchain.acquireNextImageKHR(timeout: UInt64.max, semaphore: imageAvailable, fence: nil)
        
        let graphicsSubmit = SubmitInfo(
            waitSemaphores: [imageAvailable],
            waitDstStageMask: [.colorAttachmentOutput],
            commandBuffers: [commandBuffers[Int(index)]],
            signalSemaphores: [renderComplete]
        )
        try graphicsQueue.submit(submits: [graphicsSubmit], fence: nil)
        
        try presentationQueue.presentKHR(
            presentInfo: .init(
                waitSemaphores: [renderComplete],
                swapchains: [swapchain],
                imageIndices: [index],
                results: nil
            )
        )
        
        // Note: Don't use this in the real world. It is not an efficient method of synchronization.
        try presentationQueue.waitIdle()
    }

    /// Runs the main draw loop until the window is closed.
    func mainLoop() throws {
        while glfwWindowShouldClose(window) == GLFW_FALSE {
            glfwPollEvents()
            try drawFrame()
        }
        try device.waitIdle()
    }
    
    /// Initializes and runs the application.
    func run() {
        do {
            try createWindow()
            try createInstance()
            try createSurface()
            try selectPhysicalDevice()
            try createDevice()
            try createSwapchain()
            try createImageViews()
            try createRenderPass()
            try createDescriptorSetLayout()
            try createGraphicsPipeline()
            try createFramebuffers()
            try createVertexBuffer()
            try createCommandPool()
            try copyVertexBuffer()
            createUniformData()
            try createUniformBuffer()
            try createDescriptorSet()
            try createCommandBuffers()
            try recordCommandBuffers()
            try createSync()
            try mainLoop()
        } catch let e as Result {
            print("Encountered Vulkan error: \(e)")
        } catch let e {
            print(e)
        }
    }

    /// Cleans up any resources that were created.
    deinit {
        imageAvailable?.destroy()
        renderComplete?.destroy()
        commandPool?.destroy()
        vertexBuffer?.destroy()
        vertexBufferMemory?.freeMemory()
        stagingBuffer?.destroy()
        stagingBufferMemory?.freeMemory()
        //
        uniformBuffer?.destroy()
        uniformBufferMemory?.freeMemory()
        //
        for framebuffer in framebuffers { framebuffer.destroy() }
        pipeline?.destroy()
        pipelineLayout?.destroy()
        //
        descriptorSetLayout?.destroy()
        descriptorPool?.destroy()
        //
        renderPass?.destroy()
        for imageView in imageViews { imageView.destroy() }
        swapchain?.destroyKHR()
        device?.destroy()
        surface?.destroyKHR()
        instance?.destroy()
        glfwDestroyWindow(window)
        glfwTerminate()
    }
}

/// GLFW key callback
// defined in AppClearColor

shaderは次のようになります。

/shaders_ubo
shader.vert
#version 450

layout (binding = 0) uniform bufferVals {
    mat4 model;
    mat4 view;
    mat4 proj;
} ubo;

layout (location = 0) in vec2 inPosition;
layout (location = 1) in vec3 inColor;

layout (location = 0) out vec3 fragColor;

void main() {
   vec4 pos = vec4(inPosition, 0.0, 1.0);

   gl_Position = ubo.proj * ubo.view * ubo.model * pos;
   fragColor = inColor;
}

shader.frag
#version 450

layout (location = 0) in vec3 fragColor;
layout (location = 0) out vec4 outColor;
void main() {
   outColor = vec4(fragColor, 1.0);
}

glslang でspvに変換します。

GNUStepSwiftBridge & OpenGL

GNUStepSwiftBridge を使って NSOpenGLView への描画にトライしました。

[ 実行結果 ]

Swift で OpenGL による描画のサンプルがあります。
GitHub - sakrist/Swift_OpenGL_Example: :star: Swift OpenGL Example written with swift (for Ubuntu, macOS, iOS and Android)
shader を使って描画しています。
これは、macOS, iOS, Linux, Android で動作するようです。
下図は Linux で実行したものです。XWindow に描画しています。
mouse による操作ができます。

今回は、これを参考にして、GNUStepSwiftBridge で NSOpenGLView への描画にトライしました。

前回の AppKit.swift にNSOpenGLView を追加しました。
AppKit.swift

// The Swift Programming Language
// https://docs.swift.org/swift-book
// add
// 2022-11-15
// NSImage public static func imageWithPath(_ path: String)

import ObjCSwiftInterop

////NSApplication
open class NSApplication: GNUStepNSObjectWrapper {
	public override var _nsclassName: String {
		return "NSApplication"
	}
	public var shared: NSApplication {
		get {
			var sharedApp =  forSwift_objcSendMessage(&self._nsobjptr!.pointee, sel_registerName("sharedApplication"))
			return NSApplication()
		}
	}
}

open class NSApplicationDelegate: GNUStepNSObjectWrapper {
	public override var _nsclassName: String {
		return "NSApplicationDelegateForSwift"
	}

	static var didFinishLaunchingIMP: @convention(block) (UnsafeMutablePointer<objc_object>?,SEL,id?) -> (UInt8) = { first, second, third in
		let SELF: NSApplicationDelegate? = smart_swift_lookupIvarWithType(_nsobjptr: first, name: "___swiftPtr")
		SELF?.applicationDidFinishLaunching(notification: nil)
		return 0
	}

	open func applicationDidFinishLaunching(notification: Any?) {

	}

	static var _objcClass = GNUStepNSObjectSubclassConstructor(name: "NSApplicationDelegateForSwift", superName: "NSObject", create: { ptr in
		var types = "i@:@"
		let imp = imp_implementationWithBlock(unsafeBitCast(didFinishLaunchingIMP, to: id.self))
		class_addMethod(ptr, sel_registerName("applicationDidFinishLaunching:"),imp, types)
	})

	public override init() {
		_ = NSApplicationDelegate._objcClass

		super.init()

		var nclass = objc_getClass("NSApplicationDelegateForSwift")

		var initalizedObject = forSwift_objcSendMessage(&nclass!.pointee, sel_getUid("alloc"))
		initalizedObject = forSwift_objcSendMessage(&initalizedObject!.pointee, sel_getUid("init"))

		self._nsobjptr = initalizedObject

		var cast = Unmanaged.passUnretained(self).toOpaque()
		smart_swift_setIvar(_nsobjptr: self._nsobjptr, name: "___swiftPtr", value: cast)
	}

	public required init(nsobjptr: UnsafeMutablePointer<objc_object>?) {
		super.init(nsobjptr: nsobjptr)
	}

	public func didFinishLaunching(_ OBJ: Any, notification: Any?) -> Void {

	}
}

open class UIView: NSView {
	public override var _nsclassName: String {
		return "UIView"
	}

	static var isFlippedIMP: @convention(block) (UnsafeMutablePointer<objc_object>?,SEL) -> (UInt8) = { first, second in
		print("!!!!!!!!!!!!!!!!!!!!!!!!asked if is flipped!")
		return 1
	}

	static var _objcClass = GNUStepNSObjectSubclassConstructor(
		name: "UIView", superName: "NSScrollView", create: { ptr in }
	)

	public override init() {
		_ = UIView._objcClass

		super.init()
		let nclass = objc_getClass("UIView")

		let types = "c"
		let imp = imp_implementationWithBlock(unsafeBitCast(UIView.isFlippedIMP, to: id.self))
		class_replaceMethod(UIView._objcClass._nsobjptr!, sel_registerName("isFlipped"),imp, types)

		var initalizedObject = forSwift_objcSendMessage(&nclass!.pointee, sel_getUid("alloc"))
		initalizedObject = forSwift_objcSendMessage1NSRect(&initalizedObject!.pointee, sel_getUid("initWithFrame:"), CGRect(x: 0, y: 0, width: 100, height: 100))
		initalizedObject = forSwift_objcSendMessage(&initalizedObject!.pointee, sel_getUid("retain"))
		self._nsobjptr = initalizedObject

		initalizedObject = forSwift_objcSendMessage(&initalizedObject!.pointee, sel_getUid("_rebuildCoordinates"))
		
		let cast = Unmanaged.passUnretained(self).toOpaque()
		smart_swift_setIvar(_nsobjptr: self._nsobjptr, name: "___swiftPtr", value: cast)
	}

	public required init(nsobjptr: UnsafeMutablePointer<objc_object>?) {
		super.init()
		self._nsobjptr = nsobjptr
		var initalizedObject = forSwift_objcSendMessage(&nsobjptr!.pointee, sel_registerName("retain"))
	}
}


public class NSFont: GNUStepNSObjectWrapper {
	public override var _nsclassName: String {
		return "NSFont"
	}

	public override init() {
		super.init()
	}

	public init?(name: String, size: Float) {
		super.init()
		var nsColorClass = objc_getClass("NSFont")
		var size = size
		var string = NSString(string: name)
		var imp:  (@convention(c) (id, SEL, id, Float) -> (id?))? = objc_smart_getIMP(id: &nsColorClass!.pointee, selector: "fontWithName:size:")
		if var rtn = imp?(&nsColorClass!.pointee, sel_getUid("fontWithName:size:"), &string._nsobjptr!.pointee, size) {
			self._nsobjptr = rtn
			print("FOUND FONT")
			var initalizedObject = forSwift_objcSendMessage(&rtn.pointee, sel_registerName("retain"))
		} else {
			print("FONT NOT FOUND")
			return nil
		}
	}

	public static func boldSystemFontOfSize(size: Double) -> NSFont {
		var nsColorClass = objc_getClass("NSFont")
		var size = size

		var imp:  (@convention(c) (id, SEL, Double) -> (id?))? = objc_smart_getIMP(id: &nsColorClass!.pointee, selector: "boldSystemFontOfSize:")
		if var rtn = imp?(&nsColorClass!.pointee, sel_getUid("boldSystemFontOfSize:"), size) {
			print("FOUND boldSystemFontOfSize")

			var initalizedObject = forSwift_objcSendMessage(&rtn.pointee, sel_registerName("retain"))
			return NSFont(nsobjptr: rtn)

		} else {
			return NSFont()
		}
	}

	public required init(nsobjptr: UnsafeMutablePointer<objc_object>?) {
		super.init()
		self._nsobjptr = nsobjptr
		var initalizedObject = forSwift_objcSendMessage(&nsobjptr!.pointee, sel_registerName("retain"))
	}
}

public class NSColor: GNUStepNSObjectWrapper {

	public override var _nsclassName: String {
		return "NSColor"
	}

	public override init() {
		super.init()
		let nsColorClass =  objc_getClass(self._nsclassName)
		var initalizedObject = forSwift_objcSendMessage(&nsColorClass!.pointee, sel_registerName("blueColor"))
		initalizedObject = forSwift_objcSendMessage(&initalizedObject!.pointee, sel_registerName("retain"))
		self._nsobjptr = initalizedObject
	}

	public init(red: Float, green: Float, blue: Float, alpha: Float) {
		super.init()
		let nsColorClass =  objc_getClass(self._nsclassName)
		let red = red
		let green = green
		let blue = blue
		let alpha = alpha
		var initalizedObject = forSwift_objcSendMessage4Floats(&nsColorClass!.pointee, sel_registerName("colorWithCalibratedRed:green:blue:alpha:"), red, green, blue, alpha)
		initalizedObject = forSwift_objcSendMessage(&initalizedObject!.pointee, sel_registerName("retain"))
		self._nsobjptr = initalizedObject
	}

	public static var clear: NSColor {
		return .init(red: 1.0, green: 1.0, blue: 1.0, alpha: 0.0)
	}

	public static var blue: NSColor {
		return .init(red: 0.0, green: 0.0, blue: 1.0, alpha: 1.0)
	}

	public static var black: NSColor {
		return .init(red: 0.0, green: 0.0, blue: 0.0, alpha: 1.0)
	}

	public static var white: NSColor {
		return .init(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
	}

	public static var grey: NSColor {
		return .init(red: 0.5, green: 0.5, blue: 0.5, alpha: 1.0)
	}

	public static var darkPurple: NSColor {
		return NSColor.init(red: 0.1422559619, green: 0.05977959186, blue: 0.2294596732, alpha: 1)
	}

	public static var purple: NSColor {
		let c =    NSColor.init(red: 0.2726541758, green: 0.1163508371, blue: 0.435738951, alpha: 1)
		return c
	}

	public required init(nsobjptr: UnsafeMutablePointer<objc_object>?) {
		super.init(nsobjptr: nsobjptr)
	}
}

open class NSImage: GNUStepNSObjectWrapper {
	public override var _nsclassName: String {
		return "NSImage"
	}

	public static func image(named: String) -> NSImage? {
		print("finding image named \(named)")
		var nsColorClass = objc_getClass("NSImage")
		var nsColorClassAsClass =  object_getClass(objc_getClass("NSImage"))

		var string = NSString(string: named)
		var imp:  (@convention(c) (id, SEL, id) -> (id?))? = objc_smart_getIMP(id: &nsColorClass!.pointee, selector: "imageNamed:")
		print(imp)
		if var rtn = imp?(&nsColorClass!.pointee, sel_getUid("imageNamed:"), &string._nsobjptr!.pointee) {
			var initalizedObject = forSwift_objcSendMessage(&rtn.pointee, sel_registerName("retain"))
			let v = NSImage(nsobjptr: &rtn.pointee)
			print("image is named \(v.name)")
			return v
		}
		return nil
	}
	
	public static func imageWithPath(_ path: String) -> NSImage? {
		print("file path:", path)
		let nsfilePath = NSString(string: path)
		var nsImageClass = objc_getClass("NSImage")
		var initializedObject = forSwift_objcSendMessage(&nsImageClass!.pointee, sel_getUid("alloc"))
		var vimage = forSwift_objcSendMessage1(&initializedObject!.pointee, sel_getUid("initWithContentsOfFile:"), nsfilePath._nsobjptr)
        let v = NSImage(nsobjptr: &vimage!.pointee)
        return v
    }

	public var name: String {
		get {
			print("getting name")
			if var ptr = self._nsobjptr {
				var imp: (@convention(c) (id, SEL) -> (id))? = objc_smart_getIMP(object: self, selector: "name")
				if let rtn = imp?(&ptr.pointee, sel_getUid("name")) {
					if let rtn = objc_convertToSwift_NSObject(value: rtn) as? NSString {
						return rtn.string
					}
				}
			}

			return ""
		}
	}
}

open class NSView: GNUStepNSObjectWrapper {
	public override var _nsclassName: String {
		return "NSView"
	}

	public var subviews: [Any] = []
	public func addSubview(_ subview: NSView) {
		subviews.append(subview)
		guard let selfPtr = self._nsobjptr else {return}
		if var ptr = subview._nsobjptr{
			_ = forSwift_objcSendMessage1(&selfPtr.pointee, sel_registerName("addSubview:"), &ptr.pointee)

		}
	}

	public func setBackgroundColor(_ color:  NSColor) {
		guard let colorPtr = color._nsobjptr else {return}
		guard let selfPtr = self._nsobjptr else {return}
		_ = forSwift_objcSendMessage1(&selfPtr.pointee, sel_registerName("setBackgroundColor:"), &colorPtr.pointee)
	}

	public var frame: CGRect {
		get {
			let imp:  (@convention(c) (id, SEL) -> (CGRect))? = objc_smart_getIMP(object: self, selector: "frame")
			if let rtn = imp?(&self._nsobjptr!.pointee, sel_getUid("frame")) {
				return rtn
			}
			return .init(x: 0, y: 0, width: 0, height: 0)
		}

		set {
			guard let selfPtr = self._nsobjptr else {return}
			var imp:  (@convention(c) (id, SEL, CGRect) -> (Void))? = objc_smart_getIMP(object: self, selector: "setFrame:")
			let rtn = imp?(&self._nsobjptr!.pointee, sel_getUid("setFrame:"), newValue)
		}
	}

	public func setFrame(_ rect: CGRect) {
		print("set frame: \(rect)")
		guard let selfPtr = self._nsobjptr else {return}
		_ = forSwift_objcSendMessage1NSRect(&selfPtr.pointee, sel_registerName("setFrame:"), rect)
	}

}

public class NSWindow: GNUStepNSObjectWrapper {
	public override var _nsclassName: String {
		return "NSWindow"
	}

	public override init() {
		super.init()
		let nsWindowClass =  objc_getClass(_nsclassName)
		var allocatedObject = forSwift_objcSendMessage(&nsWindowClass!.pointee, sel_registerName("alloc"))

		let styleMask: UInt = 1 + 2 + 4 + 8
		let backingStoreType: UInt = 0
		let deferr: Int8 = 0
		let rect = CGRect(x: 200, y: 200, width: 500, height: 500)
		allocatedObject = initWithContentRect_styleMask_backing_defer(&allocatedObject!.pointee, sel_registerName("initWithContentRect:styleMask:backing:defer:"), rect, styleMask, backingStoreType, deferr)

		self._nsobjptr = allocatedObject
		self.setTitle(NSString(string: "Untitled Window"))

	}

	public init(_ rect: CGRect) {
		super.init()
		let nsWindowClass =  objc_getClass(_nsclassName)
		var allocatedObject = forSwift_objcSendMessage(&nsWindowClass!.pointee, sel_registerName("alloc"))

		let styleMask: UInt = 1 + 2 + 4 + 8
		let backingStoreType: UInt = 0
		let deferr: Int8 = 0
		allocatedObject = initWithContentRect_styleMask_backing_defer(&allocatedObject!.pointee, sel_registerName("initWithContentRect:styleMask:backing:defer:"), rect, styleMask, backingStoreType, deferr)

		self._nsobjptr = allocatedObject
		self.setTitle(NSString(string: "Untitled Window"))
	}

	public required init(nsobjptr: UnsafeMutablePointer<objc_object>?) {
		super.init(nsobjptr: nsobjptr)
	}

	public func orderFront(sender:  Any?) {
		guard var ptr = self._nsobjptr else {return}
		_ = forSwift_objcSendMessage1(&ptr.pointee, sel_registerName("orderFront:"), nil)
	}

	public func setBackgroundColor(_ color:  NSColor) {
		guard let colorPtr = color._nsobjptr else {return}
		guard let selfPtr = self._nsobjptr else {return}
		_ = forSwift_objcSendMessage1(&selfPtr.pointee, sel_registerName("setBackgroundColor:"), &colorPtr.pointee)
	}

	var title: String {
		set {
			var title = NSString(string: newValue)
			if var ptr = title._nsobjptr, var selfPtr = self._nsobjptr {
				_ = forSwift_objcSendMessage1(&selfPtr.pointee, sel_registerName("setTitle:"), &ptr.pointee)
			}
		}

		get {
			if let x: NSString = objc_smart_sendMessage(object: self, selector: "title", value1: StopVariable(), value2: StopVariable(), value3: StopVariable(), value4: StopVariable(), value5: StopVariable(), value6: StopVariable(), value7: StopVariable(), value8: StopVariable(), value9: StopVariable()) {
				return x.string
			}
			return ""
		}
	}

	public func setTitle(_ title: NSString) {
		if var ptr = title._nsobjptr, var selfPtr = self._nsobjptr {
			_ = forSwift_objcSendMessage1(&selfPtr.pointee, sel_registerName("setTitle:"), &ptr.pointee)
		}
	}

	public var subviews: [Any] = []
	public func addSubview(_ subview: GNUStepNSObjectWrapper) {
		subviews.append(subview)
		guard let selfPtr = self._nsobjptr else {return}
		var contentViewPtr =  forSwift_objcSendMessage(&selfPtr.pointee, sel_registerName("contentView"))
		if var ptr = subview._nsobjptr, var contentViewPtr = contentViewPtr {
			_ = forSwift_objcSendMessage1(&contentViewPtr.pointee, sel_registerName("addSubview:"), &ptr.pointee)
		}
	}

	var contentView: NSView?
	public func setContentView(_ view: NSView) {
		guard let selfPtr = self._nsobjptr else {return}
		self.contentView = view
		if var ptr = view._nsobjptr {
			var _ =  forSwift_objcSendMessage1(&selfPtr.pointee, sel_registerName("setContentView:"), &ptr.pointee)
			_ = forSwift_objcSendMessage1(&ptr.pointee, sel_registerName("_invalidateCoordinates"), &ptr.pointee)
			_ = forSwift_objcSendMessage1(&ptr.pointee, sel_registerName("_rebuildCoordinates"), &ptr.pointee)
		}
	}

	public func printWindow() {
		guard let selfPtr = self._nsobjptr else {return}
		_ = forSwift_objcSendMessage1(&selfPtr.pointee, sel_registerName("print:"), nil)
	}

	public func setFrameOrigin(_ origin: ObjCSwiftInterop.NSPoint) {
		guard var ptr = self._nsobjptr else {return}
		var origin = origin

		_ = forSwift_objcSendMessage1(&ptr.pointee, sel_registerName("setFrameOrigin:"), &origin)
	}
}

public class NSControl: NSView {
	public func setEnabled(_ flag: Bool) {
		guard var ptr = self._nsobjptr else {return}
		var bool = flag
		_ = forSwift_objcSendMessage1(&ptr.pointee, sel_registerName("setEnabled:"), &bool)
	}

	public var stringValue: String {
		get {
			var imp:  (@convention(c) (id, SEL) -> (id))? = objc_smart_getIMP(object: self, selector: "stringValue")
			if let rtn = imp?(&self._nsobjptr!.pointee, sel_getUid("stringValue")) {
				if let rtn = objc_convertToSwift_NSObject(value: rtn) as? NSString {
					return rtn.string
				}
			}
			return ""
		}
		set {			
			guard var selfPtr = self._nsobjptr else {return}
			let ns = NSString(string: newValue)
			guard var stringPTR = ns._nsobjptr else {return}
			var imp:  (@convention(c) (id, SEL, id) -> (Void))? = objc_smart_getIMP(object: self, selector: "setStringValue:")
			let rtn = imp?(&selfPtr.pointee, sel_getUid("setStringValue:"), &stringPTR.pointee)
		}
	}
}

public class NSImageView: NSView {
	public override var _nsclassName: String {
		return "NSImageView"
	}

	public override init() {
		super.init()

		var rect = ObjCSwiftInterop.CGRect(x: 0, y: 0, width: 200, height: 22) //(x: Double(50), y: Double(50), width:  Double(50), height: Double(50))
		let  nsWindowClass =  objc_getClass("NSImageView")
		var allocatedObject = forSwift_objcSendMessage(&nsWindowClass!.pointee, sel_registerName("alloc"))

		allocatedObject = forSwift_objcSendMessage1NSRect(&allocatedObject!.pointee, sel_registerName("initWithFrame:"), rect)
		_ =  forSwift_objcSendMessage(&allocatedObject!.pointee, sel_registerName("retain"))

		self._nsobjptr = allocatedObject
	}

	public required init(nsobjptr: UnsafeMutablePointer<objc_object>?) {
		super.init()
		self._nsobjptr = nsobjptr
		var initalizedObject = forSwift_objcSendMessage(&nsobjptr!.pointee, sel_registerName("retain"))
	}

	public func setImage(_ image: NSImage) {
		print("Setting Image 1")
		guard var ptr = image._nsobjptr else {print("Setting Image image ptr == nil"); return}
		guard var selfPtr = self._nsobjptr else {print("Setting Image 1.2"); return}
		if var ptr = image._nsobjptr, var selfPtr = self._nsobjptr {
			print("Setting Image 2")
			_ = forSwift_objcSendMessage1ID(&selfPtr.pointee, sel_registerName("setImage:"), ptr)
		}
	}
}

public class NSOpenGLView: NSView {
	public override var _nsclassName: String {
		return "NSOpenGLView"
	}

	public override init() {
		super.init()

		var rect = ObjCSwiftInterop.CGRect(x: 0, y: 0, width: 300, height: 300)
		let  nsViewClass =  objc_getClass("NSOpenGLView")
		
		var allocatedObject = forSwift_objcSendMessage(&nsViewClass!.pointee, sel_registerName("alloc"))
		allocatedObject = forSwift_objcSendMessage1NSRect(&allocatedObject!.pointee, sel_registerName("initWithFrame:"), rect)
		_ =  forSwift_objcSendMessage(&allocatedObject!.pointee, sel_registerName("retain"))

		self._nsobjptr = allocatedObject
	}

	public required init(nsobjptr: UnsafeMutablePointer<objc_object>?) {
		super.init()
		self._nsobjptr = nsobjptr
		var initalizedObject = forSwift_objcSendMessage(&nsobjptr!.pointee, sel_registerName("retain"))
	}

	public func contextMakeCurrent() {
		let glcontext = forSwift_objcSendMessage(&self._nsobjptr!.pointee, sel_registerName("openGLContext"))
		forSwift_objcSendMessage(&glcontext!.pointee, sel_registerName("makeCurrentContext"))
	}

	public func display() {
		let glcontext = forSwift_objcSendMessage(&self._nsobjptr!.pointee, sel_registerName("openGLContext"))
		forSwift_objcSendMessage(&glcontext!.pointee, sel_registerName("flushBuffer"))
	}
}

public class NSButton: NSControl {
	static var _objcClass = GNUStepNSObjectSubclassConstructor(name: "NSButtonForSwift", superName: "NSButton", create: { ptr in})

	public override var _nsclassName: String {
		return "NSButton"
	}

	static var ___private_actionIMP: @convention(block) (UnsafeMutablePointer<objc_object>?,SEL,id?) -> (Void) = { first, second, third in
		let SELF: NSButton? = smart_swift_lookupIvarWithType(_nsobjptr: first, name: "___swiftPtr")
		if let SELF = SELF {
			SELF.onAction?(SELF)
		}
	}

	public lazy var onAction: ((NSButton) -> (Void))? = { first in }

	public override init() {
		super.init()
		_ = Self._objcClass
		var rect = ObjCSwiftInterop.CGRect(x: 0, y: 0, width: 200, height: 22) //(x: Double(50), y: Double(50), width:  Double(50), height: Double(50))
		let  nsWindowClass =  objc_getClass("NSButtonForSwift")
		var allocatedObject = forSwift_objcSendMessage(&nsWindowClass!.pointee, sel_registerName("alloc"))
		allocatedObject = forSwift_objcSendMessage1NSRect(&allocatedObject!.pointee, sel_registerName("initWithFrame:"), rect)
		_ =  forSwift_objcSendMessage(&allocatedObject!.pointee, sel_registerName("retain"))

		var types = "@"
		let imp = imp_implementationWithBlock(unsafeBitCast(NSButton.___private_actionIMP, to: id.self))
		class_addMethod(object_getClass(&allocatedObject!.pointee), sel_registerName("___private_action:"),imp, types)

		_ = forSwift_objcSendMessage1ID(&allocatedObject!.pointee, sel_registerName("setTarget:"), allocatedObject!)
		var sel = sel_getUid("___private_action:")
		print("SEL = \(sel!)")
		_ = forSwift_objcSendMessage1SEL(&allocatedObject!.pointee, sel_registerName("setAction:"), sel!)

		self._nsobjptr = allocatedObject
		
		var cast = Unmanaged.passUnretained(self).toOpaque()
		print("in NSButton about to cast \(cast)")
		smart_swift_setIvar(_nsobjptr: self._nsobjptr, name: "___swiftPtr", value: cast)
	}

	public required init(nsobjptr: UnsafeMutablePointer<objc_object>?) {
		super.init(nsobjptr: nsobjptr)
	}

	public func setTitle(_ text: NSString) {
		if var ptr = text._nsobjptr, var selfPtr = self._nsobjptr {
			_ = forSwift_objcSendMessage1ID(&selfPtr.pointee, sel_registerName("setTitle:"), ptr)
		}
	}

	public func setImage(_ image: NSImage) {
		if var ptr = image._nsobjptr, var selfPtr = self._nsobjptr {
			_ = forSwift_objcSendMessage1ID(&selfPtr.pointee, sel_registerName("setImage:"), ptr)
		}
	}

}

public class NSTextField: NSControl {
	public override var _nsclassName: String {
		return "NSTextField"
	}

	public override init() {
		super.init()
		var rect = CGRect(x: 10, y: 10, width: 200, height: 50)
		let  nsWindowClass =  objc_getClass("NSTextField")
		var allocatedObject = forSwift_objcSendMessage(&nsWindowClass!.pointee, sel_registerName("alloc"))
		allocatedObject = forSwift_objcSendMessage1(&allocatedObject!.pointee, sel_registerName("initWithFrame:"), &rect)
		allocatedObject =  forSwift_objcSendMessage(&allocatedObject!.pointee, sel_registerName("retain"))
		self._nsobjptr = allocatedObject
		self.setText(NSString(string: "This is me!"))
	}

	public required init(nsobjptr: UnsafeMutablePointer<objc_object>?) {
		super.init()
		self._nsobjptr = nsobjptr
		var initalizedObject = forSwift_objcSendMessage(&nsobjptr!.pointee, sel_registerName("retain"))
	}

	public func setText(_ text: NSString) {
		if var ptr = text._nsobjptr, var selfPtr = self._nsobjptr {
			_ = forSwift_objcSendMessage1(&selfPtr.pointee, sel_registerName("setStringValue:"), &ptr.pointee)
		}
	}

	public var font: NSFont? {
		get {
			var imp:  (@convention(c) (id, SEL) -> (id))? = objc_smart_getIMP(object: self, selector: "font")
			if let rtn = imp?(&self._nsobjptr!.pointee, sel_getUid("font")) {
				return NSFont(nsobjptr: rtn)
			}
			return nil
		}

		set {
			print("SETTING FONT")
			guard let selfPtr = self._nsobjptr else {print("selfPtr = nil"); return}
			guard let newValuePtr = newValue?._nsobjptr else {print("newValue = nil \(newValue)"); return}
			var imp:  (@convention(c) (id, SEL, id) -> (Void))? = objc_smart_getIMP(object: self, selector: "setFont:")
			let rtn = imp?(&self._nsobjptr!.pointee, sel_getUid("setFont:"), &newValuePtr.pointee)
		}
	}

	public func setEditable(_ flag: Bool) {
		guard var ptr = self._nsobjptr else {return}
		var bool = flag
		_ = forSwift_objcSendMessage1(&ptr.pointee, sel_registerName("setEditable:"), &bool)
	}

	public func setSelectable(_ flag: Bool) {
		guard var ptr = self._nsobjptr else {return}
		var bool = flag
		_ = forSwift_objcSendMessage1(&ptr.pointee, sel_registerName("setSelectable:"), &bool)
	}

	public func selectText() {
		guard var ptr = self._nsobjptr else {return}
		_ = forSwift_objcSendMessage(&ptr.pointee, sel_registerName("selectText"))
	}

	public var isSelectable: Bool {
		get {
			var imp:  (@convention(c) (id, SEL) -> (UInt8))? = objc_smart_getIMP(object: self, selector: "isSelectable")
			if let rtn = imp?(&self._nsobjptr!.pointee, sel_getUid("isSelectable")) {
				return rtn == 0 ? false : true
			}
			return true
		}

		set {
			guard let selfPtr = self._nsobjptr else {return}
			var imp:  (@convention(c) (id, SEL, UInt8) -> (Void))? = objc_smart_getIMP(object: self, selector: "setSelectable:")
			let rtn = imp?(&self._nsobjptr!.pointee, sel_getUid("setSelectable:"), newValue ? 1 : 0)
		}
	}

	public var isEditable: Bool {
		get {
			var imp:  (@convention(c) (id, SEL) -> (UInt8))? = objc_smart_getIMP(object: self, selector: "isEditable")
			if let rtn = imp?(&self._nsobjptr!.pointee, sel_getUid("isEditable")) {
				return rtn == 0 ? false : true
			}
			return true
		}

		set {
			guard let selfPtr = self._nsobjptr else {return}
			let imp:  (@convention(c) (id, SEL, UInt8) -> (Void))? = objc_smart_getIMP(object: self, selector: "setEditable:")
			let rtn = imp?(&self._nsobjptr!.pointee, sel_getUid("setEditable:"), newValue ? 1 : 0)
		}
	}

	public var textColor: NSColor {
		get {
			var imp:  (@convention(c) (id, SEL) -> (id))? = objc_smart_getIMP(object: self, selector: "textColor")
			if let rtn = imp?(&self._nsobjptr!.pointee, sel_getUid("textColor")) {
				return NSColor(nsobjptr: rtn)
			}
			return .blue
		}

		set {
			guard let selfPtr = self._nsobjptr else {return}
			guard let newValuePtr = newValue._nsobjptr else {return}
			var imp:  (@convention(c) (id, SEL, id) -> (Void))? = objc_smart_getIMP(object: self, selector: "setTextColor:")
			let rtn = imp?(&self._nsobjptr!.pointee, sel_getUid("setTextColor:"), &newValuePtr.pointee)
		}
	}

	public var isBordered: Bool {
		get {
			let imp:  (@convention(c) (id, SEL) -> (UInt8))? = objc_smart_getIMP(object: self, selector: "isBordered")
			if let rtn = imp?(&self._nsobjptr!.pointee, sel_getUid("isBordered")) {
				return rtn == 0 ? false : true
			}
			return true
		}

		set {
			guard let selfPtr = self._nsobjptr else {return}
			let imp:  (@convention(c) (id, SEL, UInt8) -> (Void))? = objc_smart_getIMP(object: self, selector: "setBordered:")
			let rtn = imp?(&self._nsobjptr!.pointee, sel_getUid("setBordered:"), newValue ? 1 : 0)
		}
	}

	public var isBezeled: Bool {
		get {
			let imp:  (@convention(c) (id, SEL) -> (UInt8))? = objc_smart_getIMP(object: self, selector: "isBezeled")
			if let rtn = imp?(&self._nsobjptr!.pointee, sel_getUid("isBezeled")) {
				return rtn == 0 ? false : true
			}
			return true
		}

		set {
			guard let selfPtr = self._nsobjptr else {return}
			let imp:  (@convention(c) (id, SEL, UInt8) -> (Void))? = objc_smart_getIMP(object: self, selector: "setBezeled:")
			let rtn = imp?(&self._nsobjptr!.pointee, sel_getUid("setBezeled:"), newValue ? 1 : 0)
		}
	}

	public var drawsBackground: Bool {
		get {
			let imp:  (@convention(c) (id, SEL) -> (UInt8))? = objc_smart_getIMP(object: self, selector: "drawsBackground")
			if let rtn = imp?(&self._nsobjptr!.pointee, sel_getUid("drawsBackground")) {
				return rtn == 0 ? false : true
			}
			return true
		}

		set {
			guard let selfPtr = self._nsobjptr else {return}
			let imp:  (@convention(c) (id, SEL, UInt8) -> (Void))? = objc_smart_getIMP(object: self, selector: "setDrawsBackground:")
			let _rtn = imp?(&self._nsobjptr!.pointee, sel_getUid("setDrawsBackground:"), newValue ? 1 : 0)
		}
	}
}


application package のディレクトリ配置

AppOpenGL
    /Headers ( GNUStepSwiftBridge と同じ)
    /Sources
         /AppKit
             AppKit.swift ( 編集した AppKit.swift)
             Foundation.swift ( 元ファイルの6行目import AppKitGNUStep 行をコメント)
             SwiftForGNUStep.swift ( 元ファイルの14行目import AppKitGNUStep 行をコメント)
         /AppKitGNUStep ( GNUStepSwiftBridge と同じ)
         /COpenGL
             module.modulemap
         /FoubdationGNUStep ( GNUStepSwiftBridge と同じ)
         /libobjc2 ( GNUStepSwiftBridge と同じ)
         /Main
             Resources ( 前回と同じ)
             AppDelegate.swift
             CheckResource.swift ( 前回と同じ)
             Cube.swift ( /sakrist/Swift_OpenGL_Example/Sources/app 内ファイルと同じ)
             Geometry.swift ( /sakrist/Swift_OpenGL_Example/Sources/app 内ファイルと同じ)
             main.swift
             RenderObject.swift ( /sakrist/GLApplication/Sources/GLApplication 内ファイルと同じ)
             Sene.swift ( /sakrist/Swift_OpenGL_Example/Sources/app 内ファイルと同じ)
             Shader.swift ( /sakrist/Swift_OpenGL_Example/Sources/app 内ファイルと同じ)
             String_extension.swift ( /sakrist/Swift_OpenGL_Example/Sources/app 内ファイルと同じ)
        /ObjCSwiftInterop ( GNUStepSwiftBridge と同じ)
        /SwiftMath ( /sakrist/SwiftMath/Sources  と同じ)
    Package.swift

OpenGL ライブラリ名は、COpenGL に変更。

COpenGL

module.modulemap
module COpenGL [system] {
     header "/usr/include/GL/gl.h"
     link "GL"
     export *
}


main.swift

// The Swift Programming Language
// https://docs.swift.org/swift-book

import AppKitGNUStep
import ObjCSwiftInterop

// check ResourcesFile
CheckResource()

// main
let delegate = AppDelegate()

let napClass =  objc_getClass("NSApplication")
var sharedApp =  forSwift_objcSendMessage(&napClass!.pointee, sel_registerName("sharedApplication"))
print("Just created NSApplication")

let v = forSwift_objcSendMessage1ID(&sharedApp!.pointee, sel_getUid("setDelegate:"), delegate._nsobjptr!)
print("made it!")

NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv.pointee)


AppDelegate.swift

// The Swift Programming Language
// https://docs.swift.org/swift-book

import Foundation
import AppKit
import SwiftMath

let fileManager = FileManager.default

let width  = 500.0
let height = 400.0

class AppDelegate: NSApplicationDelegate {
	lazy var window = NSWindow(CGRect(x: 150, y: 400, width: width, height: height + 50.0))
	lazy var view = UIView()
	lazy var glView = NSOpenGLView()
	lazy var button1 = NSButton()
	lazy var button2 = NSButton()
	lazy var button3 = NSButton()
	lazy var button4 = NSButton()
	lazy var textField1 = NSTextField()
	lazy var textField2 = NSTextField()

	override func applicationDidFinishLaunching(notification: Any?) {
		window.orderFront(sender: self)
		window.setTitle(NSString(string: "OpenGL Test"))

		var pitch:Float = 0.0
    	var yaw:Float = 0.0

		textField1.setBackgroundColor(.clear)
		textField1.textColor = .black
		textField1.isSelectable = false
		textField1.isBordered = false
		textField1.isBezeled = false
		textField1.drawsBackground = false
		textField1.frame = CGRect(x: 20, y: height + 20, width: 40, height: 30)
		textField1.stringValue = "pitch"

		button1.setTitle(NSString(string: "+"))
		button1.frame = .init(x: 70, y: height + 10, width: 30, height: 30)
		button2.setTitle(NSString(string: "-"))
		button2.frame = .init(x: 110, y: height + 10, width: 30, height: 30)

		textField2.setBackgroundColor(.clear)
		textField2.textColor = .black
		textField2.isSelectable = false
		textField2.isBordered = false
		textField2.isBezeled = false
		textField2.drawsBackground = false
		textField2.frame = CGRect(x: width / 2, y: height + 20, width: 40, height: 30)
		textField2.stringValue = "yaw"

		button3.setTitle(NSString(string: "+"))
		button3.frame = .init(x: width/2 + 50, y: height + 10, width: 30, height: 30)
		button4.setTitle(NSString(string: "-"))
		button4.frame = .init(x: width/2 + 90, y: height + 10, width: 30, height: 30)

		view.frame = .init(x: 0, y: 0, width: width, height: height)
		view.setBackgroundColor(NSColor(red: 0.8, green: 0.8, blue: 0.8, alpha: 1.0))

		glView.frame = .init(x: 0, y: 0, width: width, height: height)
		view.addSubview(glView)
		view.addSubview(textField1)
		view.addSubview(button1)
		view.addSubview(button2)
		view.addSubview(textField2)
		view.addSubview(button3)
		view.addSubview(button4)
		window.setContentView(view)

		glView.contextMakeCurrent()
		
		var renderObject: RenderObject?
    
    	var scene:Scene { 
        	get { 
            	return renderObject as! Scene
        	} 
        	set(scene) { 
            	renderObject = scene
        	}        
    	}

		scene = Scene()
		scene.size = Size(Int(width), Int(height))
        let rotateX = Matrix4x4f.rotate(x: Angle.init(radians: pitch))
        let rotateY = Matrix4x4f.rotate(y: Angle.init(radians: yaw))
        scene.modelViewMatrix = scene.modelViewMatrix * (rotateX * rotateY)
        scene.geometries.append(Cube())
		
		needsDisplay()

		button1.onAction = { button in
			pitch += 0.2
			//print("pitch yaw", pitch, yaw)
			let rotateX = Matrix4x4f.rotate(x: Angle.init(radians: pitch))
        	let rotateY = Matrix4x4f.rotate(y: Angle.init(radians: yaw))
        	scene.modelViewMatrix = Matrix4x4f.translate(tx: 0, ty: 0, tz: -4) * (rotateX * rotateY)
			needsDisplay()
		}
		button2.onAction = { button in
			pitch -= 0.2
			//print("pitch yaw", pitch, yaw)
			let rotateX = Matrix4x4f.rotate(x: Angle.init(radians: pitch))
        	let rotateY = Matrix4x4f.rotate(y: Angle.init(radians: yaw))
        	scene.modelViewMatrix = Matrix4x4f.translate(tx: 0, ty: 0, tz: -4) * (rotateX * rotateY)
			needsDisplay()
		}
		button3.onAction = { button in
			yaw += 0.2
			//print("pitch yaw", pitch, yaw)
			let rotateX = Matrix4x4f.rotate(x: Angle.init(radians: pitch))
        	let rotateY = Matrix4x4f.rotate(y: Angle.init(radians: yaw))
        	scene.modelViewMatrix = Matrix4x4f.translate(tx: 0, ty: 0, tz: -4) * (rotateX * rotateY)
			needsDisplay()
		}
		button4.onAction = { button in
			yaw -= 0.2
			//print("pitch yaw", pitch, yaw)
			let rotateX = Matrix4x4f.rotate(x: Angle.init(radians: pitch))
        	let rotateY = Matrix4x4f.rotate(y: Angle.init(radians: yaw))
        	scene.modelViewMatrix = Matrix4x4f.translate(tx: 0, ty: 0, tz: -4) * (rotateX * rotateY)
			needsDisplay()
		}
		
		func needsDisplay() {
			scene.render()
			glView.display()
		} 
	}
}


Package.swift

// swift-tools-version: 5.8
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
	name: "AppOpenGL",
	products: [
		// Products define the executables and libraries a package produces, making them visible to other packages.
		.executable(
			name: "Main",
			targets: ["Main"]),
	
		.library(name: "libobjc", targets: ["libobjc2"]),
		.library(name: "AppKitGNUStep", targets: ["AppKitGNUStep"]),
		.library(name: "FoundationGNUStep", targets: ["FoundationGNUStep"]),
		.library(name: "ObjCSwiftInterop",  targets: ["ObjCSwiftInterop"]),
		.library(name: "AppKit", targets: ["AppKit"]),
		.library(name: "SwiftMath", type: .static, targets: ["SwiftMath"]),
	],
	targets: [
		// Targets are the basic building blocks of a package, defining a module or a test suite.
		// Targets can depend on other targets in this package and products from dependencies.
		.executableTarget(
			name: "Main",
			dependencies: ["libobjc2", "AppKitGNUStep", "ObjCSwiftInterop", "FoundationGNUStep", "AppKit", "COpenGL", "SwiftMath"],
			cSettings: [.define("GL_GLEXT_PROTOTYPES")]
		),
		
		.target(name: "ObjCSwiftInterop"),
		.target(name: "AppKit",
			dependencies: ["libobjc2", "ObjCSwiftInterop", "FoundationGNUStep"]),
		.target(name: "SwiftMath",
			swiftSettings: [ .define("NOSIMD", .when(platforms: [.linux, .android, .windows, .wasi, ])),]),
		
		.systemLibrary(name: "libobjc2"),
		.systemLibrary(name: "AppKitGNUStep"),
		.systemLibrary(name: "FoundationGNUStep"),
		.systemLibrary(name: "COpenGL"),
	]
)

AppKit ライブラリを application 内のライブラリにしました。
これはライブラリの編集をしやすくするためです。

全体のファイルは以下にあります。
https://drive.google.com/file/d/1G5q0Rq74WN9jZBkfHBN4FY0szGTishro/view?usp=sharing

GNUStepSwiftBridge & CairoGraphics

前回の GNUStepSwiftBridge を利用して、CairoGraphics を行いました。
実行結果
起動時

メニューアイテム(AItem)をクリック時

InfoPanel の表示

前回からの変更点
Resources ファイルの .build/debug ディレクトリへのコピー
 前回はResouces を手動で コピーしていましたが、これをプログラム内で行うようにしました。FileManager を使用します。
CheckResource.swift

// The Swift Programming Language
// https://docs.swift.org/swift-book

// check Resources Files
import Foundation

func CheckResource() {
    let fileManager = FileManager.default
    let currentPath = fileManager.currentDirectoryPath
    let filePath:String =  currentPath + "/Sources/Resources"
    let copyPath:String = ".build/debug/Resources"

    let exist = fileManager.fileExists(atPath: copyPath)
    if !exist {
        do {
            try fileManager.copyItem(atPath: filePath, toPath: copyPath)
    	} catch {
            print("can not copy")
    	}
    }
}

AppKit ライブラリ
 前回はAppKit ライブラリを application project と同じフォルダに置いていましたが、これをAppKit 単体のライブラリにしました。
方法は以下の通り。

1 mkdir AppKit
2 cd AppKit
   swift package init --type library
3 copy AppKitGNUStep , FoundationGNUStep, libobjc2 , ObjCSwiftInterop in Sources dir
   copy Headers in AppKit dir
4 edit Sources/AppKit/AppKit.swift
   copy Foundation.swift , SwiftForGNUStep.swift
5 edit Package.swift
   // swift-tools-version: 5.8
   // The swift-tools-version declares the minimum version of Swift required to build
   this package.

   import PackageDescription

   let package = Package(
       name: "AppKit",
       products: [
           // Products define the executables and libraries a package produces, 
                 making them visible to other packages.
           .library(name: "libobjc", targets: ["libobjc2"]),
           .library(name: "AppKitGNUStep", targets: ["AppKitGNUStep"]),
           .library(name: "FoundationGNUStep", targets: ["FoundationGNUStep"]),
           .library(name: "ObjCSwiftInterop",  targets: ["ObjCSwiftInterop"]),
           .library(
               name: "AppKit",
               targets: ["AppKit"]),
       ],
       targets: [
           // Targets are the basic building blocks of a package,
                 defining a module or a test suite.
           // Targets can depend on other targets in this package and products
                 from dependencies.
           .target(name: "ObjCSwiftInterop"),
           .target(
                name: "AppKit",
                dependencies: ["libobjc2", "AppKitGNUStep", "ObjCSwiftInterop",
                                         "FoundationGNUStep"]),
           .systemLibrary(name: "libobjc2"),
           .systemLibrary(name: "AppKitGNUStep"),
           .systemLibrary(name: "FoundationGNUStep"),
           .testTarget(
                name: "AppKitTests",
                dependencies: ["AppKit"]),
       ]
   )
5 set git info
   in AppKit dir
       git init
       git add .
       git commit -m "Initial Commit"
       git tag 1.0.0

application (AppCG) ディレクトリ配置

    AppKit (上記ライブラリ)
    AppCG
       Sources
            CCairo
                 module.modulemap
            Resources
                 MainMenu.gorm
                 Info-gnustep.plist
                 ( tiff files )
            main.swift
            AppDelegate.swift
            Image.swift
            CGContext.swift
            CheckResource.swift
       Package.swift

MainMenu.gorm
Gorm を使って編集します。
 1 InfoPanel の表示
  メニューの InfoPanel を NSFirst に接続
  target から orderFrontStandardInfoPanel: を選択
 2 メニューアイテムの追加
  パレットウィンドウのメニューから、 Item をドラッグ

CCairo
module.modulemap

module CCairo [system] {
    module CairoXlib {
        header "/usr/include/cairo/cairo-xlib.h"
    }
    module Cairo {
        header "/usr/include/cairo/cairo.h"
    }
    link "cairo"
    export *
}

Package.swift

// swift-tools-version: 5.8
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "AppCG",
    dependencies: [
        .package(url: "../AppKit", from: Version(1,0,0))
    ],
    targets: [
        // Targets are the basic building blocks of a package, defining a module or a test suite.
        // Targets can depend on other targets in this package and products from dependencies.
        .executableTarget(
            name: "AppCG",
            dependencies: ["AppKit", "CCairo"],
            path: "Sources"
         ),
         .systemLibrary(
            name: "CCairo"),
    ]
)

main.swift

// The Swift Programming Language
// https://docs.swift.org/swift-book

import AppKitGNUStep
import ObjCSwiftInterop
import Foundation

// check ResourcesFile
CheckResource()

// main
let fileManager = FileManager.default

let delegate = AppDelegate()

let napClass =  objc_getClass("NSApplication")
var sharedApp =  forSwift_objcSendMessage(&napClass!.pointee, sel_registerName("sharedApplication"))
print("Just created NSApplication")

let v = forSwift_objcSendMessage1ID(&sharedApp!.pointee, sel_getUid("setDelegate:"), delegate._nsobjptr!)
print("made it!")

NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv.pointee)

AppDelegate.swift

// The Swift Programming Language
// https://docs.swift.org/swift-book

import FoundationGNUStep
import ObjCSwiftInterop
import AppKit
import CCairo.Cairo

let width = 640
let height = 480
var drawFlag = 1

func draw(_ context: CGContext, _ width: Float, _ height: Float) -> Void {
   // background
   context.setColor(1, 1, 1, 1)
   context.addRect(CGRect(x:0, y:0, width: Double(width), height: Double(height)))
   context.fillPath()

   switch (drawFlag) {
      case 1:
      // Draw random rects (some stroked, some filled)
      for i in 0...19 {
         if (i % 2 == 0) {
            context.setColor( Double.random(in: 0...1), Double.random(in: 0...1),
               Double.random(in: 0...1),  Double.random(in: 0...1))
            context.addRect(CGRect(
               x:Double.random(in: 0...1)*Double(width),
               y:Double.random(in: 0...1)*Double(height),
               width:Double.random(in: 0...1)*Double(width),
               height:Double.random(in: 0...1)*Double(height)))
            context.fillPath()
         }
         else {
            context.lineWidth = Float.random(in: 0...10) + 2
            context.setColor( Double.random(in: 0...1), Double.random(in: 0...1),
               Double.random(in: 0...1),  Double.random(in: 0...1))
            context.addRect(CGRect(
               x:Double.random(in: 0...1)*Double(width),
               y:Double.random(in: 0...1)*Double(height),
               width:Double.random(in: 0...1)*Double(width),
               height:Double.random(in: 0...1)*Double(height)))
            context.strokePath()
         }
      }

      case 2:
      // Draw random circles (some stroked, some filled)
      for i in 0...19 {
         context.addArc(
            center: CGPoint(x:Double.random(in: 0...1)*Double(width),
                            y:Double.random(in: 0...1)*Double(height)),
            radius: Float.random(in: 0...1)*((width>height) ? Float(height) : Float(width)),
            startAngle: 0, endAngle: 2*Float.pi, clockwise: false)
      
         if (i % 2 == 0) {
            context.setColor( Double.random(in: 0...1), Double.random(in: 0...1),
               Double.random(in: 0...1),  Double.random(in: 0...1))
            context.fillPath()
         }
         else {
            context.lineWidth = Float.random(in: 0...10) + 2
            context.setColor( Double.random(in: 0...1), Double.random(in: 0...1),
               Double.random(in: 0...1),  Double.random(in: 0...1))
            context.strokePath()
         }
      }

      default:
         break
   }
}

func display() {
	let cs = cairo_image_surface_create( CAIRO_FORMAT_ARGB32, Int32(width), Int32(height) )
	let c = CGContext(surface: cs, width: width, height: height)

   // draw
   draw(c, Float(width), Float(height))
   cairo_surface_write_to_png( cs, "test.png" )
}

class AppDelegate: NSApplicationDelegate {
	lazy var window = NSWindow(CGRect(x: 150, y: 380, width: 500, height: 450))
	lazy var imageView = NSImageView()
	lazy var view = UIView()

	override func applicationDidFinishLaunching(notification: Any?) {
		print("\nget mainMenu")
		let menu =  forSwift_objcSendMessage(&sharedApp!.pointee, sel_registerName("mainMenu"))
		// menu: add Action
		print("item at index")
		var aindex : Int = 1
		print("item index:", aindex)
		let aindexNSInt: UnsafeMutablePointer<objc_object> = unsafeBitCast(aindex, to: UnsafeMutablePointer<objc_object>.self)
		let aitem = forSwift_objcSendMessage1ID(&menu!.pointee, sel_getUid("itemAtIndex:"), aindexNSInt)
		print("index \(aindex) item:", aitem)
		let aitem2 = objc_convertToSwift_NSObject(value: aitem) as? NSString
		let aitemtitle = forSwift_objcSendMessage(&aitem!.pointee, sel_registerName("title"))
		let aitemtitle2 = objc_convertToSwift_NSObject(value: aitemtitle) as? NSString
		print("index \(aindex) item:", aitemtitle2!.string)
		
		let my_actionIMP: @convention(block) () -> () = {
			print("print from menu")
			drawFlag = 2
            display()

			let currentPath = fileManager.currentDirectoryPath
			let filePath:String =  currentPath + "/test.png"
        	let image2 = Image.imageWithPath(filePath)
        	self.imageView.setImage(image2!)
		}
		let types = "@"
		let imp = imp_implementationWithBlock(unsafeBitCast(my_actionIMP, to: id.self))
		class_addMethod(object_getClass(&aitem!.pointee), sel_registerName("my_action:"),imp, types)
		
		_ = forSwift_objcSendMessage1ID(&aitem!.pointee, sel_registerName("setTarget:"), aitem!)
		let sel = sel_getUid("my_action:")
		_ = forSwift_objcSendMessage1SEL(&aitem!.pointee, sel_registerName("setAction:"), sel!)
		//
        print("end menu\n")
        
        display()
        
		let currentPath = fileManager.currentDirectoryPath
		let filePath:String =  currentPath + "/test.png"
    	let image = Image.imageWithPath(filePath)
        
		window.orderFront(sender: self)
		window.setTitle(NSString(string: "Image Test"))

		view.frame = .init(x: 0, y: 0, width: 500, height: 450)

		imageView.frame = .init(x: 0, y: 0, width: 500, height: 450)
		
		imageView.setImage(image!)
		
		view.addSubview(imageView)

		window.setContentView(view)
	}
}

Image.swift

// The Swift Programming Language
// https://docs.swift.org/swift-book

import FoundationGNUStep
import ObjCSwiftInterop
import AppKit

class Image {
	static func imageWithPath(_ path: String) -> NSImage? {
		print("file path:", path)
		let nsfilePath = NSString(string: path)
		var nsImageClass = objc_getClass("NSImage")
		var initializedObject = forSwift_objcSendMessage(&nsImageClass!.pointee, sel_getUid("alloc"))
		var vimage = forSwift_objcSendMessage1(&initializedObject!.pointee, sel_getUid("initWithContentsOfFile:"), nsfilePath._nsobjptr)
        //print("vimage type:", type(of: vimage))
        let v = NSImage(nsobjptr: &vimage!.pointee)
        return v
    }
}

CGContext.swift

// The Swift Programming Language
// https://docs.swift.org/swift-book
//
//  CGContext.swift
//  CairoGraphics
//

import ObjCSwiftInterop
import CCairo.Cairo

open class CGContext {
    public var surface: Optional<OpaquePointer>
    public var context: Optional<OpaquePointer>
    
    public internal(set) var height: Int = 0
    public internal(set) var width: Int = 0

    public init(surface: Optional<OpaquePointer>, width: Int, height: Int) {
        self.context = cairo_create(surface)
        self.surface = surface
        self.width = width
        self.height = height
    }
}

public extension CGContext {
    func flush() {
        cairo_surface_flush(surface)
    }
}

public extension CGContext {
    func beginPath() {
        cairo_new_path(context)
    }
    
    func closePath() {
        cairo_close_path(context)
    }
    
    var currentPointOfPath: CGPoint {
        var x: Double = .zero
        var y: Double = .zero
        cairo_get_current_point(context, &x, &y)
        return CGPoint(x: x, y: y)
    }
    
    var boundingBoxOfPath: CGRect {
        var x1: Double = .zero
        var y1: Double = .zero
        var x2: Double = .zero
        var y2: Double = .zero
        
        cairo_path_extents(context, &x1, &y1, &x2, &y2)
        
        if x1.isZero && y1.isZero && x2.isZero && y2.isZero {
            //return .null
            return CGRect(x: x1, y: y1, width: x2, height: y2)
        } else {
            return CGRect(x: min(x1, x2), y: min(y1, y2), width: max(x1, x2) - min(x1, x2), height: max(y1, y2) - min(y1, y2))
        }
    }
}

public extension CGContext {
    func move(to point: CGPoint) {
        cairo_move_to(context, Double(point.x), Double(point.y))
    }
    
    func addLine(to point: CGPoint) {
        cairo_line_to(context, Double(point.x), Double(point.y))
    }
    
    func addRect(_ rect: CGRect) {
        cairo_rectangle(context, Double(rect.x), Double(rect.y), Double(rect.width), Double(rect.height))
    }
    
    func addCurve(to end: CGPoint, control1: CGPoint, control2: CGPoint) {
        cairo_curve_to(context,
                       Double(control1.x), Double(control1.y),
                       Double(control2.x), Double(control2.y),
                       Double(end.x), Double(end.y))
    }
    
    func addQuadCurve(to end: CGPoint, control: CGPoint) {
        let current = currentPointOfPath
        
        let control1 = CGPoint(x: (current.x / 3.0) + (2.0 * control.x / 3.0), y: (current.y / 3.0) + (2.0 * control.y / 3.0))
        let control2 = CGPoint(x: (2.0 * control.x / 3.0) + (end.x / 3.0), y: (2.0 * control.y / 3.0) + (end.y / 3.0))
        
        addCurve(to: end, control1: control1, control2: control2)
    }
    
    func addLines(between points: [CGPoint]) {
        if points.count == 0 { return }
        
        move(to: points[0])
        
        for i in 1..<points.count {
            addLine(to: points[i])
        }
    }
    
    func addRects(_ rects: [CGRect]) {
        for rect in rects {
            addRect(rect)
        }
    }
    
    func addArc(center: CGPoint, radius: Float, startAngle: Float, endAngle: Float, clockwise: Bool) {
        if clockwise {
            cairo_arc_negative(context, Double(center.x), Double(center.y), Double(radius), Double(startAngle), Double(endAngle))
        } else {
            cairo_arc(context, Double(center.x), Double(center.y), Double(radius), Double(startAngle), Double(endAngle))
        }
    }
}

public extension CGContext {
    func fillPath() {
        cairo_fill(context)
    }

    func clip() {
        cairo_clip(context)
    }
    
    func resetClip() {
        cairo_reset_clip(context)
    }
    
    func strokePath() {
        cairo_stroke(context)
    }
}

public extension CGContext {
    func fill(_ rect: CGRect) {
        beginPath()
        addRect(rect)
        closePath()
    }
    
    func stroke(_ rect: CGRect) {
        beginPath()
        addRect(rect)
        closePath()
        strokePath()
    }
}

public extension CGContext {
    func setColor(_ r:Double, _ g:Double, _ b:Double, _ a:Double) {
        cairo_set_source_rgba(context, r, g, b, a);
    }
    
    var lineWidth: Float {
        get {
            return Float(cairo_get_line_width(context))
        }
        set {
            cairo_set_line_width(context, Double(newValue))
        }
    }    
}

GNUStepSwiftBridge

discuss-gnustep mailing list(https://mail.gnu.org/archive/html/discuss-gnustep/)
の8月のリストに、以下のような投稿がありました。
Swift calling into GNUStep Progress
github: https://github.com/austintatiousness/GNUStepSwiftBridge

このスレッドに中に、swift から GNUstep gui を呼び出した画像があります。

この gui 呼出しにトライしてみました。
実行結果

(ウィンドウサイズ等は変えてあります)

以下がその方法です。

GNUstep
GNUStepSwiftBridge project には、
 This project is a Swift interface for GNUStep's version of AppKit.
 This project assumes that you are running OnFlapp's GNUStep Desktop project.
とあります。
そこで、 OnFlapp's GNUStep Desktop をインストールしておきます。
OnFlapp's GNUStep Desktop https://github.com/onflapp/gs-desktop

インストールは、記された手順通りにすれば、問題なく行えます。
Lubuntuの repo からのインストールと異なり、システムのルートディレクトリに
/Applications , /Developer , /Library , /System が作成され、そこにインストール
される。

Swift
Linux のシステムは、これまで使用している Lubuntu をそのまま利用します。
Swiftは、バージョンを5.8.1 にして、前回同様インストールします。

GNUStepSwiftBridge
上記の github からダウンロードし、任意の場所に保存します。


準備が整ったら、GNUStepSwiftBridge のルートディレクトリから、
 swift run NSWindowTest
と実行します。import AppKit のエラーが出ます。
これは、AppKit ライブラリを呼び出していますが、このプロジェクト内に
AppKit ライブラリが定義されていません。ライブラリが期待される場所(Sources/AppKit)に AppKit.swift がありますが、内容がライブラリと異なっています。

その関連を調べていたら、update メッセージの中にそれらしきものがありました。
それをもとに書き直した AppKit.swift です。
AppKit.swift

// The Swift Programming Language
// https://docs.swift.org/swift-book

import FoundationGNUStep
import libobjc2
import AppKitGNUStep
import ObjCSwiftInterop

//NSApplication
open class NSApplication: GNUStepNSObjectWrapper {
	public override var _nsclassName: String {
		return "NSApplication"
	}
	public var shared: NSApplication {
		get {
			var sharedApp =  forSwift_objcSendMessage(&self._nsobjptr!.pointee, sel_registerName("sharedApplication"))
			return NSApplication()
		}
	}
}

open class NSApplicationDelegate: GNUStepNSObjectWrapper {
	public override var _nsclassName: String {
		return "NSApplicationDelegateForSwift"
	}

	static var didFinishLaunchingIMP: @convention(block) (UnsafeMutablePointer<objc_object>?,SEL,id?) -> (UInt8) = { first, second, third in
		let SELF: NSApplicationDelegate? = smart_swift_lookupIvarWithType(_nsobjptr: first, name: "___swiftPtr")
		SELF?.applicationDidFinishLaunching(notification: nil)
		return 0
	}

	open func applicationDidFinishLaunching(notification: Any?) {

	}

	static var _objcClass = GNUStepNSObjectSubclassConstructor(name: "NSApplicationDelegateForSwift", superName: "NSObject", create: { ptr in
		var types = "i@:@"
		let imp = imp_implementationWithBlock(unsafeBitCast(didFinishLaunchingIMP, to: id.self))
		class_addMethod(ptr, sel_registerName("applicationDidFinishLaunching:"),imp, types)
	})

	public override init() {
		_ = NSApplicationDelegate._objcClass
		super.init()

		var nclass = objc_getClass("NSApplicationDelegateForSwift")
		var initalizedObject = forSwift_objcSendMessage(&nclass!.pointee, sel_getUid("alloc"))
		initalizedObject = forSwift_objcSendMessage(&initalizedObject!.pointee, sel_getUid("init"))
		self._nsobjptr = initalizedObject

		var cast = Unmanaged.passUnretained(self).toOpaque()
		smart_swift_setIvar(_nsobjptr: self._nsobjptr, name: "___swiftPtr", value: cast)
	}

	public required init(nsobjptr: UnsafeMutablePointer<objc_object>?) {
		super.init(nsobjptr: nsobjptr)
	}

	public func didFinishLaunching(_ OBJ: Any, notification: Any?) -> Void {

	}
}

open class UIView: NSView {
	public override var _nsclassName: String {
		return "UIView"
	}

	static var isFlippedIMP: @convention(block) (UnsafeMutablePointer<objc_object>?,SEL) -> (UInt8) = { first, second in
		print("!!!!!!!!!!!!!!!!!!!!!!!!asked if is flipped!")
		return 1
	}

	static var _objcClass = GNUStepNSObjectSubclassConstructor(
		name: "UIView", superName: "NSScrollView", create: { ptr in }
	)

	public override init() {
		_ = UIView._objcClass
		super.init()

		let nclass = objc_getClass("UIView")

		let types = "c"
		let imp = imp_implementationWithBlock(unsafeBitCast(UIView.isFlippedIMP, to: id.self))
		class_replaceMethod(UIView._objcClass._nsobjptr!, sel_registerName("isFlipped"),imp, types)

		var initalizedObject = forSwift_objcSendMessage(&nclass!.pointee, sel_getUid("alloc"))
		initalizedObject = forSwift_objcSendMessage1NSRect(&initalizedObject!.pointee, sel_getUid("initWithFrame:"), CGRect(x: 0, y: 0, width: 100, height: 100))
		initalizedObject = forSwift_objcSendMessage(&initalizedObject!.pointee, sel_getUid("retain"))
		self._nsobjptr = initalizedObject

		initalizedObject = forSwift_objcSendMessage(&initalizedObject!.pointee, sel_getUid("_rebuildCoordinates"))
		
		let cast = Unmanaged.passUnretained(self).toOpaque()
		smart_swift_setIvar(_nsobjptr: self._nsobjptr, name: "___swiftPtr", value: cast)
	}

	public required init(nsobjptr: UnsafeMutablePointer<objc_object>?) {
		super.init()
		self._nsobjptr = nsobjptr
		var initalizedObject = forSwift_objcSendMessage(&nsobjptr!.pointee, sel_registerName("retain"))
	}
}


public class NSFont: GNUStepNSObjectWrapper {
	public override var _nsclassName: String {
		return "NSFont"
	}

	public override init() {
		super.init()
	}

	public init?(name: String, size: Float) {
		super.init()

		var nsColorClass = objc_getClass("NSFont")
		var size = size
		var string = NSString(string: name)
		var imp:  (@convention(c) (id, SEL, id, Float) -> (id?))? = objc_smart_getIMP(id: &nsColorClass!.pointee, selector: "fontWithName:size:")
		if var rtn = imp?(&nsColorClass!.pointee, sel_getUid("fontWithName:size:"), &string._nsobjptr!.pointee, size) {
			self._nsobjptr = rtn
			print("FOUND FONT")
			var initalizedObject = forSwift_objcSendMessage(&rtn.pointee, sel_registerName("retain"))
		} else {
			print("FONT NOT FOUND")
			return nil
		}
	}

	public static func boldSystemFontOfSize(size: Double) -> NSFont {
		var nsColorClass = objc_getClass("NSFont")
		var size = size

		var imp:  (@convention(c) (id, SEL, Double) -> (id?))? = objc_smart_getIMP(id: &nsColorClass!.pointee, selector: "boldSystemFontOfSize:")
		if var rtn = imp?(&nsColorClass!.pointee, sel_getUid("boldSystemFontOfSize:"), size) {
			print("FOUND boldSystemFontOfSize")

			var initalizedObject = forSwift_objcSendMessage(&rtn.pointee, sel_registerName("retain"))
			return NSFont(nsobjptr: rtn)

		} else {
			return NSFont()
		}
	}

	public required init(nsobjptr: UnsafeMutablePointer<objc_object>?) {
		super.init()
		self._nsobjptr = nsobjptr
		var initalizedObject = forSwift_objcSendMessage(&nsobjptr!.pointee, sel_registerName("retain"))
	}
}

public class NSColor: GNUStepNSObjectWrapper {
	public override var _nsclassName: String {
		return "NSColor"
	}

	public override init() {
		super.init()

		let nsColorClass =  objc_getClass(self._nsclassName)
		var initalizedObject = forSwift_objcSendMessage(&nsColorClass!.pointee, sel_registerName("blueColor"))
		initalizedObject = forSwift_objcSendMessage(&initalizedObject!.pointee, sel_registerName("retain"))
		self._nsobjptr = initalizedObject
	}

	public init(red: Float, green: Float, blue: Float, alpha: Float) {
		super.init()
		let nsColorClass =  objc_getClass(self._nsclassName)
		let red = red
		let green = green
		let blue = blue
		let alpha = alpha
		var initalizedObject = forSwift_objcSendMessage4Floats(&nsColorClass!.pointee, sel_registerName("colorWithCalibratedRed:green:blue:alpha:"), red, green, blue, alpha)
		initalizedObject = forSwift_objcSendMessage(&initalizedObject!.pointee, sel_registerName("retain"))
		self._nsobjptr = initalizedObject
	}

	public static var clear: NSColor {
		return .init(red: 1.0, green: 1.0, blue: 1.0, alpha: 0.0)
	}

	public static var blue: NSColor {
		return .init(red: 0.0, green: 0.0, blue: 1.0, alpha: 1.0)
	}

	public static var black: NSColor {
		return .init(red: 0.0, green: 0.0, blue: 0.0, alpha: 1.0)
	}

	public static var white: NSColor {
		return .init(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
	}

	public static var grey: NSColor {
		return .init(red: 0.5, green: 0.5, blue: 0.5, alpha: 1.0)
	}

	public static var darkPurple: NSColor {
		return NSColor.init(red: 0.1422559619, green: 0.05977959186, blue: 0.2294596732, alpha: 1)
	}

	public static var purple: NSColor {
		let c =    NSColor.init(red: 0.2726541758, green: 0.1163508371, blue: 0.435738951, alpha: 1)
		return c
	}

	public required init(nsobjptr: UnsafeMutablePointer<objc_object>?) {
		super.init(nsobjptr: nsobjptr)
	}
}


open class NSImage: GNUStepNSObjectWrapper {
	public override var _nsclassName: String {
		return "NSImage"
	}

	public static func image(named: String) -> NSImage? {
		print("finding image named \(named)")
		var nsColorClass = objc_getClass("NSImage")
		var nsColorClassAsClass =  object_getClass(objc_getClass("NSImage"))

		var string = NSString(string: named)
		var imp:  (@convention(c) (id, SEL, id) -> (id?))? = objc_smart_getIMP(id: &nsColorClass!.pointee, selector: "imageNamed:")
		print(imp)
		if var rtn = imp?(&nsColorClass!.pointee, sel_getUid("imageNamed:"), &string._nsobjptr!.pointee) {
			var initalizedObject = forSwift_objcSendMessage(&rtn.pointee, sel_registerName("retain"))
			let v = NSImage(nsobjptr: &rtn.pointee)
			print("image is named \(v.name)")
			return v
		}
		return nil
	}

	public var name: String {
		get {
			print("getting name")
			if var ptr = self._nsobjptr {
				var imp: (@convention(c) (id, SEL) -> (id))? = objc_smart_getIMP(object: self, selector: "name")
				if let rtn = imp?(&ptr.pointee, sel_getUid("name")) {
					if let rtn = objc_convertToSwift_NSObject(value: rtn) as? NSString {
						return rtn.string
					}
				}
			}

			return ""
		}
	}
}

open class NSView: GNUStepNSObjectWrapper {
	public override var _nsclassName: String {
		return "NSView"
	}

	public var subviews: [Any] = []
	public func addSubview(_ subview: NSView) {
		subviews.append(subview)
		guard let selfPtr = self._nsobjptr else {return}
		if var ptr = subview._nsobjptr{
			_ = forSwift_objcSendMessage1(&selfPtr.pointee, sel_registerName("addSubview:"), &ptr.pointee)

		}
	}

	public func setBackgroundColor(_ color:  NSColor) {
		guard let colorPtr = color._nsobjptr else {return}
		guard let selfPtr = self._nsobjptr else {return}
		_ = forSwift_objcSendMessage1(&selfPtr.pointee, sel_registerName("setBackgroundColor:"), &colorPtr.pointee)
	}

	public var frame: CGRect {
		get {
			let imp:  (@convention(c) (id, SEL) -> (CGRect))? = objc_smart_getIMP(object: self, selector: "frame")
			if let rtn = imp?(&self._nsobjptr!.pointee, sel_getUid("frame")) {
				return rtn
			}
			return .init(x: 0, y: 0, width: 0, height: 0)
		}

		set {
			guard let selfPtr = self._nsobjptr else {return}
			var imp:  (@convention(c) (id, SEL, CGRect) -> (Void))? = objc_smart_getIMP(object: self, selector: "setFrame:")
			let rtn = imp?(&self._nsobjptr!.pointee, sel_getUid("setFrame:"), newValue)
		}
	}

	public func setFrame(_ rect: CGRect) {
		print("set frame: \(rect)")
		guard let selfPtr = self._nsobjptr else {return}
		_ = forSwift_objcSendMessage1NSRect(&selfPtr.pointee, sel_registerName("setFrame:"), rect)
	}

}

public class NSWindow: GNUStepNSObjectWrapper {
	public override var _nsclassName: String {
		return "NSWindow"
	}

	public override init() {
		super.init()

		let nsWindowClass =  objc_getClass(_nsclassName)
		var allocatedObject = forSwift_objcSendMessage(&nsWindowClass!.pointee, sel_registerName("alloc"))

		let styleMask: UInt = 1 + 2 + 4 + 8
		let backingStoreType: UInt = 0
		let deferr: Int8 = 0
		let rect = CGRect(x: 200, y: 200, width: 500, height: 500)
		allocatedObject = initWithContentRect_styleMask_backing_defer(&allocatedObject!.pointee, sel_registerName("initWithContentRect:styleMask:backing:defer:"), rect, styleMask, backingStoreType, deferr)
		self._nsobjptr = allocatedObject
		self.setTitle(NSString(string: "Untitled Window"))
	}

	public init(_ rect: CGRect) {
		super.init()
		let nsWindowClass =  objc_getClass(_nsclassName)
		var allocatedObject = forSwift_objcSendMessage(&nsWindowClass!.pointee, sel_registerName("alloc"))

		let styleMask: UInt = 1 + 2 + 4 + 8
		let backingStoreType: UInt = 0
		let deferr: Int8 = 0
		allocatedObject = initWithContentRect_styleMask_backing_defer(&allocatedObject!.pointee, sel_registerName("initWithContentRect:styleMask:backing:defer:"), rect, styleMask, backingStoreType, deferr)
		self._nsobjptr = allocatedObject
		self.setTitle(NSString(string: "Untitled Window"))
	}

	public required init(nsobjptr: UnsafeMutablePointer<objc_object>?) {
		super.init(nsobjptr: nsobjptr)
	}

	public func orderFront(sender:  Any?) {
		guard var ptr = self._nsobjptr else {return}
		_ = forSwift_objcSendMessage1(&ptr.pointee, sel_registerName("orderFront:"), nil)
	}


	public func setBackgroundColor(_ color:  NSColor) {
		guard let colorPtr = color._nsobjptr else {return}
		guard let selfPtr = self._nsobjptr else {return}
		_ = forSwift_objcSendMessage1(&selfPtr.pointee, sel_registerName("setBackgroundColor:"), &colorPtr.pointee)
	}

	var title: String {
		set {
			var title = NSString(string: newValue)
			if var ptr = title._nsobjptr, var selfPtr = self._nsobjptr {
				_ = forSwift_objcSendMessage1(&selfPtr.pointee, sel_registerName("setTitle:"), &ptr.pointee)
			}
		}

		get {
			if let x: NSString = objc_smart_sendMessage(object: self, selector: "title", value1: StopVariable(), value2: StopVariable(), value3: StopVariable(), value4: StopVariable(), value5: StopVariable(), value6: StopVariable(), value7: StopVariable(), value8: StopVariable(), value9: StopVariable()) {
				return x.string
			}
			return ""
		}
	}

	public func setTitle(_ title: NSString) {
		if var ptr = title._nsobjptr, var selfPtr = self._nsobjptr {
			_ = forSwift_objcSendMessage1(&selfPtr.pointee, sel_registerName("setTitle:"), &ptr.pointee)
		}
	}

	public var subviews: [Any] = []
	public func addSubview(_ subview: GNUStepNSObjectWrapper) {
		subviews.append(subview)
		guard let selfPtr = self._nsobjptr else {return}
		var contentViewPtr =  forSwift_objcSendMessage(&selfPtr.pointee, sel_registerName("contentView"))
		if var ptr = subview._nsobjptr, var contentViewPtr = contentViewPtr {
			_ = forSwift_objcSendMessage1(&contentViewPtr.pointee, sel_registerName("addSubview:"), &ptr.pointee)
		}
	}

	var contentView: NSView?
	public func setContentView(_ view: NSView) {
		guard let selfPtr = self._nsobjptr else {return}
		self.contentView = view
		if var ptr = view._nsobjptr {
			var _ =  forSwift_objcSendMessage1(&selfPtr.pointee, sel_registerName("setContentView:"), &ptr.pointee)
			_ = forSwift_objcSendMessage1(&ptr.pointee, sel_registerName("_invalidateCoordinates"), &ptr.pointee)
			_ = forSwift_objcSendMessage1(&ptr.pointee, sel_registerName("_rebuildCoordinates"), &ptr.pointee)
		}
	}

	public func printWindow() {
		guard let selfPtr = self._nsobjptr else {return}
		_ = forSwift_objcSendMessage1(&selfPtr.pointee, sel_registerName("print:"), nil)
	}

	public func setFrameOrigin(_ origin: ObjCSwiftInterop.NSPoint) {
		guard var ptr = self._nsobjptr else {return}
		var origin = origin

		_ = forSwift_objcSendMessage1(&ptr.pointee, sel_registerName("setFrameOrigin:"), &origin)
	}
}

public class NSControl: NSView {
	public func setEnabled(_ flag: Bool) {
		guard var ptr = self._nsobjptr else {return}
		var bool = flag
		_ = forSwift_objcSendMessage1(&ptr.pointee, sel_registerName("setEnabled:"), &bool)
	}

	public var stringValue: String {
		get {
			var imp:  (@convention(c) (id, SEL) -> (id))? = objc_smart_getIMP(object: self, selector: "stringValue")
			if let rtn = imp?(&self._nsobjptr!.pointee, sel_getUid("stringValue")) {
				if let rtn = objc_convertToSwift_NSObject(value: rtn) as? NSString {
					return rtn.string
				}
			}
			return ""
		}
		set {			
			guard var selfPtr = self._nsobjptr else {return}
			let ns = NSString(string: newValue)
			guard var stringPTR = ns._nsobjptr else {return}
			var imp:  (@convention(c) (id, SEL, id) -> (Void))? = objc_smart_getIMP(object: self, selector: "setStringValue:")
			let rtn = imp?(&selfPtr.pointee, sel_getUid("setStringValue:"), &stringPTR.pointee)
		}
	}
}


public class NSImageView: NSView {
	public override var _nsclassName: String {
		return "NSImageView"
	}

	public override init() {
		super.init()

		var rect = ObjCSwiftInterop.CGRect(x: 0, y: 0, width: 200, height: 22) //(x: Double(50), y: Double(50), width:  Double(50), height: Double(50))
		let  nsWindowClass =  objc_getClass("NSImageView")
		var allocatedObject = forSwift_objcSendMessage(&nsWindowClass!.pointee, sel_registerName("alloc"))

		allocatedObject = forSwift_objcSendMessage1NSRect(&allocatedObject!.pointee, sel_registerName("initWithFrame:"), rect)
		_ =  forSwift_objcSendMessage(&allocatedObject!.pointee, sel_registerName("retain"))
		
		self._nsobjptr = allocatedObject
	}

	public required init(nsobjptr: UnsafeMutablePointer<objc_object>?) {
		super.init()
		
		self._nsobjptr = nsobjptr
		var initalizedObject = forSwift_objcSendMessage(&nsobjptr!.pointee, sel_registerName("retain"))
	}

	public func setImage(_ image: NSImage) {
		print("Setting Image 1")
		guard var ptr = image._nsobjptr else {print("Setting Image image ptr == nil"); return}
		guard var selfPtr = self._nsobjptr else {print("Setting Image 1.2"); return}
		if var ptr = image._nsobjptr, var selfPtr = self._nsobjptr {
			print("Setting Image 2")
			_ = forSwift_objcSendMessage1ID(&selfPtr.pointee, sel_registerName("setImage:"), ptr)
		}
	}
}

public class NSButton: NSControl {
	static var _objcClass = GNUStepNSObjectSubclassConstructor(name: "NSButtonForSwift", superName: "NSButton", create: { ptr in})

	public override var _nsclassName: String {
		return "NSButton"
	}

	static var ___private_actionIMP: @convention(block) (UnsafeMutablePointer<objc_object>?,SEL,id?) -> (Void) = { first, second, third in
		let SELF: NSButton? = smart_swift_lookupIvarWithType(_nsobjptr: first, name: "___swiftPtr")
		if let SELF = SELF {
			SELF.onAction?(SELF)
		}
	}

	public lazy var onAction: ((NSButton) -> (Void))? = { first in }

	public override init() {
		super.init()
		_ = Self._objcClass
		var rect = ObjCSwiftInterop.CGRect(x: 0, y: 0, width: 200, height: 22) //(x: Double(50), y: Double(50), width:  Double(50), height: Double(50))
		let  nsWindowClass =  objc_getClass("NSButtonForSwift")
		var allocatedObject = forSwift_objcSendMessage(&nsWindowClass!.pointee, sel_registerName("alloc"))
		allocatedObject = forSwift_objcSendMessage1NSRect(&allocatedObject!.pointee, sel_registerName("initWithFrame:"), rect)
		_ =  forSwift_objcSendMessage(&allocatedObject!.pointee, sel_registerName("retain"))

		var types = "@"
		let imp = imp_implementationWithBlock(unsafeBitCast(NSButton.___private_actionIMP, to: id.self))
		class_addMethod(object_getClass(&allocatedObject!.pointee), sel_registerName("___private_action:"),imp, types)

		_ = forSwift_objcSendMessage1ID(&allocatedObject!.pointee, sel_registerName("setTarget:"), allocatedObject!)
		var sel = sel_getUid("___private_action:")
		print("SEL = \(sel!)")
		_ = forSwift_objcSendMessage1SEL(&allocatedObject!.pointee, sel_registerName("setAction:"), sel!)

		self._nsobjptr = allocatedObject
		
		var cast = Unmanaged.passUnretained(self).toOpaque()
		print("in NSButton about to cast \(cast)")
		smart_swift_setIvar(_nsobjptr: self._nsobjptr, name: "___swiftPtr", value: cast)
	}

	public required init(nsobjptr: UnsafeMutablePointer<objc_object>?) {
		super.init(nsobjptr: nsobjptr)
	}

	public func setTitle(_ text: NSString) {
		if var ptr = text._nsobjptr, var selfPtr = self._nsobjptr {
			_ = forSwift_objcSendMessage1ID(&selfPtr.pointee, sel_registerName("setTitle:"), ptr)
		}
	}

	public func setImage(_ image: NSImage) {
		if var ptr = image._nsobjptr, var selfPtr = self._nsobjptr {
			_ = forSwift_objcSendMessage1ID(&selfPtr.pointee, sel_registerName("setImage:"), ptr)
		}
	}
}

public class NSTextField: NSControl {
	public override var _nsclassName: String {
		return "NSTextField"
	}

	public override init() {
		super.init()

		var rect = CGRect(x: 10, y: 10, width: 200, height: 50)
		let  nsWindowClass =  objc_getClass("NSTextField")
		var allocatedObject = forSwift_objcSendMessage(&nsWindowClass!.pointee, sel_registerName("alloc"))
		allocatedObject = forSwift_objcSendMessage1(&allocatedObject!.pointee, sel_registerName("initWithFrame:"), &rect)
		allocatedObject =  forSwift_objcSendMessage(&allocatedObject!.pointee, sel_registerName("retain"))
		self._nsobjptr = allocatedObject
		self.setText(NSString(string: "This is me!"))
	}

	public required init(nsobjptr: UnsafeMutablePointer<objc_object>?) {
		super.init()
		self._nsobjptr = nsobjptr
		var initalizedObject = forSwift_objcSendMessage(&nsobjptr!.pointee, sel_registerName("retain"))
	}

	public func setText(_ text: NSString) {
		if var ptr = text._nsobjptr, var selfPtr = self._nsobjptr {
			_ = forSwift_objcSendMessage1(&selfPtr.pointee, sel_registerName("setStringValue:"), &ptr.pointee)
		}
	}

	public var font: NSFont? {
		get {
			var imp:  (@convention(c) (id, SEL) -> (id))? = objc_smart_getIMP(object: self, selector: "font")
			if let rtn = imp?(&self._nsobjptr!.pointee, sel_getUid("font")) {
				return NSFont(nsobjptr: rtn)
			}
			return nil
		}

		set {
			print("SETTING FONT")
			guard let selfPtr = self._nsobjptr else {print("selfPtr = nil"); return}
			guard let newValuePtr = newValue?._nsobjptr else {print("newValue = nil \(newValue)"); return}
			var imp:  (@convention(c) (id, SEL, id) -> (Void))? = objc_smart_getIMP(object: self, selector: "setFont:")
			let rtn = imp?(&self._nsobjptr!.pointee, sel_getUid("setFont:"), &newValuePtr.pointee)
		}
	}

	public func setEditable(_ flag: Bool) {
		guard var ptr = self._nsobjptr else {return}
		var bool = flag
		_ = forSwift_objcSendMessage1(&ptr.pointee, sel_registerName("setEditable:"), &bool)
	}

	public func setSelectable(_ flag: Bool) {
		guard var ptr = self._nsobjptr else {return}
		var bool = flag
		_ = forSwift_objcSendMessage1(&ptr.pointee, sel_registerName("setSelectable:"), &bool)
	}

	public func selectText() {
		guard var ptr = self._nsobjptr else {return}
		_ = forSwift_objcSendMessage(&ptr.pointee, sel_registerName("selectText"))
	}

	public var isSelectable: Bool {
		get {
			var imp:  (@convention(c) (id, SEL) -> (UInt8))? = objc_smart_getIMP(object: self, selector: "isSelectable")
			if let rtn = imp?(&self._nsobjptr!.pointee, sel_getUid("isSelectable")) {
				return rtn == 0 ? false : true
			}
			return true
		}

		set {
			guard let selfPtr = self._nsobjptr else {return}
			var imp:  (@convention(c) (id, SEL, UInt8) -> (Void))? = objc_smart_getIMP(object: self, selector: "setSelectable:")
			let rtn = imp?(&self._nsobjptr!.pointee, sel_getUid("setSelectable:"), newValue ? 1 : 0)
		}
	}

	public var isEditable: Bool {
		get {
			var imp:  (@convention(c) (id, SEL) -> (UInt8))? = objc_smart_getIMP(object: self, selector: "isEditable")
			if let rtn = imp?(&self._nsobjptr!.pointee, sel_getUid("isEditable")) {
				return rtn == 0 ? false : true
			}
			return true
		}

		set {
			guard let selfPtr = self._nsobjptr else {return}
			let imp:  (@convention(c) (id, SEL, UInt8) -> (Void))? = objc_smart_getIMP(object: self, selector: "setEditable:")
			let rtn = imp?(&self._nsobjptr!.pointee, sel_getUid("setEditable:"), newValue ? 1 : 0)
		}
	}

	public var textColor: NSColor {
		get {
			var imp:  (@convention(c) (id, SEL) -> (id))? = objc_smart_getIMP(object: self, selector: "textColor")
			if let rtn = imp?(&self._nsobjptr!.pointee, sel_getUid("textColor")) {
				return NSColor(nsobjptr: rtn)
			}
			return .blue
		}

		set {
			guard let selfPtr = self._nsobjptr else {return}
			guard let newValuePtr = newValue._nsobjptr else {return}
			var imp:  (@convention(c) (id, SEL, id) -> (Void))? = objc_smart_getIMP(object: self, selector: "setTextColor:")
			let rtn = imp?(&self._nsobjptr!.pointee, sel_getUid("setTextColor:"), &newValuePtr.pointee)
		}
	}

	public var isBordered: Bool {
		get {
			let imp:  (@convention(c) (id, SEL) -> (UInt8))? = objc_smart_getIMP(object: self, selector: "isBordered")
			if let rtn = imp?(&self._nsobjptr!.pointee, sel_getUid("isBordered")) {
				return rtn == 0 ? false : true
			}
			return true
		}

		set {
			guard let selfPtr = self._nsobjptr else {return}
			let imp:  (@convention(c) (id, SEL, UInt8) -> (Void))? = objc_smart_getIMP(object: self, selector: "setBordered:")
			let rtn = imp?(&self._nsobjptr!.pointee, sel_getUid("setBordered:"), newValue ? 1 : 0)
		}
	}

	public var isBezeled: Bool {
		get {
			let imp:  (@convention(c) (id, SEL) -> (UInt8))? = objc_smart_getIMP(object: self, selector: "isBezeled")
			if let rtn = imp?(&self._nsobjptr!.pointee, sel_getUid("isBezeled")) {
				return rtn == 0 ? false : true
			}
			return true
		}

		set {
			guard let selfPtr = self._nsobjptr else {return}
			let imp:  (@convention(c) (id, SEL, UInt8) -> (Void))? = objc_smart_getIMP(object: self, selector: "setBezeled:")
			let rtn = imp?(&self._nsobjptr!.pointee, sel_getUid("setBezeled:"), newValue ? 1 : 0)
		}
	}

	public var drawsBackground: Bool {
		get {
			let imp:  (@convention(c) (id, SEL) -> (UInt8))? = objc_smart_getIMP(object: self, selector: "drawsBackground")
			if let rtn = imp?(&self._nsobjptr!.pointee, sel_getUid("drawsBackground")) {
				return rtn == 0 ? false : true
			}
			return true
		}

		set {
			guard let selfPtr = self._nsobjptr else {return}
			let imp:  (@convention(c) (id, SEL, UInt8) -> (Void))? = objc_smart_getIMP(object: self, selector: "setDrawsBackground:")
			let rtn = imp?(&self._nsobjptr!.pointee, sel_getUid("setDrawsBackground:"), newValue ? 1 : 0)
		}
	}
}


この AppKit.swift をライブラリとして利用できるように、Package.swift を編集
します。
Package.swift

// swift-tools-version: 5.8
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
	name: "HelloWorld",
	products: [
		// Products define the executables and libraries a package produces, making them visible to other packages.
		.executable(
			name: "HelloWorld",
			targets: ["HelloWorld"]),
		
		.executable(
			name: "NSWindowTest",
			targets: ["NSWindowTest"]),
		
		.library(name: "libobjc", targets: ["libobjc2"]),
		
		.library(name: "AppKitGNUStep", targets: ["AppKitGNUStep"]),
		
		.library(name: "FoundationGNUStep", targets: ["FoundationGNUStep"]),
		.library(name: "ObjCSwiftInterop",  targets: ["ObjCSwiftInterop"]),

		.library(name: "AppKit", targets: ["AppKit"]),
	],
	targets: [
		// Targets are the basic building blocks of a package, defining a module or a test suite.
		// Targets can depend on other targets in this package and products from dependencies.
		.executableTarget(
			name: "HelloWorld",
			dependencies: ["libobjc2", "AppKitGNUStep", "ObjCSwiftInterop", "FoundationGNUStep", "AppKit"]
          //resources: [.copy("Resources")]
      ),

		.executableTarget(
			name: "NSWindowTest",
			dependencies: ["libobjc2", "AppKitGNUStep", "ObjCSwiftInterop", "FoundationGNUStep", "AppKit"]
			//resources: [.copy("Resources")]
		),
		
		.target(name: "ObjCSwiftInterop"),

		.target(
                                      name: "AppKit",
			dependencies: ["libobjc2", "AppKitGNUStep", "ObjCSwiftInterop", "FoundationGNUStep"]),
		
		.systemLibrary(name: "libobjc2"),
		.systemLibrary(name: "AppKitGNUStep"),
		.systemLibrary(name: "FoundationGNUStep"),

		.testTarget(
			name: "HelloWorldTests",
			dependencies: ["HelloWorld"]),
	]
)

次の部分を追加しています。
 products .library 文
 targets .executableTarget dependencies
 targets .target 文

NSWindowTest、HolloWorld の main.swift を以下のように編集します。
どちらも数行程、編集や追加を行っています。
NSWindowTest
main.swift

// The Swift Programming Language
// https://docs.swift.org/swift-book
import Foundation
import FoundationGNUStep
import libobjc2
import AppKitGNUStep
import ObjCSwiftInterop
import AppKit

@main
struct App {
	static var window = NSWindow()
	static var window2 = NSWindow()

	//static var label = NSLabel()
	static var label = NSTextField()
	static var button = NSButton()

	static func main() {
		print("Hello World Times Two")

		//var poolClass = objc_getClass("NSAutoreleasePool")
		//var poolClassAllocated =  forSwift_objcSendMessage(&objc_getClass("NSAutoreleasePool")!.pointee, sel_registerName("alloc"))
		//_ =  forSwift_objcSendMessage(&poolClassAllocated!.pointee, sel_registerName("init"))

		let napClass =  objc_getClass("NSApplication")
		var sharedApp =  forSwift_objcSendMessage(&napClass!.pointee, sel_registerName("sharedApplication"))
		
		App.window.orderFront(sender: nil)
		App.window.setBackgroundColor(NSColor(red: 1.0, green: 1.0, blue: 0.0, alpha: 0.5))

		App.label.frame = .init(x: 20, y: 20, width: 200, height: 32)
		
		App.label.setBackgroundColor(NSColor(red: 1.0, green: 1.0, blue: 0.0, alpha: 1.0))
		App.window.addSubview(App.label)
		
		let image = NSImage.image(named: "Dummy")

		App.button.frame = .init(x: 50, y: 100, width: 100, height: 30)

		App.window2.orderFront(sender: nil)
		App.window2.setFrameOrigin(NSPoint(x: 300, y: 300))
		App.window2.setTitle(NSString(string: "Window 2"))
		App.window2.addSubview(App.button)
		
		//App.window.printWindow()
		

		//window.setBackgroundColor(NSColor())

//'UnsafeMutablePointer<Int8>
//UnsafeMutablePointer<UnsafePointer<CChar>?>
		//return
		return NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv.pointee)		
	}
}

下の2行を追加しています。
 1 App.label.frame = .init(x: 20, y: 20, width: 200, height: 32)
  この行がないと、TextField が表示されない。
 2 let image = NSImage.image(named: "Dummy")
  この行がないと、Button 生成でコアダンプが起きる。
  App.button の前に必要。

HolloWorld
main.swift

// The Swift Programming Language
// https://docs.swift.org/swift-book
import Foundation
import FoundationGNUStep
import libobjc2
import AppKitGNUStep
import ObjCSwiftInterop
import AppKit

class AppDelegate: NSApplicationDelegate {
	//lazy var window = NSWindow()
	//lazy var newWindow = NSWindow()
	lazy var window = NSWindow(CGRect(x: 150, y: 300, width: 550, height: 450))
	lazy var newWindow = NSWindow(CGRect(x: 100, y: 100, width: 400, height: 400))
	lazy var button = NSButton()
	lazy var imageView = NSImageView()
	lazy var button2 = NSButton()
	lazy var button3 = NSButton()
	lazy var newWindowButton = NSButton()
	lazy var textField = NSTextField()
	lazy var image = NSImage.image(named: "Terminal.tiff")
	lazy var textField2 = NSTextField()
	lazy var view = UIView()
	override func applicationDidFinishLaunching(notification: Any?) {
		window.orderFront(sender: self)
		window.setTitle(NSString(string: "Hello Window"))

		view.frame = .init(x: 0, y: 0, width: 500, height: 450)

		newWindowButton.setTitle(NSString(string: "Show Window"))
		newWindowButton.frame = .init(x: 10, y: 22, width: 100, height: 32)
		newWindowButton.onAction = { button in
			self.newWindow.orderFront(sender: self)
			self.newWindow.setBackgroundColor(.purple)
		}
		view.addSubview(newWindowButton)
		
		imageView.frame = .init(x: 340, y: 22, width: 32, height: 32)
		imageView.setImage(self.image!)
		view.addSubview(imageView)
		
		button.setTitle(NSString(string: "Get Frame"))
		button.frame = .init(x: 120, y: 22, width: 100, height: 32)
		button.onAction = { button in
			self.textField.stringValue = "\(self.view.frame)"
		}
		view.addSubview(button)
		
		button2.setTitle(NSString(string: "Set Other Window"))
		button2.frame = .init(x: 230, y: 22, width: 100, height: 32)
		button2.onAction = { button in
			//self.view.frame = .init(x: 0, y: 32, width: 300, height: 300)
			self.newWindow.setBackgroundColor(.black)
		}
		view.addSubview(button2)
			
		button3.setImage(image!)
		button3.frame = .init(x: 430, y: 22, width: 100, height: 32)
		button3.onAction = { button in
			//self.view.frame = .init(x: 0, y: 32, width: 300, height: 300)
			//self.newWindow.setBackgroundColor(.black)
			self.newWindow.setBackgroundColor(.grey)
		}
		view.addSubview(button3)	
		
		textField.setBackgroundColor(.clear)
		//textField.textColor = .init(red: 1.0, green: 1.0, blue: 0.0, alpha: 0.5)
		textField.textColor = .blue
		textField.isSelectable = false
		textField.isBordered = false
		textField.isBezeled = false
		textField.drawsBackground = false
		
		textField.frame = CGRect(x: 10, y: 100, width: 300, height: 32)
		//textField.text = "Click 'Get Frame'"
		textField.stringValue = "Click 'Get Frame'"
		textField.font = .boldSystemFontOfSize(size: 30)
		view.addSubview(textField)

		textField2.frame = CGRect(x: 10, y: 140, width: 300, height: 32)
		//textField2.text = "Some Cool Text"
		textField2.stringValue = "Some Cool Text"
		textField2.textColor = .white
		textField2.setBackgroundColor(.grey)
		view.addSubview(textField2)
		
		view.setBackgroundColor(.init(red: 1.0, green: 0.0, blue: 1.0, alpha: 1.0))
		window.setContentView(view)
	}
}

@main
struct App {
	//static var window = NSWindow()
	static var delegate = AppDelegate()
	//static var window2 = NSWindow()

	//static var label = NSTextField()
	static func main() {
		print("Hello World Times Two")

		App.delegate

		//var poolClass = objc_getClass("NSAutoreleasePool")
		//var poolClassAllocated =  forSwift_objcSendMessage(&objc_getClass("NSAutoreleasePool")!.pointee, sel_registerName("alloc"))
		//_ =  forSwift_objcSendMessage(&poolClassAllocated!.pointee, sel_registerName("init"))

		
		//https://stackoverflow.com/questions/24662864/swift-how-to-use-sizeof
			
		let napClass =  objc_getClass("NSApplication")
		var sharedApp =  forSwift_objcSendMessage(&napClass!.pointee, sel_registerName("sharedApplication"))
		print("Just created NSApplication")
		
		let v = forSwift_objcSendMessage1ID(&sharedApp!.pointee, sel_getUid("setDelegate:"), delegate._nsobjptr!)
		print("made it!")

		//window.setBackgroundColor(NSColor())

//'UnsafeMutablePointer<Int8>
//UnsafeMutablePointer<UnsafePointer<CChar>?>
		//return
		return NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv.pointee)		
	}
}

1 TextField や Button は frame 設定がないと表示されない。
2 TextField の文字は text では表示されない。stringValue を用いる。


これで準備が整ったので、NSWindowTest と HelloWorld を実行します。
 swift run NSWindowTest
 swift run HelloWorld
NSWindowTest は実行できますが、HelloWorld ではコアダンプが起きます。
これは、ビルドされた実行ファイルがある場所に、gorm ファイルを含むResources
がないために起こっています。
この project site の最初に書いてあるように、Resources(NSWindowTest か HelloWorld のいずれか) を .build/debug にコピーします。(一度 swift run を実行しないと .build ディレクトリは作成されない。)今のところ、プログラムの中でコピーする方法が分かりません。

これで、 swift run を実行をすれば、正しく動作するはずです。
gorm ファイルのメニューも表示されます。

HolloWorld では、ボールドフォントのサイズ設定を行っています。
Lubuntu のシェルからの実行では、フォントが見つかりません。
/System/bin/startgsde を実行し、GWorkspace の Terminal から実行します。

Swift CairoGraphics

前回報告した GNUstep CoreGraphics は、「今更 GNUstep ?」感がありますので、同様な事を Swift で行ってみました。
Linux(Lubuntu 22.04) の Swift(v5.8) を使用しています。
(Swift のインストールは、UbuntuでSwiftの環境構築を行う方法を参照。
 バージョンは Ubuntu バージョンに揃えます。)

cairo ライブラリで描画した画像を、OpenGLで texture として描画します。
OpenGLglut ライブラリを用い、
Use a C library in Swift on Linux - Stack Overflow
に記述されている方法で利用します。
cairo ライブラリは、
AppKid
GitHub - smumriak/AppKid: UI toolkit for Linux in Swift. Powered by Vulkan
にあるCairoGraphics 関連を参照しました。

実行結果
起動時の画面 (Draw Rects) とメニュー(右クリックで表示)

Draw Circles

Draw Paths

Draw Clip

プログラム
ディレクトリ構成

TestCG
    Package.swift
    Sources
        CCairo
            module.modulemap
        CFreeGLUT
            module.modulemap
        COpenGL
             module.modulemap
        COpenGLU
             module.modulemap
        main.swift
        CGContext.swift

TestCG ディレクトリで、swift package init --type executable を実行する。
CCairo 等の各 module は、ディレクトリを作り modulemap ファイルを作成する。

CCairo
module.modulemap

module CCairo [system] {
    module CairoXlib {
        header "/usr/include/cairo/cairo-xlib.h"
    }
    module Cairo {
        header "/usr/include/cairo/cairo.h"
    }
    link "cairo"
    export *
}

CFreeGLUT
module.modulemap

module CFreeGLUT [system] {
    header "/usr/include/GL/freeglut.h"
    link "glut"
    export *
}

COpenGL
module.modulemap

module COpenGL [system] {
    header "/usr/include/GL/gl.h"
    link "GL"
    export *
}

COpenGLU
module.modulemap

module COpenGLU [system] {
    header "/usr/include/GL/glu.h"
    link "GLU"
    export *
}

Package.swift

// swift-tools-version: 5.8
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "TestCG",
    targets: [
        // Targets are the basic building blocks of a package, defining a module or a test suite.
        // Targets can depend on other targets in this package and products from dependencies.
        .executableTarget(
            name: "TestCG",
            dependencies: ["COpenGL", "COpenGLU", "CFreeGLUT", "CCairo"],
            path: "Sources"),
        .systemLibrary(
            name: "COpenGL"),
        .systemLibrary(
            name: "COpenGLU"),
        .systemLibrary(
            name: "CFreeGLUT"),
        .systemLibrary(
            name: "CCairo"),
    ]
)

main.swift

// The Swift Programming Language
// https://docs.swift.org/swift-book

//print("Hello, world!")
import COpenGL
import COpenGLU
import CFreeGLUT
import CCairo.Cairo
import Foundation

let width = 640
let height = 480
var drawFlag = 1

func drawRandomPaths(_ context: CGContext, _ width: CGFloat, _ height: CGFloat) -> Void {
   // Draw random paths (some stroked, some filled)
   for i in 0...19 {
      let numberOfSegments = Int.random(in: 0...7)
      let sx = CGFloat.random(in: 0...1) * width
      let sy = CGFloat.random(in: 0...1) * height
      context.move(to: CGPoint(
         x:CGFloat.random(in: 0...1)*width,
         y:CGFloat.random(in: 0...1)*height
      ))

      for j in 0...numberOfSegments {
         if (j % 2 == 0) {
            context.addLine(to: CGPoint(
               x:CGFloat.random(in: 0...1)*width,
               y:CGFloat.random(in: 0...1)*height
            ))
         }
         else {
            context.addCurve(
               to: CGPoint(
                  x:CGFloat.random(in: 0...1)*height,
                  y:CGFloat.random(in: 0...1)*height
               ),
               control1: CGPoint(
                  x:CGFloat.random(in: 0...1)*width,
                  y:CGFloat.random(in: 0...1)*height
               ),
               control2: CGPoint(
                  x:CGFloat.random(in: 0...1)*width,
                  y:CGFloat.random(in: 0...1)*height
               )
            )
         }
      }

      if (i % 2 == 0) {
         context.addCurve(
            to: CGPoint(x:sx, y:sy),
            control1: CGPoint(
               x:CGFloat.random(in: 0...1)*width,
               y:CGFloat.random(in: 0...1)*height
            ),
            control2: CGPoint(
               x:CGFloat.random(in: 0...1)*width,
               y:CGFloat.random(in: 0...1)*height
            )
         )
         context.closePath()
         context.setColor( Double.random(in: 0...1), Double.random(in: 0...1),
            Double.random(in: 0...1),  Double.random(in: 0...1))
         context.fillPath()
            
      }
      else {
            context.lineWidth = CGFloat.random(in: 0...10) + 2
            context.setColor( Double.random(in: 0...1), Double.random(in: 0...1),
                Double.random(in: 0...1),  Double.random(in: 0...1))
            context.strokePath()
      }
   }
}

func draw(_ context: CGContext, _ width: CGFloat, _ height: CGFloat) -> Void {
   // background
   context.setColor(1, 1, 1, 1)
   context.addRect(CGRect(x:0, y:0, width:width, height:height))
   context.fillPath()

   switch (drawFlag) {
      case 1:
      // Draw random rects (some stroked, some filled)
      for i in 0...19 {
         if (i % 2 == 0) {
            context.setColor( Double.random(in: 0...1), Double.random(in: 0...1),
               Double.random(in: 0...1),  Double.random(in: 0...1))
            context.addRect(CGRect(
               x:CGFloat.random(in: 0...1)*width,
               y:CGFloat.random(in: 0...1)*height,
               width:CGFloat.random(in: 0...1)*width,
               height:CGFloat.random(in: 0...1)*height))
            context.fillPath()
         }
         else {
            context.lineWidth = CGFloat.random(in: 0...10) + 2
            context.setColor( Double.random(in: 0...1), Double.random(in: 0...1),
               Double.random(in: 0...1),  Double.random(in: 0...1))
            context.addRect(CGRect(
               x:CGFloat.random(in: 0...1)*width,
               y:CGFloat.random(in: 0...1)*height,
               width:CGFloat.random(in: 0...1)*width,
               height:CGFloat.random(in: 0...1)*height))
            context.strokePath()
         }
      }

      case 2:
      // Draw random circles (some stroked, some filled)
      for i in 0...19 {
         context.addArc(
            center: CGPoint(x:CGFloat.random(in: 0...1)*CGFloat(width),
                            y:CGFloat.random(in: 0...1)*CGFloat(height)),
            radius: CGFloat.random(in: 0...1)*((width>height) ? CGFloat(height) : CGFloat(width)),
            startAngle: 0, endAngle: 2*CGFloat.pi, clockwise: false)
      
         if (i % 2 == 0) {
            context.setColor( Double.random(in: 0...1), Double.random(in: 0...1),
               Double.random(in: 0...1),  Double.random(in: 0...1))
            context.fillPath()
         }
         else {
            context.lineWidth = CGFloat.random(in: 0...10) + 2
            context.setColor( Double.random(in: 0...1), Double.random(in: 0...1),
               Double.random(in: 0...1),  Double.random(in: 0...1))
            context.strokePath()
         }
      }

      case 3:
      // Draw random paths (some stroked, some filled)
      drawRandomPaths(context, width, height)

      case 4:
      // Cliped: Draw random paths (some stroked, some filled)
      context.addArc(
         center: CGPoint(x:width/2, y:height/2),
         radius: (width>height) ? height/2 : width/2,
         startAngle: 0, endAngle: 2*CGFloat.pi, clockwise: false)
      context.closePath()
      context.clip()

      drawRandomPaths(context, width, height)

      context.lineWidth = 1
      context.setColor(0, 0, 0, 1)
      context.strokePath()

      default:
         break
   }
}

// Menu
func showMenuItem(_ val: Int32) {
   switch (val) {
      case 1:
         drawFlag = 1
      case 2:
         drawFlag = 2
      case 3:
         drawFlag = 3
      case 4:
         drawFlag = 4
      case 999:
         glutLeaveMainLoop()
      default:
         break
   }
}

func setupMenus() {
   glutCreateMenu(showMenuItem)

   glutAddMenuEntry("Draw Rects", 1)
   glutAddMenuEntry("Draw Circles", 2)
   glutAddMenuEntry("Draw Paths", 3)
   glutAddMenuEntry("Draw Clip", 4)
   glutAddMenuEntry("Exit", 999)

   glutAttachMenu(GLUT_RIGHT_BUTTON)
}

func display() {
   let cs = cairo_image_surface_create( CAIRO_FORMAT_ARGB32, Int32(width), Int32(height) )
   let c = CGContext(surface: cs, width: width, height: height)


   // draw
   draw(c, CGFloat(width), CGFloat(height))

   var _texture: GLuint = 0
   glGenTextures(1, &_texture);
   glBindTexture(UInt32(GL_TEXTURE_2D), _texture);
   glPixelStorei(UInt32(GL_UNPACK_ALIGNMENT), 1);

   let data = cairo_image_surface_get_data(cs);
   gluBuild2DMipmaps(UInt32(GL_TEXTURE_2D), Int32(GL_RGBA), Int32(width), Int32(height), UInt32(GL_RGBA), UInt32(GL_UNSIGNED_BYTE), data);
   
   glClearColor(0.0, 0.0, 0.0, 0.0)
   glClear(UInt32(GL_COLOR_BUFFER_BIT))
   glOrtho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0)
   
   glEnable(UInt32(GL_TEXTURE_2D));
   glBindTexture(UInt32(GL_TEXTURE_2D), _texture);
   glColor3f(1,1,1);

   glBegin(UInt32(GL_QUADS));
      glTexCoord2f(0.0, 1.0);
      glVertex2f(-1.0, -1.0);

      glTexCoord2f(0.0, 0.0);
      glVertex2f(-1.0, 1.0);

      glTexCoord2f(1.0, 0.0);
      glVertex2f(1.0, 1.0);

      glTexCoord2f(1.0, 1.0);
      glVertex2f(1.0, -1.0);
   glEnd();

   glFlush()
}

var localArgc = CommandLine.argc
glutInit(&localArgc, CommandLine.unsafeArgv)
glutInitDisplayMode(UInt32(GLUT_SINGLE | GLUT_RGB))
glutInitWindowPosition(80, 80)
glutInitWindowSize(Int32(width), Int32(height))
glutCreateWindow("CairoGraphics")
glutDisplayFunc(display)

setupMenus()
glutMainLoop()

CGContext.swift

// The Swift Programming Language
// https://docs.swift.org/swift-book
//
//  CGContext.swift
//  CairoGraphics
//
//  Created by Serhii Mumriak on 03.02.2020.
//

import Foundation
import CCairo.Cairo

open class CGContext {
    public var surface: Optional<OpaquePointer>
    public var context: Optional<OpaquePointer>
    
    public internal(set) var height: Int = 0
    public internal(set) var width: Int = 0

    public init(surface: Optional<OpaquePointer>, width: Int, height: Int) {
        self.context = cairo_create(surface)
        self.surface = surface
        self.width = width
        self.height = height
    }
}

public extension CGContext {
    func flush() {
        cairo_surface_flush(surface)
    }
}

public extension CGContext {
    func beginPath() {
        cairo_new_path(context)
    }
    
    func closePath() {
        cairo_close_path(context)
    }
    
    var currentPointOfPath: CGPoint {
        var x: Double = .zero
        var y: Double = .zero
        cairo_get_current_point(context, &x, &y)
        return CGPoint(x: x, y: y)
    }
    
    var boundingBoxOfPath: CGRect {
        var x1: Double = .zero
        var y1: Double = .zero
        var x2: Double = .zero
        var y2: Double = .zero
        
        cairo_path_extents(context, &x1, &y1, &x2, &y2)
        
        if x1.isZero && y1.isZero && x2.isZero && y2.isZero {
            return .null
        } else {
            return CGRect(x: min(x1, x2), y: min(y1, y2), width: max(x1, x2) - min(x1, x2), height: max(y1, y2) - min(y1, y2))
        }
    }
}

public extension CGContext {
    func move(to point: CGPoint) {
        cairo_move_to(context, Double(point.x), Double(point.y))
    }
    
    func addLine(to point: CGPoint) {
        cairo_line_to(context, Double(point.x), Double(point.y))
    }
    
    func addRect(_ rect: CGRect) {
        cairo_rectangle(context, Double(rect.origin.x), Double(rect.origin.y), Double(rect.width), Double(rect.height))
    }
    
    func addCurve(to end: CGPoint, control1: CGPoint, control2: CGPoint) {
        cairo_curve_to(context,
                       Double(control1.x), Double(control1.y),
                       Double(control2.x), Double(control2.y),
                       Double(end.x), Double(end.y))
    }
    
    func addQuadCurve(to end: CGPoint, control: CGPoint) {
        let current = currentPointOfPath
        
        let control1 = CGPoint(x: (current.x / 3.0) + (2.0 * control.x / 3.0), y: (current.y / 3.0) + (2.0 * control.y / 3.0))
        let control2 = CGPoint(x: (2.0 * control.x / 3.0) + (end.x / 3.0), y: (2.0 * control.y / 3.0) + (end.y / 3.0))
        
        addCurve(to: end, control1: control1, control2: control2)
    }
    
    func addLines(between points: [CGPoint]) {
        if points.count == 0 { return }
        
        move(to: points[0])
        
        for i in 1..<points.count {
            addLine(to: points[i])
        }
    }
    
    func addRects(_ rects: [CGRect]) {
        for rect in rects {
            addRect(rect)
        }
    }
    
    func addArc(center: CGPoint, radius: CGFloat, startAngle: CGFloat, endAngle: CGFloat, clockwise: Bool) {
        if clockwise {
            cairo_arc_negative(context, Double(center.x), Double(center.y), Double(radius), Double(startAngle), Double(endAngle))
        } else {
            cairo_arc(context, Double(center.x), Double(center.y), Double(radius), Double(startAngle), Double(endAngle))
        }
    }
}

public extension CGContext {
    func fillPath() {
        cairo_fill(context)
    }

    func clip() {
        cairo_clip(context)
    }
    
    func resetClip() {
        cairo_reset_clip(context)
    }
    
    func strokePath() {
        cairo_stroke(context)
    }
}

public extension CGContext {
    func fill(_ rect: CGRect) {
        beginPath()
        addRect(rect)
        closePath()
    }
    
    func stroke(_ rect: CGRect) {
        beginPath()
        addRect(rect)
        closePath()
        strokePath()
    }
}

public extension CGContext {
    func setColor(_ r:Double, _ g:Double, _ b:Double, _ a:Double) {
        cairo_set_source_rgba(context, r, g, b, a);
    }
    
    var lineWidth: CGFloat {
        get {
            return CGFloat(cairo_get_line_width(context))
        }
        set {
            cairo_set_line_width(context, Double(newValue))
        }
    }    
}

GNUstep CoreGraphics (2)

前回報告した ProjectCenter を使って作成した CoreGraphics のプログラムです。

実行結果
起動時の画面 (Draw1)

Draw2(メニューから Draw → Draw2 を選択)

Draw3

Draw4

プログラム
(プロジェクト生成時以降、編集したプログラム)
Headers
AppController.h

#ifndef _PCAPPPROJ_APPCONTROLLER_H
#define _PCAPPPROJ_APPCONTROLLER_H

#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
#import "QEB2OpenGLView.h"

@interface AppController : NSObject
{
    NSWindow *window;
    QEB2OpenGLView * openGLView;
}

+ (void)  initialize;

- (id) init;
- (void) dealloc;

- (void) awakeFromNib;

- (void) applicationDidFinishLaunching: (NSNotification *)aNotif;
- (BOOL) applicationShouldTerminate: (id)sender;
- (void) applicationWillTerminate: (NSNotification *)aNotif;
- (BOOL) application: (NSApplication *)application
              openFile: (NSString *)fileName;

- (void) showPrefPanel: (id)sender;
- (void) drawAction1: (id)sender;
- (void) drawAction2: (id)sender;
- (void) drawAction3: (id)sender;
- (void) drawAction4: (id)sender;

@end

#endif

QEB2OpenGLView.h (新しく生成)

#import <GL/gl.h>
#import <GL/glu.h>
#import <cairo/cairo.h>

#import <AppKit/AppKit.h>
#import <Foundation/Foundation.h>
#import <CoreGraphics/CoreGraphics.h>

@interface QEB2OpenGLView : NSOpenGLView
{
   cairo_surface_t * _cairoSurface;
   CGContextRef _opalContext;
   GLuint _texture;
   CGRect rootRect;
}

- (void) draw1;
- (void) draw2;
- (void) draw3;
- (void) draw4;
- (void) reDraw;
- (void) clearContextWithRect:(CGRect)rect;
@end

Classes
AppController.m

#import "AppController.h"

#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>

@implementation AppController

+ (void) initialize
{
   NSMutableDictionary *defaults = [NSMutableDictionary dictionary];

   /*
    * Register your app's defaults here by adding objects to the
    * dictionary, eg
    *
    * [defaults setObject:anObject forKey:keyForThatObject];
    *
    */
  
   [[NSUserDefaults standardUserDefaults] registerDefaults: defaults];
   [[NSUserDefaults standardUserDefaults] synchronize];
}

- (id) init
{
   if ((self = [super init]))
   {
   }
      return self;
}

- (void) dealloc
{
   RELEASE (openGLView);
   RELEASE (window);
   [super dealloc];
}

- (void) awakeFromNib
{
}

- (void) applicationDidFinishLaunching: (NSNotification *)aNotif
{ 
   openGLView = [[QEB2OpenGLView alloc]
               initWithFrame: [[window contentView] frame]
                   pixelFormat: [QEB2OpenGLView defaultPixelFormat]];
  
   [window setContentView: openGLView];
  
   [openGLView reDraw];
}

- (BOOL) applicationShouldTerminate: (id)sender
{
   return YES;
}

- (void) applicationWillTerminate: (NSNotification *)aNotif
{
}

- (BOOL) application: (NSApplication *)application
       	      openFile: (NSString *)fileName
{
   return NO;
}

- (void) showPrefPanel: (id)sender
{
}

- (void) drawAction1: (id)sender
{
   NSLog(@"drawAction1");
   [openGLView draw1];
}

- (void) drawAction2: (id)sender
{
   NSLog(@"drawAction2");
   [openGLView draw2];
}

- (void) drawAction3: (id)sender
{
   NSLog(@"drawAction3");
   [openGLView draw3];
}

- (void) drawAction4: (id)sender
{
   NSLog(@"drawAction4");
   [openGLView draw4];
}

@end

QEB2OpenGLView.m (新しく生成)

#import "QEB2OpenGLView.h"

#define TEXTURE_TARGET GL_TEXTURE_2D

#define PI 3.14159265358979323846

CGContextRef opal_new_CGContext(cairo_surface_t *target, CGSize device_size);

void drawRandomPaths(CGContextRef context, int w, int h)
{
   for (int i = 0; i < 20; i++) {
      int numberOfSegments = rand() % 8;
      int j;
      CGFloat sx, sy;
        
      CGContextBeginPath(context);
      sx = rand()%w; sy = rand()%h;
      CGContextMoveToPoint(context, rand()%w, rand()%h);
      for (j = 0; j < numberOfSegments; j++) {
         if (j % 2) {
            CGContextAddLineToPoint(context, rand()%w, rand()%h);
         }
         else {
            CGContextAddCurveToPoint(context, rand()%w, rand()%h,  
               rand()%w, rand()%h,  rand()%h, rand()%h);
         }
      }
      if(i % 2) {
         CGContextAddCurveToPoint(context, rand()%w, rand()%h,
            rand()%w, rand()%h,  sx, sy);
         CGContextClosePath(context);
         CGContextSetRGBFillColor(context, (CGFloat)(rand()%256)/255, 
            (CGFloat)(rand()%256)/255, (CGFloat)(rand()%256)/255, 
            (CGFloat)(rand()%256)/255);
         CGContextFillPath(context);
      }
      else {
         CGContextSetLineWidth(context, (rand()%10)+2);
         CGContextSetRGBStrokeColor(context, (CGFloat)(rand()%256)/255, 
            (CGFloat)(rand()%256)/255, (CGFloat)(rand()%256)/255, 
            (CGFloat)(rand()%256)/255);
         CGContextStrokePath(context);
      }
   }
}

@implementation QEB2OpenGLView

- (void) draw1
{
   NSLog(@"GLView draw1");
   [self clearContextWithRect:rootRect];
   
   int width  = rootRect.size.width;
   int height = rootRect.size.height;
  
   for (int i = 0; i < 20; i++) {
      if(i % 2) {
         CGContextSetRGBFillColor(_opalContext, (CGFloat)(rand()%256)/255, 
            (CGFloat)(rand()%256)/255, (CGFloat)(rand()%256)/255, 
            (CGFloat)(rand()%256)/255);
         CGContextFillRect(_opalContext, CGRectMake(rand()%width,
            rand()%height, rand()%width, rand()%height));
      } 
      else {
         CGContextSetLineWidth(_opalContext, (rand()%10)+2);
            CGContextSetRGBStrokeColor(_opalContext, (CGFloat)(rand()%256)/255, 
            (CGFloat)(rand()%256)/255, (CGFloat)(rand()%256)/255, 
            (CGFloat)(rand()%256)/255);
         CGContextStrokeRect(_opalContext, CGRectMake(rand()%width,
            rand()%height, rand()%width, rand()%height));
      }
   }

   [self reDraw];
}

- (void) draw2
{
   NSLog(@"GLView draw2");
   [self clearContextWithRect:rootRect];
   
   int width  = rootRect.size.width;
   int height = rootRect.size.height;
  
   // Draw random circles (some stroked, some filled)
   for (int i = 0; i < 20; i++) {
      CGContextBeginPath(_opalContext);
      CGContextAddArc(_opalContext, rand()%width, rand()%height,
         rand()%((width>height) ? height : width), 0, 2*PI, 0);
      CGContextClosePath(_opalContext);

      if(i % 2) {
         CGContextSetRGBFillColor(_opalContext,
            (CGFloat)(rand()%256)/255, (CGFloat)(rand()%256)/255,
            (CGFloat)(rand()%256)/255, (CGFloat)(rand()%256)/255);
         CGContextFillPath(_opalContext);
      }
      else {
         CGContextSetLineWidth(_opalContext, (rand()%10)+2);
         CGContextSetRGBStrokeColor(_opalContext,
            (CGFloat)(rand()%256)/255, (CGFloat)(rand()%256)/255,
            (CGFloat)(rand()%256)/255, (CGFloat)(rand()%256)/255);
         CGContextStrokePath(_opalContext);
      }
   }
   
   [self reDraw];
}

- (void) draw3
{
   NSLog(@"GLView draw3");
  [self clearContextWithRect:rootRect];
   
   drawRandomPaths(_opalContext, rootRect.size.width,
      rootRect.size.height);
      
   [self reDraw];
}

- (void) draw4
{
   NSLog(@"GLView draw4");
   [self clearContextWithRect:rootRect];
   
   int width  = rootRect.size.width;
   int height = rootRect.size.height;
  
   /* Clipping example - draw random path through a circular clip */
   CGContextBeginPath(_opalContext);
   CGContextAddArc(_opalContext, width/2, height/2,
      ((width>height) ? height : width)/2, 0, 2*PI, 0);
   CGContextClosePath(_opalContext);
   CGContextClip(_opalContext);
        
   // Draw something into the clip
   drawRandomPaths(_opalContext, width, height);
   
   // Draw an clip path on top as a black stroked circle.
   CGContextBeginPath(_opalContext);
   CGContextAddArc(_opalContext, width/2, height/2,
      ((width>height) ? height : width)/2, 0, 2*PI, 0);
   CGContextClosePath(_opalContext);
   CGContextSetLineWidth(_opalContext, 1);
   CGContextSetRGBStrokeColor(_opalContext, 0, 0, 0, 1);
   CGContextStrokePath(_opalContext);
   
   [self reDraw];
   
   OPContextResetClip(_opalContext);
}

-(void) reDraw
{  
   [self reshape];
}

- (void) clearContextWithRect:(CGRect)rect
{
   CGContextSetRGBFillColor(_opalContext, 1, 1, 1, 1);
   CGContextFillRect(_opalContext, rect);
}

- (void) prepareOpenGL
{
   [super prepareOpenGL];
   int width  = [self frame].size.width;
   int height = [self frame].size.height;

   _cairoSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
   _opalContext = opal_new_CGContext(_cairoSurface, CGSizeMake(width, height));
  
   /* Draw some content into the context */
   rootRect = CGRectMake(0, 0, width, height);
   [self clearContextWithRect:rootRect];
   
   // Draw random rectangles (some stroked some filled)
   for (int i = 0; i < 20; i++) {
      if(i % 2) {
         CGContextSetRGBFillColor(_opalContext, (CGFloat)(rand()%256)/255, 
            (CGFloat)(rand()%256)/255, (CGFloat)(rand()%256)/255, 
            (CGFloat)(rand()%256)/255);
         CGContextFillRect(_opalContext, CGRectMake(rand()%width,
            rand()%height, rand()%width, rand()%height));
      } 
      else {
         CGContextSetLineWidth(_opalContext, (rand()%10)+2);
            CGContextSetRGBStrokeColor(_opalContext, (CGFloat)(rand()%256)/255, 
            (CGFloat)(rand()%256)/255, (CGFloat)(rand()%256)/255, 
            (CGFloat)(rand()%256)/255);
         CGContextStrokeRect(_opalContext, CGRectMake(rand()%width,
            rand()%height, rand()%width, rand()%height));
      }
  }
}

- (void) dealloc
{
   cairo_surface_finish(_cairoSurface);
   CGContextRelease(_opalContext);

   [super dealloc];
}

-(void) reshape
{
   NSLog(@"reshaping");
   NSRect rect = [self bounds];
   glViewport(0, 0, rect.size.width, rect.size.height);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   gluPerspective(60.0, rect.size.width/rect.size.height, 0.2, 7);

   [self setNeedsDisplay:YES];
}

- (void) drawRect: (NSRect)r
{
   [[self openGLContext] makeCurrentContext];
   
   int width = [self frame].size.width;
   int height = [self frame].size.height;
   
   glGenTextures(1, &_texture);
   glBindTexture(TEXTURE_TARGET, _texture);
   glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
   unsigned char * data = cairo_image_surface_get_data(_cairoSurface);
   gluBuild2DMipmaps(TEXTURE_TARGET, GL_RGBA, width, height, GL_RGBA, GL_UNSIGNED_BYTE, data);
   
   glViewport(0, 0, [self frame].size.width, [self frame].size.height);
 
   glClear(GL_COLOR_BUFFER_BIT);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();

   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();

   glEnable(GL_TEXTURE_2D);
   glEnable(TEXTURE_TARGET);
   glBindTexture(TEXTURE_TARGET, _texture);
   glColor3f(1,1,1);

   glBegin(GL_QUADS);
   glTexCoord2f(0.0, 1.0);
   glVertex2f(-1.0, -1.0);

   glTexCoord2f(0.0, 0.0);
   glVertex2f(-1.0, 1.0);

   glTexCoord2f(1.0, 0.0);
   glVertex2f(1.0, 1.0);

   glTexCoord2f(1.0, 1.0);
   glVertex2f(1.0, -1.0);
   glEnd();

   [[self openGLContext] flushBuffer];
}

@end

Supporting Files
GNUmakefile.preamble(変更箇所のみ)

# Additional flags to pass to Objective C compiler
ADDITIONAL_OBJCFLAGS += -std=gnu99

# Additional flags to pass to C compiler
ADDITIONAL_CFLAGS += -std=gnu99

# Additional GUI libraries to link
ADDITIONAL_GUI_LIBS += -lm -lGL -lGLU -lopal -lcairo

Interfaces
(プロジェクト名).gorm
変更は下参照


ProjectCenter と Gorm によるプログラム作成
1 ProjectCenter でプロジェクトを作成
 Project Types は Application を選択
 ビルドとランを実行
 [Main Menu] が表示される。
2 window を追加
 Interfaces の (プロジェクト名).gorm をダブルクリック
 Gorm が起動する。
 Gorm のControls window -> Windows から [Window] を
 Resources window の Objects にドロップ
 Window(0) インスタンスが生成される。
 size, attributes (Visual at launch をチェック)を設定する。

 設定を保存する場合
 Gorm メインメニュー -> Document -> Save

 AppController と Window の接続
 Window の View の設定は AppController で行うので、AppController と
 Window を接続する。
(1)AppController Outlet の作成
  (作成した Outlet 名が winsow instance の名前になる、Action は要らない)
  Resources window の Classes で、NSObject -> AppController を選択
  Inspector window の Outlet add ボタンで作成(Outlet名: window)
 (AppController が表示されていない場合は、下のスクロールバーを
  左にする)
(2)接続
  Resources window の Objects で、 AppController(S) から Window(0)(T) に接続
  接続は、[Ctrl]キーを押しながらドラッグする。
  Inspector window で、Outlet window を選択し接続[Connect] する。

3 Draw menu の追加
 interfaces の gorm file をダブルクリックし、 Gorm を起動
 ・[Main Menu](作成プログラムのメニュー)に [Submenu] を追加
  Controls window -> Menus から、 [Submenu>] を [Main Menu] の目的の
  場所にドラッグする。
  [Submanu] が追加され、メニューに [item] が1個できる。
  Item 名を変更する。(Draw1 など)
  ( 2つ目い以降は、 Menus の [item] をドッラグして追加する)
 ・Outlet/Action の追加と接続
  [Submenu] のコントロールは AppController で行うようにするので、
  [Submenu] を AppController に接続する。
  *Outlet/Action の追加
  AppController に
   Outlet: draw1, draw2, draw3, draw4
   Action: drawAction1:, drawAction2:, drawAction3:, drawAction4
  を追加する。
  *接続
  [Main Menu] の Draw1(draw1) を AppController(drawAction1:) に接続
  接続方法は window の場合と同様。(残りのメニューも同様に接続する)

4 プログラムの生成と編集
 QEB2OpenGLView.h, QEB2OpenGLView.m の生成と編集
 (新しく作成したクラスファイルのプロジェクトへの追加は、Classes を
  選択しメニューから Project -> Add Files を実行する。)
 AppController.h, AppController.m の編集
 GNUmakefile.preamble の編集

GNUstep CoreGraphics (1)

今回は、ブログタイトルから離れて、GNUstep について書いています。

最近 GNUstepObjective-C についての記事が、2、3報告されています。
今更のObjective-C | 貯え 小道具 興味ない?
M1 Mac-Ubuntu20.04でGNUstep | 貯え 小道具 興味ない?
GNUstepをWindowsで試すならWSLを使う-Ubuntu 20.04 LTS+WSL2へのインストール - Technically Impossible

当方 2007年に「GNUstep 入門」(ふくろう出版、友野印刷)を出版しています。
Objective-C プログラミングや ProjectCenter、Gorm の使用方法について記してい
ます。

GNUstep は、それ以降もライブラリの追加やアップデートが行われています。
今回は、CoreGraphics(libs-opal) について調べてみました。
CoreGraphics による描画

ProjectCenter を使ってプログラムの編集、ビルド、実行を行っています。

OSは Lubuntu22.04 Linux を使用しています。GNUstep のベースシステムは
Lubuntu の repo からインストールします。
プログラミングを行うので、
・make, gnustep-make, gcc, gobjc などの build tools
・ProjectCenter, Gorm のプログラミングツール
もインストールしておきます。

*日本語の表示
スタートアップメニューの設定から System Preferences を起動し、Preferences
ウインドウから「Font」を選択する。
最初は、gnustep default の DejaVuSansMono を選択し、その後
NotoSans CJK jp Regular等(Lubuntu の日本語フォント)に変更する。

*CoreGraphics(libs-opal)、CoreFoundation(libs-corebase, 依存ライブラリ) の
ビルドとインストール
libs-corebase とlibs-opalはバイナリーが無いので、ソースからビルドし
インストールします。ソースはGNUstepGithub からダウンロードします。
・libs-corebase
 1依存ライブラリ libicu-dev をインストール(lubuntu repo から)
 2./configure
 3make
 4sudo make install

・libs-opal
 1依存ライブラリ libfontconfig-dev、libcairo2-dev、liblcms2-dev、
   libjpeg-turbo8-dev、libtiff-devをインストール(lubuntu repo から)
 2make
 3sudo make install
 libファイルlibopal.so.o は /usr/local/lib にインストールされるので、
 フォルダを設定します。
  export LD_LIBRARY_PATH=/usr/local/lib

・libs-QuartzCore
 libs-opal では、XWindow に表示するようになっています。これをNSWindow に
 表示するため、libs-QuartzCore を利用します。NSOpenGLView に表示
 します。
 1依存ライブラリ libgl-dev、libglu1-mesa-devをインストール
  (lubuntu repo から)
 2Tests の make で、cairo reference error が出るので、
  Tests/GNUmakefile の ADDITIONAL_TOOL_LIBS に -lcairo を追加
 3make
 4sudo make install

*ProjectCenter のビルドとインストール
CoreGraphics では gnustep 以外に OpenGL、opal、cairo等のライブラリも使用します。これらが利用出来るように、プログラムのビルド時にGNUmakefile.preamble
に設定します。
  ADDITIONAL_GUI_LIBS += -lm -lGL -lGLU -lopal -lcairo

しかし、1つ問題があります。ProjectCenter では、プロジェクトの保存や
再オープン時に GNUmakefile.preamble に設定した内容が、リセット されます。
これを避けるため、ProjectCenter のソースファイルを修正しインストールします。
ソース(apps-projectcenter)はGNUstepGithub からダウンロードします。(バージョンは 0.7)
・修正箇所
Modules/Projects にある各Projectの PC(Project名).m を修正する。
writeMakefile メソッド内の [mf createPreambleForProject:self] を
createProjectAt メソッド内に移動し、
[[PCMakefileFactory sharedFactory] createPreambleForProject:self];
とする。
その後、
 make
 sudo make install
を行う。

実際のプログラム作成は、次回に報告。