Skip to content

Commit 49f16e8

Browse files
authored
Refactor and abstract Image interfaces (#18)
This rewrites the image handling to operate on an Image type and allow more flexible translation of images.
1 parent cf1570a commit 49f16e8

File tree

8 files changed

+292
-135
lines changed

8 files changed

+292
-135
lines changed

CMakeLists.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ macro(add_hlsl_tool name)
3737
endmacro()
3838

3939
macro(add_hlsl_library name)
40-
add_llvm_library(HLSLTest${name} ${ARGN})
40+
add_llvm_library(HLSLTest${name} BUILDTREE_ONLY ${ARGN})
4141
set_target_properties(HLSLTest${name} PROPERTIES FOLDER "HLSL Test/Libraries")
4242
endmacro()
4343

@@ -75,6 +75,7 @@ if (WIN32)
7575
# These are some extra fun hacks becauze ZLIB's CMake is fragile
7676
if (NOT TARGET ZLIB::ZLIB)
7777
add_library(ZLIB::ZLIB ALIAS zlibstatic)
78+
set_property(GLOBAL APPEND PROPERTY LLVM_EXPORTS_BUILDTREE_ONLY zlibstatic)
7879
endif ()
7980
# libpng does some crazy stuff with zlib's include directories.
8081
set(ZLIB_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/third-party/zlib
@@ -86,6 +87,7 @@ if (WIN32)
8687
set(SKIP_INSTALL_EXPORT On)
8788
endif ()
8889
add_subdirectory(third-party/libpng)
90+
set_property(GLOBAL APPEND PROPERTY LLVM_EXPORTS_BUILDTREE_ONLY png_static)
8991

9092
include(Warp)
9193

include/Image/Image.h

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
//===- Image.h - Image Description ------------------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
//
10+
//===----------------------------------------------------------------------===//
11+
12+
#ifndef HLSLTEST_IMAGE_IMAGE_H
13+
#define HLSLTEST_IMAGE_IMAGE_H
14+
15+
#include "Support/Pipeline.h"
16+
17+
#include "llvm/ADT/StringRef.h"
18+
#include "llvm/ADT/bit.h"
19+
#include "llvm/Support/Error.h"
20+
21+
#include <cassert>
22+
#include <cstdint>
23+
24+
namespace hlsltest {
25+
26+
class ImageRef {
27+
uint32_t Height;
28+
uint32_t Width;
29+
uint8_t Depth;
30+
uint8_t Channels;
31+
bool IsFloat;
32+
33+
protected:
34+
llvm::StringRef Data;
35+
ImageRef(uint32_t H, uint32_t W, uint8_t D, uint8_t C, bool F)
36+
: Height(H), Width(W), Depth(D), Channels(C), IsFloat(F), Data() {}
37+
38+
public:
39+
ImageRef() = default;
40+
ImageRef(const ImageRef &I) = default;
41+
ImageRef(ImageRef &&II) = default;
42+
ImageRef &operator=(const ImageRef &) = default;
43+
ImageRef &operator=(ImageRef &&) = default;
44+
45+
ImageRef(uint32_t H, uint32_t W, uint8_t D, uint8_t C, bool F,
46+
llvm::StringRef S)
47+
: Height(H), Width(W), Depth(D), Channels(C), IsFloat(F), Data(S) {
48+
assert((Channels == 3 || Channels == 4) && "Channels must be 3 or 4");
49+
assert(llvm::popcount(Depth) == 1 && "Depth must be a power of 2");
50+
assert(Depth <= 8 && "Depth must be <= 8 (64-bit)");
51+
assert(Data.size() == size() && "Data size does not match properties");
52+
}
53+
54+
ImageRef(const Resource &R)
55+
: ImageRef(R.OutputProps.Height, R.OutputProps.Width,
56+
R.getSingleElementSize(), R.Channels,
57+
R.Format == DataFormat::Float32 ||
58+
R.Format == DataFormat::Float64,
59+
llvm::StringRef(R.Data.get(), R.Size)) {}
60+
61+
uint32_t getHeight() const { return Height; }
62+
uint32_t getWidth() const { return Width; }
63+
uint8_t getDepth() const { return Depth; }
64+
uint32_t getBitDepth() const { return Depth * 8; }
65+
uint8_t getChannels() const { return Channels; }
66+
bool isFloat() const { return IsFloat; }
67+
68+
size_t size() const { return Data.size(); }
69+
const char *data() const { return Data.data(); }
70+
bool empty() const { return Data.empty(); }
71+
};
72+
73+
class Image : public ImageRef {
74+
std::unique_ptr<char[]> OwnedData;
75+
76+
Image(uint32_t H, uint32_t W, uint8_t D, uint8_t C, bool F)
77+
: ImageRef(H, W, D, C, F) {
78+
uint64_t Sz = static_cast<uint64_t>(H) * static_cast<uint64_t>(W) *
79+
static_cast<uint64_t>(D) * static_cast<uint64_t>(C);
80+
OwnedData = std::make_unique<char[]>(Sz);
81+
Data = llvm::StringRef(OwnedData.get(), Sz);
82+
}
83+
84+
public:
85+
// Not default constructable.
86+
Image() = delete;
87+
// Not copyable.
88+
Image(const Image &I) = delete;
89+
Image &operator=(const Image &I) = delete;
90+
// Movable.
91+
Image(Image &&I) = default;
92+
Image &operator=(Image &&I) = default;
93+
94+
ImageRef getRef() const { return ImageRef(*this); }
95+
96+
static llvm::Error WritePNG(ImageRef I, llvm::StringRef Path);
97+
98+
static Image translateImage(ImageRef I, uint8_t Depth, uint8_t Channels,
99+
bool Float);
100+
101+
char *data() { return OwnedData.get(); }
102+
};
103+
104+
} // namespace hlsltest
105+
106+
#endif // HLSLTEST_IMAGE_IMAGE_H

lib/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
add_subdirectory(API)
2+
add_subdirectory(Image)
23
add_subdirectory(Support)

lib/Image/CMakeLists.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
add_hlsl_library(Image Image.cpp)
2+
3+
target_include_directories(HLSLTestImage PRIVATE SYSTEM BEFORE
4+
"${HLSLTEST_BINARY_DIR}/third-party/libpng/"
5+
"${HLSLTEST_SOURCE_DIR}/third-party/libpng/")
6+
target_link_libraries(HLSLTestImage INTERFACE png_static)
7+
add_dependencies(HLSLTestImage png_genfiles)

lib/Image/Image.cpp

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
//===- Image.cpp - Image Description ----------------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
//
10+
//===----------------------------------------------------------------------===//
11+
12+
#include "Image/Image.h"
13+
14+
#include "llvm/ADT/ScopeExit.h"
15+
#include "llvm/ADT/StringRef.h"
16+
#include "llvm/Support/Error.h"
17+
#include "llvm/Support/SwapByteOrder.h"
18+
19+
#include <limits.h>
20+
#include <png.h>
21+
#include <stdio.h>
22+
23+
using namespace hlsltest;
24+
25+
template <typename DstType, typename SrcType>
26+
void TranslatePixelData(Image &Dst, ImageRef Src) {
27+
uint64_t Pixels = Dst.getHeight() * Dst.getWidth();
28+
uint32_t CopiedChannels = std::min(Dst.getChannels(), Src.getChannels());
29+
DstType *DstPtr = reinterpret_cast<DstType *>(Dst.data());
30+
const SrcType *SrcPtr = reinterpret_cast<const SrcType *>(Src.data());
31+
for (uint64_t I = 0; I < Pixels; ++I) {
32+
for (uint32_t J = 0; J < CopiedChannels; ++J, ++SrcPtr, ++DstPtr) {
33+
double Tmp = static_cast<double>(*SrcPtr);
34+
// If the source type is not a float, normalize it between 0 & 1.
35+
if constexpr (!std::is_floating_point<SrcType>())
36+
Tmp /= std::numeric_limits<SrcType>::max();
37+
// If the destination type is not a float, scale it into the integer type.
38+
if constexpr (!std::is_floating_point<DstType>())
39+
Tmp *= std::numeric_limits<DstType>::max();
40+
41+
*DstPtr = static_cast<DstType>(Tmp);
42+
if constexpr (sizeof(DstType) > 1 && !llvm::sys::IsBigEndianHost)
43+
llvm::sys::swapByteOrder(*DstPtr);
44+
}
45+
// If the destination has more channels fill it with saturated values.
46+
for (uint32_t J = 0; J < CopiedChannels - Dst.getChannels();
47+
++J, ++DstPtr) {
48+
if constexpr (std::is_floating_point<DstType>())
49+
*DstPtr = 1.0;
50+
else
51+
*DstPtr = std::numeric_limits<DstType>::max();
52+
}
53+
// If the source has more channels skip them.
54+
for (uint32_t J = 0; J < CopiedChannels - Src.getChannels(); ++J, ++SrcPtr)
55+
;
56+
}
57+
}
58+
59+
template <typename SrcType> void translatePixelSrc(Image &Dst, ImageRef Src) {
60+
61+
switch (Dst.getDepth()) {
62+
case 1:
63+
assert(!Dst.isFloat() && "No float8 support!");
64+
TranslatePixelData<uint8_t, SrcType>(Dst, Src);
65+
break;
66+
case 2:
67+
assert(!Dst.isFloat() && "No float16 support!");
68+
TranslatePixelData<uint16_t, SrcType>(Dst, Src);
69+
break;
70+
default:
71+
llvm_unreachable("Destination depth out of expected range.");
72+
}
73+
}
74+
75+
void translatePixels(Image &Dst, ImageRef Src) {
76+
switch (Src.getDepth()) {
77+
case 1:
78+
assert(!Src.isFloat() && "No float8 support!");
79+
translatePixelSrc<uint8_t>(Dst, Src);
80+
break;
81+
case 2:
82+
assert(!Src.isFloat() && "No float16 support!");
83+
translatePixelSrc<uint16_t>(Dst, Src);
84+
break;
85+
case 4:
86+
if (Src.isFloat())
87+
translatePixelSrc<float>(Dst, Src);
88+
else
89+
translatePixelSrc<uint32_t>(Dst, Src);
90+
break;
91+
case 8:
92+
if (Src.isFloat())
93+
translatePixelSrc<double>(Dst, Src);
94+
else
95+
translatePixelSrc<uint64_t>(Dst, Src);
96+
break;
97+
default:
98+
llvm_unreachable("Source depth out of expected range.");
99+
}
100+
}
101+
102+
Image Image::translateImage(ImageRef Src, uint8_t Depth, uint8_t Channels,
103+
bool Float) {
104+
Image NewImage =
105+
Image(Src.getHeight(), Src.getWidth(), Depth, Channels, Float);
106+
translatePixels(NewImage, Src);
107+
return NewImage;
108+
}
109+
110+
llvm::Error WritePNGImpl(ImageRef Img, llvm::StringRef OutputPath) {
111+
png_structp PNG =
112+
png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
113+
if (!PNG)
114+
return llvm::createStringError(std::errc::io_error,
115+
"Failed creating PNG data");
116+
117+
png_infop PNGInfo = png_create_info_struct(PNG);
118+
FILE *F = nullptr;
119+
auto ScopeExit = llvm::make_scope_exit([&PNG, &PNGInfo, &F]() {
120+
png_destroy_write_struct(&PNG, &PNGInfo);
121+
if (F)
122+
fclose(F);
123+
});
124+
if (!PNGInfo)
125+
return llvm::createStringError(std::errc::io_error,
126+
"Failed writing PNG info");
127+
128+
F = fopen(OutputPath.data(), "wb");
129+
if (!F)
130+
return llvm::createStringError(std::errc::io_error, "Failed openiong file");
131+
png_init_io(PNG, F);
132+
png_set_IHDR(PNG, PNGInfo, Img.getWidth(), Img.getHeight(), Img.getBitDepth(),
133+
PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE,
134+
PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
135+
png_colorp ColorP =
136+
(png_colorp)png_malloc(PNG, PNG_MAX_PALETTE_LENGTH * sizeof(png_color));
137+
if (!ColorP)
138+
return llvm::createStringError(std::errc::io_error,
139+
"Failed creating color palette");
140+
png_set_PLTE(PNG, PNGInfo, ColorP, PNG_MAX_PALETTE_LENGTH);
141+
png_write_info(PNG, PNGInfo);
142+
png_set_packing(PNG);
143+
144+
png_bytepp Rows =
145+
(png_bytepp)png_malloc(PNG, Img.getHeight() * sizeof(png_bytep));
146+
uint64_t RowSize = Img.getWidth() * Img.getChannels() * Img.getDepth();
147+
// Step one row back from the end
148+
const uint8_t *Row = reinterpret_cast<const uint8_t *>(Img.data()) +
149+
(RowSize * Img.getHeight()) - RowSize;
150+
for (uint32_t I = 0; I < Img.getHeight(); ++I, Row -= RowSize)
151+
Rows[I] = const_cast<png_bytep>(Row);
152+
153+
png_write_image(PNG, Rows);
154+
png_write_end(PNG, PNGInfo);
155+
png_free(PNG, ColorP);
156+
return llvm::Error::success();
157+
}
158+
159+
llvm::Error Image::WritePNG(ImageRef Img, llvm::StringRef Path) {
160+
uint32_t NewDepth = std::min(static_cast<uint32_t>(Img.getDepth()), 2u);
161+
if (Img.isFloat() || Img.getDepth() != NewDepth) {
162+
Image Translated = translateImage(Img, NewDepth, Img.getChannels(), false);
163+
return WritePNGImpl(Translated, Path);
164+
}
165+
return WritePNGImpl(Img, Path);
166+
}

tools/offloader/CMakeLists.txt

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
add_hlsl_tool(offloader
2-
offloader.cpp
3-
WritePNG.cpp)
2+
offloader.cpp)
43

5-
target_include_directories(offloader PRIVATE SYSTEM BEFORE "${HLSLTEST_BINARY_DIR}/third-party/libpng/")
6-
7-
target_link_libraries(offloader PRIVATE LLVMSupport HLSLTestAPI HLSLTestSupport png_static)
4+
target_link_libraries(offloader PRIVATE
5+
LLVMSupport
6+
HLSLTestAPI
7+
HLSLTestImage
8+
HLSLTestSupport)

0 commit comments

Comments
 (0)