2017-12-10 3 views
9

Ich habe ein Bild, das ich programmgesteuert generieren und ich möchte dieses Bild als Textur an einen Compute Shader senden. Die Art, wie ich dieses Bild erzeuge, besteht darin, dass ich jede RGBA-Komponente als UInt8 Werte berechne und sie zu einer UInt32 kombiniere und sie im Puffer des Bildes abspeichere. Ich tue dies mit dem folgenden Code:Übergeben Texturen mit UInt8 Komponententyp zu Metal Compute Shader

guard let cgContext = CGContext(data: nil, 
           width: width, 
           height: height, 
           bitsPerComponent: 8, 
           bytesPerRow: 0, 
           space: CGColorSpaceCreateDeviceRGB(), 
           bitmapInfo: RGBA32.bitmapInfo) else { 
            print("Unable to create CGContext") 
            return 
} 

guard let buffer = cgContext.data else { 
    print("Unable to create textures") 
    return 
} 
let pixelBuffer = buffer.bindMemory(to: RGBA32.self, capacity: width * height) 
let heightFloat = Float(height) 
let widthFloat = Float(width) 
for i in 0 ..< height { 
    let latitude = Float(i + 1)/heightFloat 
    for j in 0 ..< width { 
    let longitude = Float(j + 1)/widthFloat 
    let x = UInt8(((sin(longitude * Float.pi * 2) * cos(latitude * Float.pi) + 1)/2) * 255) 
    let y = UInt8(((sin(longitude * Float.pi * 2) * sin(latitude * Float.pi) + 1)/2) * 255) 
    let z = UInt8(((cos(latitude * Float.pi) + 1)/2) * 255) 
    let offset = width * i + j 
    pixelBuffer[offset] = RGBA32(red: x, green: y, blue: z, alpha: 255) 
    } 
} 

let coordinateConversionImage = cgContext.makeImage() 

wo RGBA32 ein wenig Struktur ist, die die Verschiebung tut und die Schaffung der UInt32 Wert. Dieses Bild ist gut, da ich es in UIImage konvertieren und in meiner Fotogalerie speichern kann.

Das Problem tritt auf, wenn ich versuche, dieses Bild als Textur an einen Compute Shader zu senden. Unten ist mein Shader-Code:

kernel void updateEnvironmentMap(texture2d<uint, access::read> currentFrameTexture [[texture(0)]], 
           texture2d<uint, access::read> coordinateConversionTexture [[texture(1)]], 
           texture2d<uint, access::write> environmentMap [[texture(2)]] 
           uint2 gid [[thread_position_in_grid]]) 
{ 
    const uint4 pixel = {255, 127, 63, 255}; 
    environmentMap.write(pixel, gid); 
} 

Das Problem mit diesem Code ist, dass die Art meiner Texturen uint ist, die 32-Bit ist, und ich möchte 32-Bit-Pixel die gleiche Weise, die ich auf das tun generieren CPU, durch Anhängen von 4 8-Bit-Werten. Allerdings kann ich das anscheinend nicht auf Metal machen, da es keinen byte Typ gibt, den ich einfach zusammenfügen kann und einen uint32 zusammensetze. Also, meine Frage ist, was ist der richtige Weg, um 2D-Texturen zu behandeln und 32-Bit-Pixel auf einem Metal Compute Shader zu setzen?

Bonus Frage: Ich habe auch Beispiel Shader-Codes mit texture2d<float, access::read> als Eingabe Textur-Typ gesehen. Ich gehe davon aus, dass es einen Wert zwischen 0.0 und 1.0 darstellt, aber welchen Vorteil hat das gegenüber einem vorzeichenlosen int mit Werten zwischen 0 und 255?

Bearbeiten: Um zu klären, hat die Ausgabestruktur des Shaders, environmentMap, genau die gleichen Eigenschaften (Breite, Höhe, PixelFormat usw.) wie die Eingabetexturen. Warum ich denke, dass dies gegenläufig ist, ist, dass wir ein uint4 als ein Pixel setzen, was bedeutet, dass es aus 4 32-Bit-Werten besteht, während jedes Pixel 32-Bit sein sollte. Mit diesem aktuellen Code hat {255, 127, 63, 255} das exakt gleiche Ergebnis wie {2550, 127, 63, 255}, was bedeutet, dass die Werte irgendwie zwischen 0-255 geklemmt werden, bevor sie in die Ausgangstextur geschrieben werden. Aber das ist extrem kontraintuitiv.

Antwort

8

Es ist ein bisschen mehr Magie im Spiel, als Sie zu kennen scheinen, also werde ich versuchen, aufzuklären.

Zunächst einmal gibt es eine lockere Verbindung zwischen dem Speicherformat von Texturen in Metal und dem Typ, den Sie beim Lesen/Sampling erhalten. Sie können eine Textur im .bgra8Unorm Format haben, die, wenn Sie durch eine Textur, die als texture2d<float, access::sample> gebunden ist, eine float4 mit ihren Komponenten in RGBA-Reihenfolge erhalten. Die Konvertierung von diesen gepackten Bytes in den Float-Vektor mit swizzled Komponenten folgt gut dokumentierten Konvertierungsregeln, wie in der Metal Shading Language Specification spezifiziert.

Es ist auch der Fall, dass beim Schreiben in eine Textur, deren Speicher (zum Beispiel) 8 Bits pro Komponente enthält, die Werte geklammert werden, um in das zugrunde liegende Speicherformat zu passen. Dies wird zusätzlich dadurch beeinflusst, ob die Textur ein norm Typ ist oder nicht: Wenn das Format norm enthält, werden die Werte so interpretiert, als ob sie einen Wert zwischen 0 und 1 hätten. Ansonsten sind die von Ihnen gelesenen Werte nicht normalisiert.

Ein Beispiel: Wenn eine Textur .bgra8Unorm und ein gegebenes Pixel enthält das Byte [0, 64, 128, 255] Werte, dann, wenn in einem Shader lesen, die float Komponenten anfordert, werden Sie [0.5, 0.25, 0, 1.0] erhalten, wenn Sie es probieren. Wenn das Format dagegen .rgba8Uint lautet, erhalten Sie [0, 64, 128, 255].Das Speicherformat der Textur hat eine vorherrschende Wirkung darauf, wie ihr Inhalt bei der Abtastung interpretiert wird.

Ich nehme an, dass das Pixelformat Ihrer Textur etwas wie .rgba8Unorm ist. Wenn das der Fall ist, können Sie erreichen, was Sie wollen Ihren Kernel wie folgt zu schreiben:

kernel void updateEnvironmentMap(texture2d<float, access::read> currentFrameTexture [[texture(0)]], 
           texture2d<float, access::read> coordinateConversionTexture [[texture(1)]], 
           texture2d<float, access::write> environmentMap [[texture(2)]] 
           uint2 gid [[thread_position_in_grid]]) 
{ 
    const float4 pixel(255, 127, 63, 255); 
    environmentMap.write(pixel * (1/255.0), gid); 
} 

Wenn dagegen die Textur ein Format von .rgba8Uint hat, können Sie den gleichen Effekt erhalten werden, indem es wie folgt schreiben:

kernel void updateEnvironmentMap(texture2d<float, access::read> currentFrameTexture [[texture(0)]], 
           texture2d<float, access::read> coordinateConversionTexture [[texture(1)]], 
           texture2d<float, access::write> environmentMap [[texture(2)]] 
           uint2 gid [[thread_position_in_grid]]) 
{ 
    const float4 pixel(255, 127, 63, 255); 
    environmentMap.write(pixel, gid); 
} 

ich verstehe, dass dies ein Spielzeug Beispiel ist, aber ich hoffe, dass mit den oben genannten Informationen können Sie herausfinden, wie richtig zu speichern und Probenwert zu erreichen, was Sie wollen.

+0

Dank Warren, das hat eine Menge Dinge geklärt. Wenn jemand weitere Informationen dazu wünscht, ist das Kapitel 7.7 Texturadressierungs- und Konvertierungsregeln der Metal Shading Language Specification der richtige Ort dafür. – halileohalilei

Verwandte Themen