yum-mirror/slang

Making it easier to work with shaders

git clone https://git.yummers.dev/yum-mirror/slang

Gangzheng TongConvert gfx unit tests and examples to use slang-rhi (#7577)43d0c2100

master
18.5 KiB571 linesraw
1// model.cpp
2#include "model.h"
3
4#include "window.h"
5
6#define TINYOBJLOADER_IMPLEMENTATION
7#include "tinyobjloader/tiny_obj_loader.h"
8
9#define STB_IMAGE_IMPLEMENTATION
10#include "stb_image.h"
11
12#define STB_IMAGE_RESIZE_IMPLEMENTATION
13#include "glm/glm/glm.hpp"
14#include "glm/glm/gtc/constants.hpp"
15#include "glm/glm/gtc/matrix_transform.hpp"
16#include "stb_image_resize.h"
17
18#include <memory>
19#include <unordered_map>
20#include <unordered_set>
21
22namespace platform
23{
24
25using namespace rhi;
26using namespace Slang;
27
28// TinyObj provides a tuple type that bundles up indices, but doesn't
29// provide equality comparison or hashing for that type. We'd like
30// to have a hash function so that we can unique indices.
31//
32// In the simplest case, we could define hashing and operator== operations
33// directly on `tinobj::index_t`, but that would create problems if they
34// revise their API.
35//
36// We will instead define our own wrapper type that supports equality
37// comparisons.
38//
39struct ObjIndexKey
40{
41    tinyobj::index_t index;
42};
43
44bool operator==(ObjIndexKey const& left, ObjIndexKey const& right)
45{
46    return left.index.vertex_index == right.index.vertex_index &&
47           left.index.normal_index == right.index.normal_index &&
48           left.index.texcoord_index == right.index.texcoord_index;
49}
50
51struct Hasher
52{
53    template<typename T>
54    void add(T const& v)
55    {
56        state ^= std::hash<T>()(v) + 0x9e3779b9 + (state << 6) + (state >> 2);
57    }
58    size_t state = 0;
59};
60
61struct SmoothingGroupVertexID
62{
63    size_t smoothingGroup;
64    size_t positionID;
65};
66bool operator==(SmoothingGroupVertexID const& left, SmoothingGroupVertexID const& right)
67{
68    return left.smoothingGroup == right.smoothingGroup && left.positionID == right.positionID;
69}
70
71} // namespace platform
72
73namespace std
74{
75template<>
76struct hash<platform::ObjIndexKey>
77{
78    size_t operator()(platform::ObjIndexKey const& key) const
79    {
80        platform::Hasher hasher;
81        hasher.add(key.index.vertex_index);
82        hasher.add(key.index.normal_index);
83        hasher.add(key.index.texcoord_index);
84        return hasher.state;
85    }
86};
87
88template<>
89struct hash<platform::SmoothingGroupVertexID>
90{
91    size_t operator()(platform::SmoothingGroupVertexID const& id) const
92    {
93        platform::Hasher hasher;
94        hasher.add(id.smoothingGroup);
95        hasher.add(id.positionID);
96        return hasher.state;
97    }
98};
99} // namespace std
100
101namespace platform
102{
103
104ComPtr<ITexture> loadTextureImage(IDevice* device, char const* path)
105{
106    int extentX = 0;
107    int extentY = 0;
108    int originalChannelCount = 0;
109    int requestedChannelCount = 4; // force to 4-component result
110    stbi_uc* data =
111        stbi_load(path, &extentX, &extentY, &originalChannelCount, requestedChannelCount);
112    if (!data)
113        return nullptr;
114
115    int channelCount = requestedChannelCount ? requestedChannelCount : originalChannelCount;
116
117    Format format;
118    switch (channelCount)
119    {
120    default:
121        return nullptr;
122
123    case 4:
124        format = Format::RGBA8Unorm;
125
126        // TODO: handle other cases here if/when we stop forcing 4-component
127        // results when loading the image with stb_image.
128    }
129
130    std::vector<SubresourceData> subresourceInitData;
131
132    ptrdiff_t stride = extentX * channelCount * sizeof(stbi_uc);
133
134    SubresourceData baseInitData;
135    baseInitData.data = data;
136    baseInitData.rowPitch = stride;
137    baseInitData.slicePitch = 0;
138
139    subresourceInitData.push_back(baseInitData);
140
141    // create down-sampled images for the different mip levels
142    bool generateMips = true;
143    if (generateMips)
144    {
145        int prevExtentX = extentX;
146        int prevExtentY = extentY;
147        stbi_uc* prevData = data;
148        int prevStride = int(stride);
149
150        for (;;)
151        {
152            if (prevExtentX == 1 && prevExtentY == 1)
153                break;
154
155            int newExtentX = prevExtentX / 2;
156            int newExtentY = prevExtentY / 2;
157
158            if (!newExtentX)
159                newExtentX = 1;
160            if (!newExtentY)
161                newExtentY = 1;
162
163            stbi_uc* newData =
164                (stbi_uc*)malloc(newExtentX * newExtentY * channelCount * sizeof(stbi_uc));
165            int newStride = int(newExtentX * channelCount * sizeof(stbi_uc));
166
167            stbir_resize_uint8_srgb(
168                prevData,
169                prevExtentX,
170                prevExtentY,
171                prevStride,
172                newData,
173                newExtentX,
174                newExtentY,
175                newStride,
176                channelCount,
177                STBIR_ALPHA_CHANNEL_NONE,
178                STBIR_FLAG_ALPHA_PREMULTIPLIED);
179
180
181            SubresourceData mipInitData;
182            mipInitData.data = newData;
183            mipInitData.rowPitch = newStride;
184            mipInitData.slicePitch = 0;
185
186            subresourceInitData.push_back(mipInitData);
187
188            prevExtentX = newExtentX;
189            prevExtentY = newExtentY;
190            prevData = newData;
191            prevStride = newStride;
192        }
193    }
194
195    int mipCount = (int)subresourceInitData.size();
196
197    TextureDesc desc = {};
198    desc.type = TextureType::Texture2D;
199    desc.usage = TextureUsage::ShaderResource;
200    desc.format = format;
201    desc.size.width = extentX;
202    desc.size.height = extentY;
203    desc.size.depth = 1;
204    desc.mipCount = mipCount;
205    auto texture = device->createTexture(desc, subresourceInitData.data());
206    free(data);
207
208    return texture;
209}
210
211static std::string makeString(const char* start, const char* end)
212{
213    return std::string(start, size_t(end - start));
214}
215
216SlangResult ModelLoader::load(char const* inputPath, void** outModel)
217{
218    // TODO: need to actually allocate/load the data
219
220    tinyobj::attrib_t objVertexAttributes;
221    std::vector<tinyobj::shape_t> objShapes;
222    std::vector<tinyobj::material_t> objMaterials;
223
224    std::string baseDir;
225    if (auto lastSlash = strrchr(inputPath, '/'))
226    {
227        baseDir = makeString(inputPath, lastSlash);
228    }
229
230    std::string diagnostics;
231    bool shouldTriangulate = true;
232    bool success = tinyobj::LoadObj(
233        &objVertexAttributes,
234        &objShapes,
235        &objMaterials,
236        &diagnostics,
237        inputPath,
238        baseDir.size() ? baseDir.c_str() : nullptr,
239        shouldTriangulate);
240
241    if (!diagnostics.empty())
242    {
243        printf("%s", diagnostics.c_str());
244    }
245    if (!success)
246    {
247        return SLANG_FAIL;
248    }
249
250    // Translate each material imported by TinyObj into a format that
251    // we can actually use for rendering.
252    //
253    std::vector<void*> materials;
254    for (auto& objMaterial : objMaterials)
255    {
256        MaterialData materialData;
257
258        materialData.diffuseColor =
259            glm::vec3(objMaterial.diffuse[0], objMaterial.diffuse[1], objMaterial.diffuse[2]);
260
261        materialData.specularColor =
262            glm::vec3(objMaterial.specular[0], objMaterial.specular[1], objMaterial.specular[2]);
263
264        materialData.specularity = objMaterial.shininess;
265
266        // load any referenced textures here
267        if (objMaterial.diffuse_texname.length())
268        {
269            materialData.diffuseMap = loadTextureImage(device, objMaterial.diffuse_texname.c_str());
270        }
271
272        auto material = callbacks->createMaterial(materialData);
273        materials.push_back(material);
274    }
275
276    // Flip the winding order on all faces if we are asked to...
277    //
278    if (loadFlags & LoadFlag::FlipWinding)
279    {
280        for (auto& objShape : objShapes)
281        {
282            size_t objIndexCounter = 0;
283            size_t objFaceCounter = 0;
284            for (auto objFaceVertexCount : objShape.mesh.num_face_vertices)
285            {
286                size_t beginIndex = objIndexCounter;
287                size_t endIndex = beginIndex + objFaceVertexCount;
288                objIndexCounter = endIndex;
289
290                size_t halfCount = objFaceVertexCount / 2;
291                for (size_t ii = 0; ii < halfCount; ++ii)
292                {
293                    std::swap(
294                        objShape.mesh.indices[beginIndex + ii],
295                        objShape.mesh.indices[endIndex - (ii + 1)]);
296                }
297            }
298        }
299    }
300
301    // Identify cases where a face has a vertex without a normal, and in that
302    // case remember that the given vertex needs to be "smoothed" as part of
303    // the smoothing group for that face. Note that it is possible for the
304    // same vertex (position) to be part of faces in distinct smoothing groups.
305    //
306    std::unordered_map<SmoothingGroupVertexID, size_t> smoothedVertexNormals;
307    size_t firstSmoothedNormalID = objVertexAttributes.normals.size() / 3;
308    size_t flatFaceCounter = 0;
309    for (auto& objShape : objShapes)
310    {
311        size_t objIndexCounter = 0;
312        size_t objFaceCounter = 0;
313        for (auto objFaceVertexCount : objShape.mesh.num_face_vertices)
314        {
315            const size_t flatFaceIndex = flatFaceCounter++;
316            const size_t objFaceIndex = objFaceCounter++;
317            size_t smoothingGroup = objShape.mesh.smoothing_group_ids[objFaceIndex];
318            if (!smoothingGroup)
319            {
320                smoothingGroup = ~flatFaceIndex;
321            }
322
323            for (size_t objFaceVertex = 0; objFaceVertex < objFaceVertexCount; ++objFaceVertex)
324            {
325                tinyobj::index_t& objIndex = objShape.mesh.indices[objIndexCounter++];
326
327                if (objIndex.normal_index < 0)
328                {
329                    SmoothingGroupVertexID smoothVertexID;
330                    smoothVertexID.positionID = objIndex.vertex_index;
331                    smoothVertexID.smoothingGroup = smoothingGroup;
332
333                    if (smoothedVertexNormals.find(smoothVertexID) == smoothedVertexNormals.end())
334                    {
335                        size_t normalID = objVertexAttributes.normals.size() / 3;
336                        objVertexAttributes.normals.push_back(0);
337                        objVertexAttributes.normals.push_back(0);
338                        objVertexAttributes.normals.push_back(0);
339
340                        smoothedVertexNormals.insert(std::make_pair(smoothVertexID, normalID));
341
342                        objIndex.normal_index = int(normalID);
343                    }
344                }
345            }
346        }
347    }
348    //
349    // Having identified which vertices we need to smooth, we will make another
350    // pass to compute face normals and apply them to the vertices that belong
351    // to the same smoothing group.
352    //
353    flatFaceCounter = 0;
354    for (auto& objShape : objShapes)
355    {
356        size_t objIndexCounter = 0;
357        size_t objFaceCounter = 0;
358        for (auto objFaceVertexCount : objShape.mesh.num_face_vertices)
359        {
360            const size_t flatFaceIndex = flatFaceCounter++;
361            const size_t objFaceIndex = objFaceCounter++;
362            size_t smoothingGroup = objShape.mesh.smoothing_group_ids[objFaceIndex];
363            if (!smoothingGroup)
364            {
365                smoothingGroup = ~flatFaceIndex;
366            }
367
368            glm::vec3 faceNormal;
369            if (objFaceVertexCount >= 3)
370            {
371                glm::vec3 v[3];
372                for (size_t objFaceVertex = 0; objFaceVertex < 3; ++objFaceVertex)
373                {
374                    tinyobj::index_t objIndex =
375                        objShape.mesh.indices[objIndexCounter + objFaceVertex];
376                    if (objIndex.vertex_index >= 0)
377                    {
378                        v[objFaceVertex] = glm::vec3(
379                            objVertexAttributes.vertices[3 * objIndex.vertex_index + 0],
380                            objVertexAttributes.vertices[3 * objIndex.vertex_index + 1],
381                            objVertexAttributes.vertices[3 * objIndex.vertex_index + 2]);
382                    }
383                }
384                faceNormal = cross(v[1] - v[0], v[2] - v[0]);
385            }
386
387            // Add this face normal to any to-be-smoothed vertex on the face.
388            for (size_t objFaceVertex = 0; objFaceVertex < objFaceVertexCount; ++objFaceVertex)
389            {
390                tinyobj::index_t objIndex = objShape.mesh.indices[objIndexCounter++];
391
392                SmoothingGroupVertexID smoothVertexID;
393                smoothVertexID.positionID = objIndex.vertex_index;
394                smoothVertexID.smoothingGroup = smoothingGroup;
395
396                auto ii = smoothedVertexNormals.find(smoothVertexID);
397                if (ii != smoothedVertexNormals.end())
398                {
399                    size_t normalID = ii->second;
400                    objVertexAttributes.normals[normalID * 3 + 0] += faceNormal.x;
401                    objVertexAttributes.normals[normalID * 3 + 1] += faceNormal.y;
402                    objVertexAttributes.normals[normalID * 3 + 2] += faceNormal.z;
403                }
404            }
405        }
406    }
407    //
408    // Once we've added all contributions from each smoothing group,
409    // we can normalize the normals to compute the area-weighted average.
410    //
411    size_t normalCount = objVertexAttributes.normals.size() / 3;
412    for (size_t ii = firstSmoothedNormalID; ii < normalCount; ++ii)
413    {
414        glm::vec3 normal = glm::vec3(
415            objVertexAttributes.normals[3 * ii + 0],
416            objVertexAttributes.normals[3 * ii + 1],
417            objVertexAttributes.normals[3 * ii + 2]);
418
419        normal = normalize(normal);
420
421        objVertexAttributes.normals[3 * ii + 0] = normal.x;
422        objVertexAttributes.normals[3 * ii + 1] = normal.y;
423        objVertexAttributes.normals[3 * ii + 2] = normal.z;
424    }
425
426    // TODO: we should sort the faces to group faces with
427    // the same material ID together, in case they weren't
428    // grouped in the original file.
429
430    // We need to undo the .obj indexing stuff so that we have
431    // standard position/normal/etc. data in a single flat array
432
433    std::unordered_map<ObjIndexKey, Index> mapObjIndexToFlatIndex;
434    std::vector<Vertex> flatVertices;
435    std::vector<Index> flatIndices;
436
437    MeshData* currentMesh = nullptr;
438    MeshData currentMeshStorage;
439
440    std::vector<void*> meshes;
441
442    void* defaultMaterial = nullptr;
443
444    for (auto& objShape : objShapes)
445    {
446        size_t objIndexCounter = 0;
447        size_t objFaceCounter = 0;
448        for (auto objFaceVertexCount : objShape.mesh.num_face_vertices)
449        {
450            size_t objFaceIndex = objFaceCounter++;
451            int faceMaterialID = objShape.mesh.material_ids[objFaceIndex];
452            void* faceMaterial = nullptr;
453            if (faceMaterialID < 0)
454            {
455                if (!defaultMaterial)
456                {
457                    MaterialData defaultMaterialData;
458                    defaultMaterialData.diffuseColor = glm::vec3(0.5, 0.5, 0.5);
459                    defaultMaterial = callbacks->createMaterial(defaultMaterialData);
460                }
461                faceMaterial = defaultMaterial;
462            }
463            else
464            {
465                faceMaterial = materials[faceMaterialID];
466            }
467
468            if (!currentMesh || (faceMaterial != currentMesh->material))
469            {
470                // finish old mesh.
471                if (currentMesh)
472                {
473                    meshes.push_back(callbacks->createMesh(*currentMesh));
474                }
475
476                // Need to start a new mesh.
477                currentMesh = &currentMeshStorage;
478                currentMesh->material = faceMaterial;
479                currentMesh->firstIndex = (int)flatIndices.size();
480                currentMesh->indexCount = 0;
481            }
482
483            for (size_t objFaceVertex = 0; objFaceVertex < objFaceVertexCount; ++objFaceVertex)
484            {
485                tinyobj::index_t objIndex = objShape.mesh.indices[objIndexCounter++];
486                ObjIndexKey objIndexKey;
487                objIndexKey.index = objIndex;
488
489
490                Index flatIndex = Index(-1);
491                auto iter = mapObjIndexToFlatIndex.find(objIndexKey);
492                if (iter != mapObjIndexToFlatIndex.end())
493                {
494                    flatIndex = iter->second;
495                }
496                else
497                {
498                    Vertex flatVertex;
499                    if (objIndex.vertex_index >= 0)
500                    {
501                        flatVertex.position =
502                            scale *
503                            glm::vec3(
504                                objVertexAttributes.vertices[3 * objIndex.vertex_index + 0],
505                                objVertexAttributes.vertices[3 * objIndex.vertex_index + 1],
506                                objVertexAttributes.vertices[3 * objIndex.vertex_index + 2]);
507                    }
508                    if (objIndex.normal_index >= 0)
509                    {
510                        flatVertex.normal = glm::vec3(
511                            objVertexAttributes.normals[3 * objIndex.normal_index + 0],
512                            objVertexAttributes.normals[3 * objIndex.normal_index + 1],
513                            objVertexAttributes.normals[3 * objIndex.normal_index + 2]);
514                    }
515                    if (objIndex.texcoord_index >= 0)
516                    {
517                        flatVertex.uv = glm::vec2(
518                            objVertexAttributes.texcoords[2 * objIndex.texcoord_index + 0],
519                            objVertexAttributes.texcoords[2 * objIndex.texcoord_index + 1]);
520                    }
521
522                    flatIndex = uint32_t(flatVertices.size());
523                    mapObjIndexToFlatIndex.insert(std::make_pair(objIndexKey, flatIndex));
524                    flatVertices.push_back(flatVertex);
525                }
526
527                flatIndices.push_back(flatIndex);
528                currentMesh->indexCount++;
529            }
530        }
531    }
532
533    // finish last mesh.
534    if (currentMesh)
535    {
536        meshes.push_back(callbacks->createMesh(*currentMesh));
537    }
538
539    ModelData modelData;
540
541    modelData.vertexCount = (int)flatVertices.size();
542    modelData.indexCount = (int)flatIndices.size();
543    modelData.primitiveTopology = PrimitiveTopology::TriangleList;
544
545    modelData.meshCount = int(meshes.size());
546    modelData.meshes = meshes.data();
547
548    BufferDesc vertexBufferDesc;
549    vertexBufferDesc.size = modelData.vertexCount * sizeof(Vertex);
550    vertexBufferDesc.usage = BufferUsage::VertexBuffer;
551    vertexBufferDesc.elementSize = sizeof(Vertex);
552
553    modelData.vertexBuffer = device->createBuffer(vertexBufferDesc, flatVertices.data());
554    if (!modelData.vertexBuffer)
555        return SLANG_FAIL;
556
557    BufferDesc indexBufferDesc;
558    indexBufferDesc.size = modelData.indexCount * sizeof(Index);
559    indexBufferDesc.usage = BufferUsage::IndexBuffer;
560    indexBufferDesc.elementSize = sizeof(Index);
561
562    modelData.indexBuffer = device->createBuffer(indexBufferDesc, flatIndices.data());
563    if (!modelData.indexBuffer)
564        return SLANG_FAIL;
565
566    *outModel = callbacks->createModel(modelData);
567
568    return SLANG_OK;
569}
570
571} // namespace platform