mlx_window.swift
1 2 import Cocoa 3 import Metal 4 import MetalKit 5 import Darwin 6 7 import mlx_image 8 9 10 class WinEvent: NSWindow 11 { 12 var eventFuncts = [UnsafeMutableRawPointer?]() 13 var eventParams = [UnsafeMutableRawPointer]() 14 /// var eventParams = UnsafeMutableRawPointer.allocate(capacity: 32) 15 16 var keyrepeat = 1 17 var keyflag:UInt32 = 0 18 19 public var size_y:Int 20 21 init(frame rect:CGRect) 22 { 23 /// eventParams.initialize(to:nil, count:32) 24 var ptr = UnsafeMutableRawPointer(bitPattern:1)! 25 ptr -= 1 26 for _ in 0...31 27 { 28 eventFuncts.append(Optional.none) 29 /// eventParams.append(UnsafeMutableRawPointer(&keyrepeat)) /// dummy address here, null not needed 30 eventParams.append(ptr) 31 } 32 33 let wsm = NSWindow.StyleMask(rawValue: NSWindow.StyleMask.titled.rawValue|NSWindow.StyleMask.closable.rawValue|NSWindow.StyleMask.miniaturizable.rawValue) 34 let bck = NSWindow.BackingStoreType.buffered 35 size_y = Int(rect.size.height) 36 super.init(contentRect: rect, styleMask: wsm, backing: bck, defer: false) 37 } 38 39 func setNotifs() 40 { 41 NotificationCenter.default.addObserver(self, selector: #selector(exposeNotification(_:)), name: NSWindow.didBecomeKeyNotification, object: nil) 42 NotificationCenter.default.addObserver(self, selector: #selector(deminiaturizeNotification(_:)), name: NSWindow.didDeminiaturizeNotification, object: nil) 43 NotificationCenter.default.addObserver(self, selector: #selector(closeNotification(_:)), name: NSWindow.willCloseNotification, object: nil) 44 45 /*** 46 [[NSNotificationCenter defaultCenter] addObserver:win selector:@selector(exposeNotification:) name:@"NSWindowDidBecomeKeyNotification" object:win]; 47 [[NSNotificationCenter defaultCenter] addObserver:win selector:@selector(deminiaturizeNotification:) name:@"NSWindowDidDeminiaturizeNotification" object:win]; 48 [[NSNotificationCenter defaultCenter] addObserver:win selector:@selector(closeNotification:) name:@"NSWindowWillCloseNotification" object:win]; 49 ***/ 50 51 } 52 53 func delNotifs() 54 { 55 NotificationCenter.default.removeObserver(self, name: NSWindow.willCloseNotification, object: nil) 56 } 57 58 public func setKeyRepeat(_ mode:Int) 59 { 60 keyrepeat = mode; 61 } 62 63 64 func addHook(index idx:Int, fct fptr:UnsafeMutableRawPointer?, param pptr:UnsafeMutableRawPointer) 65 { 66 eventFuncts[idx] = fptr; 67 eventParams[idx] = pptr; 68 if (idx == 6 || idx == 32) 69 { 70 if (fptr != nil) /// == nullptr) 71 { self.acceptsMouseMovedEvents = true } 72 else { self.acceptsMouseMovedEvents = false } 73 } 74 } 75 76 77 override func keyDown(with event: NSEvent) 78 { 79 /// print("got keydown with code: \(event.keyCode) ") 80 if (event.isARepeat && keyrepeat == 0) 81 { return } 82 if (eventFuncts[2] != nil) 83 { 84 _ = unsafeBitCast(eventFuncts[2],to:(@convention(c)(Int32, UnsafeRawPointer)->Int32).self)(Int32(event.keyCode), eventParams[2]) 85 } 86 } 87 88 89 override func keyUp(with event: NSEvent) 90 { 91 /// print("got keyup with code: \(event.keyCode) and calling key hook") 92 if (event.isARepeat && keyrepeat == 0) 93 { return } 94 if (eventFuncts[3] != nil) 95 { 96 _ = unsafeBitCast(eventFuncts[3],to:(@convention(c)(Int32, UnsafeRawPointer)->Int32).self)(Int32(event.keyCode), eventParams[3]) 97 } 98 } 99 100 101 func get_mouse_button(with ev:NSEvent) -> Int 102 { 103 switch (ev.type) { 104 case NSEvent.EventType.leftMouseDown, 105 NSEvent.EventType.leftMouseUp, 106 NSEvent.EventType.leftMouseDragged: 107 return 1; 108 case NSEvent.EventType.rightMouseDown, 109 NSEvent.EventType.rightMouseUp, 110 NSEvent.EventType.rightMouseDragged: 111 return 2; 112 case NSEvent.EventType.otherMouseDown, 113 NSEvent.EventType.otherMouseUp, 114 NSEvent.EventType.otherMouseDragged: 115 return 3; 116 default: 117 return 0; 118 } 119 } 120 121 122 123 func mouse(with event: NSEvent, index idx:Int, type t:Int) 124 { 125 var thepoint:NSPoint 126 var button:Int 127 128 thepoint = event.locationInWindow 129 button = get_mouse_button(with:event) 130 /// button = event.buttonNumber 131 /// print(" mouse down button \(event.buttonNumber) at location \(thepoint.x) x \(thepoint.y)") 132 if (eventFuncts[idx] != nil) 133 { 134 if (t == 0) 135 { _ = unsafeBitCast(eventFuncts[idx],to:(@convention(c)(Int32, Int32, Int32, UnsafeRawPointer)->Int32).self)(Int32(button), Int32(thepoint.x), Int32(CGFloat(size_y)-1.0-thepoint.y), eventParams[idx]) } 136 if (t == 1) 137 { _ = unsafeBitCast(eventFuncts[idx],to:(@convention(c)(Int32, Int32, UnsafeRawPointer)->Int32).self)(Int32(thepoint.x), Int32(CGFloat(size_y)-1.0-thepoint.y), eventParams[idx]) } 138 } 139 } 140 141 override func mouseDown(with event: NSEvent) { mouse(with:event, index:4, type:0) } 142 override func rightMouseDown(with event: NSEvent) { mouse(with:event, index:4, type:0) } 143 override func otherMouseDown(with event: NSEvent) { mouse(with:event, index:4, type:0) } 144 145 override func mouseUp(with event: NSEvent) { mouse(with:event, index:5, type:0) } 146 override func rightMouseUp(with event: NSEvent) { mouse(with:event, index:5, type:0) } 147 override func otherMouseUp(with event: NSEvent) { mouse(with:event, index:5, type:0) } 148 149 override func mouseMoved(with event: NSEvent) { mouse(with:event, index:6, type:1) } 150 override func mouseDragged(with event: NSEvent) { mouse(with:event, index:6, type:1) } 151 override func rightMouseDragged(with event: NSEvent) { mouse(with:event, index:6, type:1) } 152 override func otherMouseDragged(with event: NSEvent) { mouse(with:event, index:6, type:1) } 153 154 155 override func scrollWheel(with event: NSEvent) 156 { 157 var thepoint:NSPoint 158 var button = 0; 159 160 thepoint = event.locationInWindow 161 if (event.deltaY > 0.2) { button = 4; } 162 if (event.deltaY < -0.2) { button = 5; } 163 if (event.deltaX > 0.2) { button = 6; } 164 if (event.deltaX < -0.2) { button = 7; } 165 if (button != 0 && eventFuncts[4] != nil) 166 { 167 _ = unsafeBitCast(eventFuncts[4],to:(@convention(c)(Int32, Int32, Int32, UnsafeRawPointer)->Int32).self)(Int32(button), Int32(thepoint.x), Int32(thepoint.y), eventParams[4]) 168 } 169 } 170 171 172 override func flagsChanged(with event: NSEvent) 173 { 174 var flag:UInt32 175 var the_key:Int32 176 var val:UInt32 177 178 flag = UInt32(event.modifierFlags.rawValue) 179 val = (keyflag|flag)&(~(keyflag&flag)) 180 if (val == 0) 181 { return } /// no change - can happen when loosing focus on special key pressed, then re-pressed later 182 the_key = 1 183 while (((val >> (the_key-1)) & 0x01)==0) 184 { the_key += 1 } 185 if (flag > keyflag && eventFuncts[2] != nil) 186 { _ = unsafeBitCast(eventFuncts[2],to:(@convention(c)(Int32, UnsafeRawPointer)->Int32).self)(0xFF+the_key, eventParams[2]) } 187 if (flag < keyflag && eventFuncts[3] != nil) 188 { _ = unsafeBitCast(eventFuncts[3],to:(@convention(c)(Int32, UnsafeRawPointer)->Int32).self)(0xFF+the_key, eventParams[3]) } 189 keyflag = flag 190 } 191 192 193 @objc func exposeNotification(_ notification:Notification) 194 { 195 if (eventFuncts[12] != nil) 196 { 197 _ = unsafeBitCast(eventFuncts[12],to:(@convention(c)(UnsafeRawPointer)->Int32).self)(eventParams[12]) 198 } 199 } 200 201 @objc func closeNotification(_ notification:Notification) 202 { 203 if (eventFuncts[17] != nil) 204 { 205 _ = unsafeBitCast(eventFuncts[17],to:(@convention(c)(UnsafeRawPointer)->Int32).self)(eventParams[17]) 206 } 207 } 208 209 @objc func deminiaturizeNotification(_ notification:Notification) 210 { 211 exposeNotification(notification) 212 } 213 214 } 215 216 217 218 219 220 struct textureList 221 { 222 var uniformBuffer: MTLBuffer! 223 var uniform_data:UnsafeMutablePointer<Float> 224 unowned var image:MlxImg 225 } 226 227 228 public class MlxWin 229 { 230 let vrect: CGRect 231 var winE: WinEvent 232 var mlayer: CAMetalLayer 233 234 unowned var device: MTLDevice 235 var commandQueue: MTLCommandQueue! 236 var pipelineState: MTLRenderPipelineState! 237 var vertexBuffer: MTLBuffer! 238 239 var texture_list: Array<textureList> = Array() 240 var texture_list_count = 0 241 242 var pixel_image:MlxImg 243 var pixel_count:Int 244 245 var drawable_image: MlxImg 246 var uniq_renderPassDescriptor: MTLRenderPassDescriptor 247 var mtl_origin_null : MTLOrigin 248 var mtl_size_all : MTLSize 249 var doClear = false 250 var GPUbatch = 0 251 252 253 public init(device d:MTLDevice, width w:Int, height h:Int, title t:String) 254 { 255 vrect = CGRect(x: 100, y: 100, width: w, height: h) 256 winE = WinEvent(frame: vrect) 257 258 device = d 259 mlayer = CAMetalLayer() 260 mlayer.device = device 261 mlayer.pixelFormat = .bgra8Unorm 262 mlayer.framebufferOnly = true 263 mlayer.contentsScale = 1.0 /// winE.screen!.backingScaleFactor 264 mlayer.frame = vrect 265 winE.contentView! = NSView(frame: vrect) 266 winE.contentView!.wantsLayer = true 267 winE.contentView!.layer = mlayer 268 winE.title = t 269 winE.isReleasedWhenClosed = false 270 winE.makeKeyAndOrderFront(nil) 271 272 273 /// drawable_image = MlxImg(d: device, w:Int(CGFloat(vrect.size.width)*winE.screen!.backingScaleFactor), h:Int(CGFloat(vrect.size.height)*winE.screen!.backingScaleFactor), t:1) 274 drawable_image = MlxImg(d: device, w:Int(vrect.size.width), h:Int(vrect.size.height), t:1) 275 pixel_image = MlxImg(d: device, w:Int(vrect.size.width), h:Int(vrect.size.height)) 276 for i in 0...(pixel_image.texture_height*pixel_image.texture_sizeline/4-1) 277 { pixel_image.texture_data[i] = UInt32(0xFF000000) } 278 pixel_count = 0 279 280 mtl_origin_null = MTLOriginMake(0,0,0) 281 mtl_size_all = MTLSizeMake(drawable_image.texture.width, drawable_image.texture.height, 1) 282 283 uniq_renderPassDescriptor = MTLRenderPassDescriptor() 284 uniq_renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColor(red: 0.0, green: 0.0, blue: 0.0, alpha:0.0) 285 uniq_renderPassDescriptor.colorAttachments[0].texture = drawable_image.texture 286 uniq_renderPassDescriptor.colorAttachments[0].storeAction = .store 287 uniq_renderPassDescriptor.colorAttachments[0].loadAction = .load 288 } 289 290 291 /// winEvent calls 292 public func convertToDisplay(_ pt:NSPoint) -> NSPoint { 293 var pt2 = NSPoint(x:pt.x, y:pt.y) 294 pt2.y = CGFloat(winE.size_y)-1.0-pt2.y 295 pt2 = winE.convertPoint(toScreen: pt2) 296 pt2.y = (winE.screen!.frame.size.height)-pt2.y 297 return pt2 298 } 299 public func getWinEFrame() -> NSRect { return winE.frame } 300 public func getScreenFrame() -> NSRect { return winE.screen!.frame } 301 public func getMouseLoc() -> NSPoint { 302 var pt = NSPoint() 303 pt = winE.mouseLocationOutsideOfEventStream 304 pt.y = CGFloat(winE.size_y)-1.0-pt.y 305 return pt 306 } 307 public func addHook(index idx:Int, fct fptr:UnsafeMutableRawPointer, param pptr:UnsafeMutableRawPointer) 308 { winE.addHook(index: idx, fct: fptr, param: pptr) } 309 public func setKeyRepeat(_ mode:Int) { winE.setKeyRepeat(mode) } 310 public func destroyWinE() { winE.close() } 311 public func setNotifs() { winE.setNotifs() } 312 public func delNotifs() { winE.delNotifs() } 313 314 315 public func initMetal() 316 { 317 commandQueue = device.makeCommandQueue()! 318 319 /// vertex buffer & shaders stay the always the same. 320 let lib = try! device.makeLibrary(source: shaders, options: nil) 321 let vertexFunction = lib.makeFunction(name: "basic_vertex_function") 322 let fragmentFunction = lib.makeFunction(name: "basic_fragment_function") 323 let pipelineDesc = MTLRenderPipelineDescriptor() 324 pipelineDesc.colorAttachments[0].pixelFormat = .bgra8Unorm 325 pipelineDesc.colorAttachments[0].isBlendingEnabled = true 326 pipelineDesc.colorAttachments[0].rgbBlendOperation = .add 327 pipelineDesc.colorAttachments[0].alphaBlendOperation = .add 328 pipelineDesc.colorAttachments[0].sourceRGBBlendFactor = .oneMinusSourceAlpha 329 pipelineDesc.colorAttachments[0].sourceAlphaBlendFactor = .oneMinusSourceAlpha 330 pipelineDesc.colorAttachments[0].destinationRGBBlendFactor = .sourceAlpha 331 pipelineDesc.colorAttachments[0].destinationAlphaBlendFactor = .sourceAlpha 332 pipelineDesc.vertexFunction = vertexFunction 333 pipelineDesc.fragmentFunction = fragmentFunction 334 pipelineState = try! device.makeRenderPipelineState(descriptor: pipelineDesc) 335 336 let vertexData: [Float] = [ 337 -1.0, -1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 338 -1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 339 1.0, -1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 340 1.0, -1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 341 -1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 342 1.0, 1.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0 ] 343 var dataSize = vertexData.count * MemoryLayout.size(ofValue: vertexData[0]) 344 vertexBuffer = device.makeBuffer(bytes: vertexData, length: dataSize, options: []) 345 346 let uniformData: [Float] = [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Float(vrect.size.width), Float(vrect.size.height), 0.0, 0.0, 0.0, 0.0, 347 1.0, 1.0, 1.0, 1.0 ] 348 dataSize = uniformData.count * MemoryLayout.size(ofValue: uniformData[0]) 349 for _ in 0...255 350 { 351 let uniformBuffer = device.makeBuffer(bytes: uniformData, length: dataSize, options: [])! 352 let uniform_data = (uniformBuffer.contents()).assumingMemoryBound(to:Float.self) 353 texture_list.append(textureList(uniformBuffer:uniformBuffer, uniform_data:uniform_data, image:pixel_image)) 354 } 355 356 self.clearWin(); 357 } 358 359 360 public func clearWin() 361 { 362 /// discard previous put_images, doClear become first operation in next render pass. 363 var i = 0 364 while i < texture_list_count 365 { 366 texture_list[i].image.onGPU -= 1 367 i += 1 368 } 369 texture_list_count = 0 370 doClear = true 371 /// next flush images should call draw(), even if there is no image to put 372 } 373 374 func flushPixels() 375 { 376 if (pixel_count > 0) 377 { 378 pixel_count = 0 379 self.putImage(image:pixel_image, x:0, y:0) 380 } 381 } 382 383 public func flushImages() 384 { 385 flushPixels() 386 if (texture_list_count > 0 || doClear) 387 { 388 self.draw() 389 } 390 } 391 392 public func waitForGPU() 393 { 394 while (GPUbatch > 0) { } 395 } 396 397 398 public func pixelPut(_ x:Int32, _ y:Int32, _ color:UInt32) 399 { 400 if (pixel_count == 0) 401 { 402 while (pixel_image.onGPU > 0) 403 { 404 if (GPUbatch > 0) { waitForGPU() } 405 else { flushImages() } 406 } 407 for i in 0...pixel_image.texture_height*pixel_image.texture_sizeline/4-1 408 { pixel_image.texture_data[i] = UInt32(0xFF000000) } 409 } 410 let t = (x&(Int32(vrect.size.width-1)-x))&(y&(Int32(vrect.size.height-1)-y)) 411 if t >= 0 412 { 413 pixel_image.texture_data[Int(y)*pixel_image.texture_sizeline/4+Int(x)] = color 414 pixel_count += 1 415 } 416 } 417 418 public func putImage(image img:MlxImg, x posx:Int32, y posy:Int32) 419 { 420 flushPixels() 421 putImageScale(image:img, sx:0, sy:0, sw:Int32(img.texture_width), sh:Int32(img.texture_height), 422 dx:posx, dy:posy, dw:Int32(img.texture_width), dh:Int32(img.texture_height), 423 c:UInt32(0xFFFFFFFF)) 424 } 425 426 public func putImageScale(image img:MlxImg, sx src_x:Int32, sy src_y:Int32, sw src_w:Int32, sh src_h:Int32, dx dest_x:Int32, dy dest_y:Int32, dw dest_w:Int32, dh dest_h:Int32, c color:UInt32) 427 { 428 flushPixels() 429 if (texture_list_count == 0) /// means I just draw 430 { 431 waitForGPU() /// to be able to write again in uniforms 432 } 433 texture_list[texture_list_count].uniform_data[0] = Float(img.texture_width) 434 texture_list[texture_list_count].uniform_data[1] = Float(img.texture_height) 435 texture_list[texture_list_count].uniform_data[2] = Float(src_x) 436 texture_list[texture_list_count].uniform_data[3] = Float(src_y) 437 texture_list[texture_list_count].uniform_data[4] = Float(src_w) 438 texture_list[texture_list_count].uniform_data[5] = Float(src_h) 439 440 texture_list[texture_list_count].uniform_data[8] = Float(dest_x) 441 texture_list[texture_list_count].uniform_data[9] = Float(dest_y) 442 texture_list[texture_list_count].uniform_data[10] = Float(dest_w) 443 texture_list[texture_list_count].uniform_data[11] = Float(dest_h) 444 445 texture_list[texture_list_count].uniform_data[12] = Float((color>>16)&0xFF)/255.0; 446 texture_list[texture_list_count].uniform_data[13] = Float((color>>8)&0xFF)/255.0; 447 texture_list[texture_list_count].uniform_data[14] = Float((color>>0)&0xFF)/255.0; 448 texture_list[texture_list_count].uniform_data[15] = Float((color>>24)&0xFF)/255.0; 449 450 texture_list[texture_list_count].image = img 451 img.onGPU += 1 452 453 texture_list_count += 1 454 if (texture_list_count == 255) /// keep 1 slot for put_pixels image 455 { 456 flushImages() 457 } 458 } 459 460 461 func draw() 462 { 463 var commandBuffer = commandQueue.makeCommandBuffer()! 464 465 /// clear if asked 466 if (doClear) 467 { 468 uniq_renderPassDescriptor.colorAttachments[0].loadAction = .clear 469 let commandEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: uniq_renderPassDescriptor)! 470 commandEncoder.endEncoding() 471 uniq_renderPassDescriptor.colorAttachments[0].loadAction = .load 472 doClear = false 473 } 474 475 /// then draw the images if any. 476 var i = 0 477 while i < texture_list_count 478 { 479 let commandEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: uniq_renderPassDescriptor)! 480 commandEncoder.setRenderPipelineState(pipelineState) 481 commandEncoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0) 482 commandEncoder.setVertexBuffer(texture_list[i].uniformBuffer, offset: 0, index: 1) 483 commandEncoder.setFragmentTexture(texture_list[i].image.texture, index: 0) 484 commandEncoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 6, instanceCount:2) 485 commandEncoder.endEncoding() 486 ({ j in 487 commandBuffer.addCompletedHandler { cb in self.texture_list[j].image.onGPU -= 1 } 488 })(i) 489 i += 1 490 } 491 texture_list_count = 0 492 commandBuffer.addCompletedHandler { cb in self.GPUbatch -= 1 } 493 commandBuffer.commit() 494 GPUbatch += 1 495 496 /// finally copy to MTLdrawable to present, using a new commandqueue 497 commandBuffer = commandQueue.makeCommandBuffer()! 498 let curdraw = mlayer.nextDrawable()! 499 500 let commandBEncoder = commandBuffer.makeBlitCommandEncoder()! 501 commandBEncoder.copy(from:drawable_image.texture, sourceSlice:0, sourceLevel:0, sourceOrigin: mtl_origin_null, sourceSize: mtl_size_all, to:curdraw.texture, destinationSlice:0, destinationLevel:0, destinationOrigin: mtl_origin_null) 502 commandBEncoder.endEncoding() 503 504 commandBuffer.addCompletedHandler { cb in self.GPUbatch -= 1 } 505 commandBuffer.present(curdraw) 506 commandBuffer.commit() 507 GPUbatch += 1 508 } 509 510 511 } 512 513 514 515 516 let shaders = """ 517 #include <metal_stdlib> 518 using namespace metal; 519 520 struct VertexIn { 521 float4 position; 522 float4 UV; 523 }; 524 struct VertexOut { 525 float4 position [[ position ]]; 526 float4 color; 527 float2 UV; 528 }; 529 struct uniforms { 530 packed_float2 origin_size; 531 packed_float2 origin_pos; 532 packed_float2 origin_sub; 533 packed_float2 dest_size; 534 packed_float2 dest_pos; 535 packed_float2 dest_sub; 536 packed_float4 color; 537 }; 538 vertex VertexOut basic_vertex_function(const device VertexIn *vertices [[ buffer(0) ]], constant uniforms& uni [[ buffer(1) ]], 539 uint vertexID [[ vertex_id ]]) 540 { 541 VertexOut vOut; 542 float4 start = float4((2.0*uni.dest_pos.x)/(uni.dest_size.x-1.0) - 1.0, 1.0 - (2.0*uni.dest_pos.y)/(uni.dest_size.y-1.0) - (uni.dest_sub.y*2.0)/uni.dest_size.y, 0.0, 0.0); 543 /* vOut.position = (start + (vertices[vertexID].position + 1.0) * float4(uni.dest_sub, 0.0, 0.0))/float4(uni.dest_size, 1.0, 1.0); */ 544 545 vOut.position = float4(start.x+((vertices[vertexID].position.x + 1.0)*uni.dest_sub.x)/(uni.dest_size.x), 546 start.y+((vertices[vertexID].position.y + 1.0)*uni.dest_sub.y)/(uni.dest_size.y), 0.0, 1.0); 547 548 vOut.UV = (uni.origin_pos + float2(vertices[vertexID].UV.x, vertices[vertexID].UV.y)*(uni.origin_sub-1.0))/(uni.origin_size-1.0); 549 vOut.color = uni.color; 550 return vOut; 551 } 552 fragment float4 basic_fragment_function(VertexOut vIn [[ stage_in ]], texture2d<float> texture [[ texture(0) ]]) 553 { 554 constexpr sampler textureSampler(address::clamp_to_edge); 555 return vIn.color*texture.sample(textureSampler, vIn.UV); 556 } 557 """ 558