1 | // Copyright 2019 The SwiftShader Authors. All Rights Reserved. |
2 | // |
3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
4 | // you may not use this file except in compliance with the License. |
5 | // You may obtain a copy of the License at |
6 | // |
7 | // http://www.apache.org/licenses/LICENSE-2.0 |
8 | // |
9 | // Unless required by applicable law or agreed to in writing, software |
10 | // distributed under the License is distributed on an "AS IS" BASIS, |
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | // See the License for the specific language governing permissions and |
13 | // limitations under the License. |
14 | |
15 | #include "SpirvShader.hpp" |
16 | |
17 | #include "SamplerCore.hpp" // TODO: Figure out what's needed. |
18 | #include "System/Math.hpp" |
19 | #include "Vulkan/VkDebug.hpp" |
20 | #include "Vulkan/VkDescriptorSetLayout.hpp" |
21 | #include "Vulkan/VkDevice.hpp" |
22 | #include "Vulkan/VkImageView.hpp" |
23 | #include "Vulkan/VkSampler.hpp" |
24 | #include "Device/Config.hpp" |
25 | |
26 | #include <spirv/unified1/spirv.hpp> |
27 | #include <spirv/unified1/GLSL.std.450.h> |
28 | |
29 | #include <climits> |
30 | #include <mutex> |
31 | |
32 | namespace sw { |
33 | |
34 | SpirvShader::ImageSampler *SpirvShader::getImageSampler(uint32_t inst, vk::SampledImageDescriptor const *imageDescriptor, const vk::Sampler *sampler) |
35 | { |
36 | ImageInstruction instruction(inst); |
37 | const auto samplerId = sampler ? sampler->id : 0; |
38 | ASSERT(imageDescriptor->imageViewId != 0 && (samplerId != 0 || instruction.samplerMethod == Fetch)); |
39 | |
40 | vk::Device::SamplingRoutineCache::Key key = {inst, imageDescriptor->imageViewId, samplerId}; |
41 | |
42 | ASSERT(imageDescriptor->device); |
43 | |
44 | if(auto routine = imageDescriptor->device->findInConstCache(key)) |
45 | { |
46 | return (ImageSampler*)(routine->getEntry()); |
47 | } |
48 | |
49 | std::unique_lock<std::mutex> lock(imageDescriptor->device->getSamplingRoutineCacheMutex()); |
50 | vk::Device::SamplingRoutineCache* cache = imageDescriptor->device->getSamplingRoutineCache(); |
51 | |
52 | auto routine = cache->query(key); |
53 | if(routine) |
54 | { |
55 | return (ImageSampler*)(routine->getEntry()); |
56 | } |
57 | |
58 | auto type = imageDescriptor->type; |
59 | |
60 | Sampler samplerState = {}; |
61 | samplerState.textureType = type; |
62 | samplerState.textureFormat = imageDescriptor->format; |
63 | |
64 | samplerState.addressingModeU = convertAddressingMode(0, sampler, type); |
65 | samplerState.addressingModeV = convertAddressingMode(1, sampler, type); |
66 | samplerState.addressingModeW = convertAddressingMode(2, sampler, type); |
67 | |
68 | samplerState.mipmapFilter = convertMipmapMode(sampler); |
69 | samplerState.swizzle = imageDescriptor->swizzle; |
70 | samplerState.gatherComponent = instruction.gatherComponent; |
71 | samplerState.highPrecisionFiltering = false; |
72 | samplerState.largeTexture = (imageDescriptor->extent.width > SHRT_MAX) || |
73 | (imageDescriptor->extent.height > SHRT_MAX) || |
74 | (imageDescriptor->extent.depth > SHRT_MAX); |
75 | |
76 | if(sampler) |
77 | { |
78 | samplerState.textureFilter = (instruction.samplerMethod == Gather) ? FILTER_GATHER : convertFilterMode(sampler); |
79 | samplerState.border = sampler->borderColor; |
80 | |
81 | samplerState.mipmapFilter = convertMipmapMode(sampler); |
82 | |
83 | samplerState.compareEnable = (sampler->compareEnable == VK_TRUE); |
84 | samplerState.compareOp = sampler->compareOp; |
85 | samplerState.unnormalizedCoordinates = (sampler->unnormalizedCoordinates == VK_TRUE); |
86 | |
87 | if(sampler->ycbcrConversion) |
88 | { |
89 | samplerState.ycbcrModel = sampler->ycbcrConversion->ycbcrModel; |
90 | samplerState.studioSwing = (sampler->ycbcrConversion->ycbcrRange == VK_SAMPLER_YCBCR_RANGE_ITU_NARROW); |
91 | samplerState.swappedChroma = (sampler->ycbcrConversion->components.r != VK_COMPONENT_SWIZZLE_R); |
92 | } |
93 | |
94 | if(sampler->anisotropyEnable != VK_FALSE) |
95 | { |
96 | UNSUPPORTED("anisotropyEnable" ); |
97 | } |
98 | } |
99 | |
100 | routine = emitSamplerRoutine(instruction, samplerState); |
101 | |
102 | cache->add(key, routine); |
103 | return (ImageSampler*)(routine->getEntry()); |
104 | } |
105 | |
106 | std::shared_ptr<rr::Routine> SpirvShader::emitSamplerRoutine(ImageInstruction instruction, const Sampler &samplerState) |
107 | { |
108 | // TODO(b/129523279): Hold a separate mutex lock for the sampler being built. |
109 | rr::Function<Void(Pointer<Byte>, Pointer<Byte>, Pointer<SIMD::Float>, Pointer<SIMD::Float>, Pointer<Byte>)> function; |
110 | { |
111 | Pointer<Byte> texture = function.Arg<0>(); |
112 | Pointer<Byte> sampler = function.Arg<1>(); |
113 | Pointer<SIMD::Float> in = function.Arg<2>(); |
114 | Pointer<SIMD::Float> out = function.Arg<3>(); |
115 | Pointer<Byte> constants = function.Arg<4>(); |
116 | |
117 | SIMD::Float uvw[4] = {0, 0, 0, 0}; |
118 | SIMD::Float q = 0; |
119 | SIMD::Float lodOrBias = 0; // Explicit level-of-detail, or bias added to the implicit level-of-detail (depending on samplerMethod). |
120 | Vector4f dsx = {0, 0, 0, 0}; |
121 | Vector4f dsy = {0, 0, 0, 0}; |
122 | Vector4f offset = {0, 0, 0, 0}; |
123 | SamplerFunction samplerFunction = instruction.getSamplerFunction(); |
124 | |
125 | uint32_t i = 0; |
126 | for( ; i < instruction.coordinates; i++) |
127 | { |
128 | uvw[i] = in[i]; |
129 | } |
130 | |
131 | if (instruction.isDref()) |
132 | { |
133 | q = in[i]; |
134 | i++; |
135 | } |
136 | |
137 | // TODO(b/134669567): Currently 1D textures are treated as 2D by setting the second coordinate to 0. |
138 | // Implement optimized 1D sampling. |
139 | if(samplerState.textureType == VK_IMAGE_VIEW_TYPE_1D) |
140 | { |
141 | uvw[1] = SIMD::Float(0); |
142 | } |
143 | else if(samplerState.textureType == VK_IMAGE_VIEW_TYPE_1D_ARRAY) |
144 | { |
145 | uvw[1] = SIMD::Float(0); |
146 | uvw[2] = in[1]; // Move 1D layer coordinate to 2D layer coordinate index. |
147 | } |
148 | |
149 | if(instruction.samplerMethod == Lod || instruction.samplerMethod == Bias || instruction.samplerMethod == Fetch) |
150 | { |
151 | lodOrBias = in[i]; |
152 | i++; |
153 | } |
154 | else if(instruction.samplerMethod == Grad) |
155 | { |
156 | for(uint32_t j = 0; j < instruction.grad; j++, i++) |
157 | { |
158 | dsx[j] = in[i]; |
159 | } |
160 | |
161 | for(uint32_t j = 0; j < instruction.grad; j++, i++) |
162 | { |
163 | dsy[j] = in[i]; |
164 | } |
165 | } |
166 | |
167 | for(uint32_t j = 0; j < instruction.offset; j++, i++) |
168 | { |
169 | offset[j] = in[i]; |
170 | } |
171 | |
172 | // TODO(b/133868964): Handle 'Sample' operand. |
173 | |
174 | SamplerCore s(constants, samplerState); |
175 | |
176 | // For explicit-lod instructions the LOD can be different per SIMD lane. SamplerCore currently assumes |
177 | // a single LOD per four elements, so we sample the image again for each LOD separately. |
178 | if(samplerFunction.method == Lod || samplerFunction.method == Grad) // TODO(b/133868964): Also handle divergent Bias and Fetch with Lod. |
179 | { |
180 | auto lod = Pointer<Float>(&lodOrBias); |
181 | |
182 | For(Int i = 0, i < SIMD::Width, i++) |
183 | { |
184 | SIMD::Float dPdx; |
185 | SIMD::Float dPdy; |
186 | |
187 | dPdx.x = Pointer<Float>(&dsx.x)[i]; |
188 | dPdx.y = Pointer<Float>(&dsx.y)[i]; |
189 | dPdx.z = Pointer<Float>(&dsx.z)[i]; |
190 | |
191 | dPdy.x = Pointer<Float>(&dsy.x)[i]; |
192 | dPdy.y = Pointer<Float>(&dsy.y)[i]; |
193 | dPdy.z = Pointer<Float>(&dsy.z)[i]; |
194 | |
195 | // 1D textures are treated as 2D texture with second coordinate 0, so we also need to zero out the second grad component. TODO(b/134669567) |
196 | if(samplerState.textureType == VK_IMAGE_VIEW_TYPE_1D || samplerState.textureType == VK_IMAGE_VIEW_TYPE_1D_ARRAY) |
197 | { |
198 | dPdx.y = Float(0.0f); |
199 | dPdy.y = Float(0.0f); |
200 | } |
201 | |
202 | Vector4f sample = s.sampleTexture(texture, sampler, uvw[0], uvw[1], uvw[2], q, lod[i], dPdx, dPdy, offset, samplerFunction); |
203 | |
204 | Pointer<Float> rgba = out; |
205 | rgba[0 * SIMD::Width + i] = Pointer<Float>(&sample.x)[i]; |
206 | rgba[1 * SIMD::Width + i] = Pointer<Float>(&sample.y)[i]; |
207 | rgba[2 * SIMD::Width + i] = Pointer<Float>(&sample.z)[i]; |
208 | rgba[3 * SIMD::Width + i] = Pointer<Float>(&sample.w)[i]; |
209 | } |
210 | } |
211 | else |
212 | { |
213 | Vector4f sample = s.sampleTexture(texture, sampler, uvw[0], uvw[1], uvw[2], q, lodOrBias.x, (dsx.x), (dsy.x), offset, samplerFunction); |
214 | |
215 | Pointer<SIMD::Float> rgba = out; |
216 | rgba[0] = sample.x; |
217 | rgba[1] = sample.y; |
218 | rgba[2] = sample.z; |
219 | rgba[3] = sample.w; |
220 | } |
221 | } |
222 | |
223 | return function("sampler" ); |
224 | } |
225 | |
226 | sw::FilterType SpirvShader::convertFilterMode(const vk::Sampler *sampler) |
227 | { |
228 | switch(sampler->magFilter) |
229 | { |
230 | case VK_FILTER_NEAREST: |
231 | switch(sampler->minFilter) |
232 | { |
233 | case VK_FILTER_NEAREST: return FILTER_POINT; |
234 | case VK_FILTER_LINEAR: return FILTER_MIN_LINEAR_MAG_POINT; |
235 | default: |
236 | UNIMPLEMENTED("minFilter %d" , sampler->minFilter); |
237 | return FILTER_POINT; |
238 | } |
239 | break; |
240 | case VK_FILTER_LINEAR: |
241 | switch(sampler->minFilter) |
242 | { |
243 | case VK_FILTER_NEAREST: return FILTER_MIN_POINT_MAG_LINEAR; |
244 | case VK_FILTER_LINEAR: return FILTER_LINEAR; |
245 | default: |
246 | UNIMPLEMENTED("minFilter %d" , sampler->minFilter); |
247 | return FILTER_POINT; |
248 | } |
249 | break; |
250 | default: |
251 | break; |
252 | } |
253 | |
254 | UNIMPLEMENTED("magFilter %d" , sampler->magFilter); |
255 | return FILTER_POINT; |
256 | } |
257 | |
258 | sw::MipmapType SpirvShader::convertMipmapMode(const vk::Sampler *sampler) |
259 | { |
260 | if(!sampler) |
261 | { |
262 | return MIPMAP_POINT; // Samplerless operations (OpImageFetch) can take an integer Lod operand. |
263 | } |
264 | |
265 | if(sampler->ycbcrConversion) |
266 | { |
267 | return MIPMAP_NONE; // YCbCr images can only have one mipmap level. |
268 | } |
269 | |
270 | switch(sampler->mipmapMode) |
271 | { |
272 | case VK_SAMPLER_MIPMAP_MODE_NEAREST: return MIPMAP_POINT; |
273 | case VK_SAMPLER_MIPMAP_MODE_LINEAR: return MIPMAP_LINEAR; |
274 | default: |
275 | UNIMPLEMENTED("mipmapMode %d" , sampler->mipmapMode); |
276 | return MIPMAP_POINT; |
277 | } |
278 | } |
279 | |
280 | sw::AddressingMode SpirvShader::convertAddressingMode(int coordinateIndex, const vk::Sampler *sampler, VkImageViewType imageViewType) |
281 | { |
282 | switch(imageViewType) |
283 | { |
284 | case VK_IMAGE_VIEW_TYPE_CUBE_ARRAY: |
285 | UNSUPPORTED("SPIR-V ImageCubeArray Capability (imageViewType: %d)" , int(imageViewType)); |
286 | if(coordinateIndex == 3) |
287 | { |
288 | return ADDRESSING_LAYER; |
289 | } |
290 | // Fall through to CUBE case: |
291 | case VK_IMAGE_VIEW_TYPE_CUBE: |
292 | if(coordinateIndex <= 1) // Cube faces themselves are addressed as 2D images. |
293 | { |
294 | // Vulkan 1.1 spec: |
295 | // "Cube images ignore the wrap modes specified in the sampler. Instead, if VK_FILTER_NEAREST is used within a mip level then |
296 | // VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE is used, and if VK_FILTER_LINEAR is used within a mip level then sampling at the edges |
297 | // is performed as described earlier in the Cube map edge handling section." |
298 | // This corresponds with our 'SEAMLESS' addressing mode. |
299 | return ADDRESSING_SEAMLESS; |
300 | } |
301 | else if(coordinateIndex == 2) |
302 | { |
303 | // The cube face is an index into array layers. |
304 | return ADDRESSING_CUBEFACE; |
305 | } |
306 | else |
307 | { |
308 | return ADDRESSING_UNUSED; |
309 | } |
310 | break; |
311 | |
312 | case VK_IMAGE_VIEW_TYPE_1D: // Treated as 2D texture with second coordinate 0. TODO(b/134669567) |
313 | if(coordinateIndex == 1) |
314 | { |
315 | return ADDRESSING_WRAP; |
316 | } |
317 | else if(coordinateIndex >= 2) |
318 | { |
319 | return ADDRESSING_UNUSED; |
320 | } |
321 | break; |
322 | |
323 | case VK_IMAGE_VIEW_TYPE_3D: |
324 | if(coordinateIndex >= 3) |
325 | { |
326 | return ADDRESSING_UNUSED; |
327 | } |
328 | break; |
329 | |
330 | case VK_IMAGE_VIEW_TYPE_1D_ARRAY: // Treated as 2D texture with second coordinate 0. TODO(b/134669567) |
331 | if(coordinateIndex == 1) |
332 | { |
333 | return ADDRESSING_WRAP; |
334 | } |
335 | // Fall through to 2D_ARRAY case: |
336 | case VK_IMAGE_VIEW_TYPE_2D_ARRAY: |
337 | if(coordinateIndex == 2) |
338 | { |
339 | return ADDRESSING_LAYER; |
340 | } |
341 | else if(coordinateIndex >= 3) |
342 | { |
343 | return ADDRESSING_UNUSED; |
344 | } |
345 | // Fall through to 2D case: |
346 | case VK_IMAGE_VIEW_TYPE_2D: |
347 | if(coordinateIndex >= 2) |
348 | { |
349 | return ADDRESSING_UNUSED; |
350 | } |
351 | break; |
352 | |
353 | default: |
354 | UNIMPLEMENTED("imageViewType %d" , imageViewType); |
355 | return ADDRESSING_WRAP; |
356 | } |
357 | |
358 | if(!sampler) |
359 | { |
360 | // OpImageFetch does not take a sampler descriptor, but still needs a valid, |
361 | // arbitrary addressing mode that prevents out-of-bounds accesses: |
362 | // "The value returned by a read of an invalid texel is undefined, unless that |
363 | // read operation is from a buffer resource and the robustBufferAccess feature |
364 | // is enabled. In that case, an invalid texel is replaced as described by the |
365 | // robustBufferAccess feature." - Vulkan 1.1 |
366 | |
367 | return ADDRESSING_WRAP; |
368 | } |
369 | |
370 | VkSamplerAddressMode addressMode = VK_SAMPLER_ADDRESS_MODE_REPEAT; |
371 | switch(coordinateIndex) |
372 | { |
373 | case 0: addressMode = sampler->addressModeU; break; |
374 | case 1: addressMode = sampler->addressModeV; break; |
375 | case 2: addressMode = sampler->addressModeW; break; |
376 | default: UNSUPPORTED("coordinateIndex: %d" , coordinateIndex); |
377 | } |
378 | |
379 | switch(addressMode) |
380 | { |
381 | case VK_SAMPLER_ADDRESS_MODE_REPEAT: return ADDRESSING_WRAP; |
382 | case VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT: return ADDRESSING_MIRROR; |
383 | case VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE: return ADDRESSING_CLAMP; |
384 | case VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER: return ADDRESSING_BORDER; |
385 | case VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE: return ADDRESSING_MIRRORONCE; |
386 | default: |
387 | UNIMPLEMENTED("addressMode %d" , addressMode); |
388 | return ADDRESSING_WRAP; |
389 | } |
390 | } |
391 | |
392 | } // namespace sw |
393 | |