adding draw3d support for execution via trace files: cgltrace

This commit is contained in:
Blaise Tine 2022-03-12 16:26:14 -05:00
parent 4eb0a0d3ad
commit 3c7412c068
19 changed files with 1560 additions and 438 deletions

View file

@ -18,6 +18,7 @@ THREADS=4
L2=0
L3=0
DEBUG=0
DEBUG_LEVEL=0
SCOPE=0
HAS_ARGS=0
@ -57,7 +58,8 @@ case $i in
shift
;;
--debug=*)
DEBUG=${i#*=}
DEBUG_LEVEL=${i#*=}
DEBUG=1
shift
;;
--scope)
@ -142,11 +144,11 @@ if [ $DEBUG -ne 0 ]
then
if [ $SCOPE -eq 1 ]
then
echo "running: DEBUG=$DEBUG SCOPE=1 CONFIGS="$CONFIGS" make -C $DRIVER_PATH"
DEBUG=$DEBUG SCOPE=1 CONFIGS="$CONFIGS" make -C $DRIVER_PATH
echo "running: DEBUG=$DEBUG_LEVEL SCOPE=1 CONFIGS="$CONFIGS" make -C $DRIVER_PATH"
DEBUG=$DEBUG_LEVEL SCOPE=1 CONFIGS="$CONFIGS" make -C $DRIVER_PATH
else
echo "running: DEBUG=$DEBUG CONFIGS="$CONFIGS" make -C $DRIVER_PATH"
DEBUG=$DEBUG CONFIGS="$CONFIGS" make -C $DRIVER_PATH
echo "running: DEBUG=$DEBUG_LEVEL CONFIGS="$CONFIGS" make -C $DRIVER_PATH"
DEBUG=$DEBUG_LEVEL CONFIGS="$CONFIGS" make -C $DRIVER_PATH
fi
if [ $HAS_ARGS -eq 1 ]

View file

@ -62,9 +62,18 @@ public:
, func2_(0)
, func3_(0)
, func6_(0)
, func7_(0) {
, func7_(0)
, vmask_(0)
, vlsWidth_(0)
, vMop_(0)
, vNf_(0)
, vs3_(0)
, vlmul_(0)
, vsew_(0)
, vediv_(0) {
for (uint32_t i = 0; i < MAX_REG_SOURCES; ++i) {
rsrc_type_[i] = RegType::None;
rsrc_[i] = 0;
}
}

View file

@ -52,10 +52,11 @@ public:
//--
union {
LsuType lsu_type;
AluType alu_type;
FpuType fpu_type;
GpuType gpu_type;
uint32_t unit_type;
LsuType lsu_type;
AluType alu_type;
FpuType fpu_type;
GpuType gpu_type;
};
ITraceData* data;
@ -75,6 +76,7 @@ public:
, rdest_type(RegType::None)
, wb(false)
, exe_type(ExeType::NOP)
, unit_type(0)
, data(nullptr)
, fetch_stall(false)
, stalled_(false)

View file

@ -21,11 +21,18 @@ public:
CSR()
: stamp_(nullptr)
, frag(0)
{}
~CSR() {
if (stamp_ )
this->clear();
}
void clear() {
if (stamp_ ) {
delete stamp_;
stamp_ = nullptr;
}
}
void set_stamp(RasterUnit::Stamp *stamp) {
@ -62,13 +69,16 @@ public:
~Impl() {}
void clear() {
//--
for (auto& csr : csrs_) {
csr.clear();
}
}
uint32_t csr_read(int32_t wid, uint32_t tid, uint32_t addr) {
uint32_t ltid = wid * arch_.num_threads() + tid;
auto& csr = csrs_.at(ltid);
if (0 == (csr.get_stamp()->mask & (1 << csr.frag)))
if (0 == (csr.get_stamp()->mask & (1 << csr.frag))
&& addr != CSR_RASTER_FRAG)
return 0;
auto i = csr.frag & 0x1;
auto j = csr.frag >> 1;
@ -105,7 +115,9 @@ public:
void csr_write(uint32_t wid, uint32_t tid, uint32_t addr, uint32_t value) {
uint32_t ltid = wid * arch_.num_threads() + tid;
auto& csr = csrs_.at(ltid);
bool valid_frag = (csr.get_stamp()->mask & (1 << csr.frag));
if (0 == (csr.get_stamp()->mask & (1 << csr.frag))
&& addr != CSR_RASTER_FRAG)
return;
auto i = csr.frag & 0x1;
auto j = csr.frag >> 1;
auto px = csr.get_stamp()->x + i;
@ -117,16 +129,12 @@ public:
csr.frag = value;
break;
case CSR_RASTER_GRAD_X:
if (valid_frag) {
csr.gradients[csr.frag].x = fixed24_t::make(value);
//printf("grad.x[%d,%d]=%d\n", px, py, csr.gradients[csr.frag].x.data());
}
csr.gradients[csr.frag].x = fixed24_t::make(value);
//printf("grad.x[%d,%d]=%d\n", px, py, csr.gradients[csr.frag].x.data());
break;
case CSR_RASTER_GRAD_Y:
if (valid_frag) {
csr.gradients[csr.frag].y = fixed24_t::make(value);
//printf("grad.y[%d,%d]=%d\n", px, py, csr.gradients[csr.frag].y.data());
}
csr.gradients[csr.frag].y = fixed24_t::make(value);
//printf("grad.y[%d,%d]=%d\n", px, py, csr.gradients[csr.frag].y.data());
break;
default:
std::abort();

View file

@ -57,9 +57,27 @@ private:
uint32_t num_prims_;
uint32_t cur_tile_;
uint32_t cur_prim_;
std::queue<RasterUnit::Stamp*> stamp_queue_;
MemoryPool<RasterUnit::Stamp> stamp_allocator_;
bool initialized_;
RasterUnit::Stamp* stamps_head_;
RasterUnit::Stamp *stamps_tail_;
bool initialized_;
void stamps_push(RasterUnit::Stamp* stamp) {
stamp->next_ = stamps_tail_;
stamp->prev_ = nullptr;
if (stamps_tail_)
stamps_tail_->prev_ = stamp;
else
stamps_head_ = stamp;
stamps_tail_ = stamp;
}
void stamps_pop() {
stamps_head_ = stamps_head_->prev_;
if (stamps_head_)
stamps_head_->next_ = nullptr;
else
stamps_tail_ = nullptr;
}
void renderQuad(const primitive_t& primitive,
uint32_t x,
@ -68,11 +86,8 @@ private:
fixed16_t e1,
fixed16_t e2) {
//printf("Quad (%d,%d) :\n", x, y);
RasterUnit::Stamp stamp;
stamp.x = x;
stamp.y = y;
stamp.mask = 0;
stamp.pid = cur_prim_;
uint32_t mask = 0;
std::array<vec3_fx_t, 4> bcoords;
for (uint32_t j = 0; j < 2; ++j) {
auto ee0 = e0;
@ -82,10 +97,10 @@ private:
// test if pixel overlaps triangle
if (ee0 >= fxZero && ee1 >= fxZero && ee2 >= fxZero) {
uint32_t f = j * 2 + i;
stamp.mask |= (1 << f);
stamp.bcoords[f].x = ee0;
stamp.bcoords[f].y = ee1;
stamp.bcoords[f].z = ee2;
mask |= (1 << f);
bcoords[f].x = ee0;
bcoords[f].y = ee1;
bcoords[f].z = ee2;
}
// update edge equation x components
ee0 += primitive.edges[0].x;
@ -97,10 +112,10 @@ private:
e1 += primitive.edges[1].y;
e2 += primitive.edges[2].y;
}
// submit stamp
if (stamp.mask) {
stamp_queue_.push(new RasterUnit::Stamp(stamp));
if (mask) {
// add stamp to queue
this->stamps_push(new RasterUnit::Stamp(x, y, mask, bcoords, cur_prim_));
}
}
@ -260,6 +275,8 @@ private:
assert(num_prims_ > 0);
}
//printf("renderNextPrimitive(tile=%d/%d, prim=%d/%d)\n", cur_tile_, num_tiles_, cur_prim_, num_prims_);
// get next primitive index from current tile
mem_->read(&cur_prim_, tbuf_addr_, 4);
tbuf_addr_ += 4;
@ -276,6 +293,10 @@ private:
pbuf_addr += 4;
}
//printf("edge0=(%d, %d, %d)\n", primitive.edges[0].x.data(), primitive.edges[0].y.data(), primitive.edges[0].z.data());
//printf("edge1=(%d, %d, %d)\n", primitive.edges[1].x.data(), primitive.edges[1].y.data(), primitive.edges[1].z.data());
//printf("edge2=(%d, %d, %d)\n", primitive.edges[2].x.data(), primitive.edges[2].y.data(), primitive.edges[2].z.data());
uint32_t tx = (tile_xy_ & 0xffff) << tile_logsize_;
uint32_t ty = (tile_xy_ >> 16) << tile_logsize_;
@ -314,7 +335,8 @@ public:
, dcrs_(dcrs)
, tile_logsize_(tile_logsize)
, block_logsize_(block_logsize)
, stamp_allocator_(STAMP_POOL_MAX_SIZE)
, stamps_head_(nullptr)
, stamps_tail_(nullptr)
, initialized_(false) {
assert(block_logsize >= 1);
assert(tile_logsize >= block_logsize);
@ -336,13 +358,17 @@ public:
if (!initialized_) {
this->initialize();
}
if (stamp_queue_.empty() && cur_tile_ == num_tiles_)
return nullptr;
if (stamp_queue_.empty()) {
this->renderNextPrimitive();
}
auto stamp = stamp_queue_.front();
stamp_queue_.pop();
do {
if (stamps_head_ == nullptr && cur_tile_ == num_tiles_)
return nullptr;
if (stamps_head_ == nullptr) {
this->renderNextPrimitive();
}
} while (stamps_head_ == nullptr);
auto stamp = stamps_head_;
this->stamps_pop();
return stamp;
}
};

View file

@ -17,9 +17,37 @@ public:
struct Stamp {
uint32_t x;
uint32_t y;
vec3_fx_t bcoords[4]; // barycentric coordinates
uint32_t mask;
std::array<vec3_fx_t, 4> bcoords; // barycentric coordinates
uint32_t pid;
Stamp* next_;
Stamp* prev_;
Stamp(uint32_t x, uint32_t y, uint32_t mask, const std::array<vec3_fx_t, 4>& bcoords, uint32_t pid)
: x(x)
, y(y)
, mask(mask)
, bcoords(bcoords)
, pid(pid)
, next_(nullptr)
, prev_(nullptr)
{}
void* operator new(size_t /*size*/) {
return allocator().allocate();
}
void operator delete(void* ptr) {
allocator().deallocate(ptr);
}
private:
static MemoryPool<Stamp>& allocator() {
static MemoryPool<Stamp> instance(1024);
return instance;
}
};
struct PerfStats {

View file

@ -5,7 +5,7 @@ VORTEX_DRV_PATH ?= $(realpath ../../../driver)
VORTEX_RT_PATH ?= $(wildcard ../../../runtime)
LLVM_PREFIX ?= /opt/llvm-riscv
OPTS ?= -g1
OPTS ?=
VX_CC = $(RISCV_TOOLCHAIN_PATH)/bin/riscv32-unknown-elf-gcc
VX_CXX = $(RISCV_TOOLCHAIN_PATH)/bin/riscv32-unknown-elf-g++
@ -28,7 +28,7 @@ CXXFLAGS += -std=c++17 -Wall -Wextra -Wfatal-errors
CXXFLAGS += -I$(VORTEX_DRV_PATH)/include -I$(VORTEX_RT_PATH)/../hw -I$(VORTEX_RT_PATH)/../sim/common -I$(VORTEX_RT_PATH)/../third_party
LDFLAGS += -L$(VORTEX_DRV_PATH)/stub -lvortex $(VORTEX_RT_PATH)/../third_party/cocogfx/libcocogfx.a -lpng -lz
LDFLAGS += -L$(VORTEX_DRV_PATH)/stub -lvortex $(VORTEX_RT_PATH)/../third_party/cocogfx/libcocogfx.a -lpng -lz -lboost_serialization
# Debugigng
ifdef DEBUG

View file

@ -0,0 +1,894 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<!DOCTYPE boost_serialization>
<boost_serialization signature="serialization::archive" version="15">
<cgltrace class_id="0" tracking_level="0" version="0">
<version>0</version>
<drawcalls class_id="1" tracking_level="0" version="0">
<count>2</count>
<item_version>0</item_version>
<item class_id="2" tracking_level="0" version="0">
<states class_id="3" tracking_level="0" version="0">
<color_enabled>1</color_enabled>
<color_format>5</color_format>
<color_writemask>4294967295</color_writemask>
<depth_test>1</depth_test>
<depth_writemask>1</depth_writemask>
<depth_format>13</depth_format>
<depth_func>1</depth_func>
<stencil_test>0</stencil_test>
<stencil_func>0</stencil_func>
<stencil_zpass>0</stencil_zpass>
<stencil_zfail>0</stencil_zfail>
<stencil_fail>0</stencil_fail>
<stencil_ref>0</stencil_ref>
<stencil_mask>255</stencil_mask>
<stencil_writemask>0</stencil_writemask>
<texture_enabled>1</texture_enabled>
<texture_envcolor class_id="4" tracking_level="0" version="0">
<r>0.000000000e+00</r>
<g>0.000000000e+00</g>
<b>0.000000000e+00</b>
<a>1.000000000e+00</a>
</texture_envcolor>
<texture_envmode>2</texture_envmode>
<texture_minfilter>1</texture_minfilter>
<texture_magfilter>2</texture_magfilter>
<texture_addressU>0</texture_addressU>
<texture_addressV>0</texture_addressV>
<blend_enabled>0</blend_enabled>
<blend_src>0</blend_src>
<blend_dst>0</blend_dst>
</states>
<texture_id>0</texture_id>
<vertices class_id="5" tracking_level="0" version="0">
<count>4</count>
<bucket_count>13</bucket_count>
<item_version>0</item_version>
<item class_id="6" tracking_level="0" version="0">
<first>3</first>
<second class_id="7" tracking_level="0" version="0">
<pos class_id="8" tracking_level="0" version="0">
<x>-6.225735188e+00</x>
<y>-1.033663750e-01</y>
<z>1.372576523e+01</z>
<w>1.500743675e+01</w>
</pos>
<color>
<r>1.000000000e+00</r>
<g>1.000000000e+00</g>
<b>1.000000000e+00</b>
<a>1.000000000e+00</a>
</color>
<texcoord class_id="9" tracking_level="0" version="0">
<u>1.000000000e+00</u>
<v>0.000000000e+00</v>
</texcoord>
</second>
</item>
<item>
<first>0</first>
<second>
<pos>
<x>-6.225735188e+00</x>
<y>-1.205309677e+01</y>
<z>1.892934608e+01</z>
<w>1.995718384e+01</w>
</pos>
<color>
<r>1.000000000e+00</r>
<g>1.000000000e+00</g>
<b>1.000000000e+00</b>
<a>1.000000000e+00</a>
</color>
<texcoord>
<u>1.000000000e+00</u>
<v>1.000000000e+00</v>
</texcoord>
</second>
</item>
<item>
<first>2</first>
<second>
<pos>
<x>6.446937084e+00</x>
<y>1.051850319e-01</y>
<z>1.381658077e+01</z>
<w>1.509382248e+01</w>
</pos>
<color>
<r>1.000000000e+00</r>
<g>1.000000000e+00</g>
<b>1.000000000e+00</b>
<a>1.000000000e+00</a>
</color>
<texcoord>
<u>0.000000000e+00</u>
<v>0.000000000e+00</v>
</texcoord>
</second>
</item>
<item>
<first>1</first>
<second>
<pos>
<x>6.446937084e+00</x>
<y>-1.184454536e+01</y>
<z>1.902016068e+01</z>
<w>2.004356956e+01</w>
</pos>
<color>
<r>1.000000000e+00</r>
<g>1.000000000e+00</g>
<b>1.000000000e+00</b>
<a>1.000000000e+00</a>
</color>
<texcoord>
<u>0.000000000e+00</u>
<v>1.000000000e+00</v>
</texcoord>
</second>
</item>
</vertices>
<primitives class_id="10" tracking_level="0" version="0">
<count>2</count>
<item_version>0</item_version>
<item class_id="11" tracking_level="0" version="0">
<i0>1</i0>
<i1>2</i1>
<i2>0</i2>
</item>
<item>
<i0>0</i0>
<i1>2</i1>
<i2>3</i2>
</item>
</primitives>
<viewport class_id="12" tracking_level="0" version="0">
<left>0</left>
<right>800</right>
<top>0</top>
<bottom>600</bottom>
<near>0.000000000e+00</near>
<far>1.000000000e+00</far>
</viewport>
</item>
<item>
<states>
<color_enabled>1</color_enabled>
<color_format>5</color_format>
<color_writemask>4294967295</color_writemask>
<depth_test>1</depth_test>
<depth_writemask>1</depth_writemask>
<depth_format>13</depth_format>
<depth_func>1</depth_func>
<stencil_test>0</stencil_test>
<stencil_func>0</stencil_func>
<stencil_zpass>0</stencil_zpass>
<stencil_zfail>0</stencil_zfail>
<stencil_fail>0</stencil_fail>
<stencil_ref>0</stencil_ref>
<stencil_mask>255</stencil_mask>
<stencil_writemask>0</stencil_writemask>
<texture_enabled>1</texture_enabled>
<texture_envcolor>
<r>0.000000000e+00</r>
<g>0.000000000e+00</g>
<b>0.000000000e+00</b>
<a>1.000000000e+00</a>
</texture_envcolor>
<texture_envmode>2</texture_envmode>
<texture_minfilter>1</texture_minfilter>
<texture_magfilter>2</texture_magfilter>
<texture_addressU>0</texture_addressU>
<texture_addressV>0</texture_addressV>
<blend_enabled>0</blend_enabled>
<blend_src>0</blend_src>
<blend_dst>0</blend_dst>
</states>
<texture_id>0</texture_id>
<vertices>
<count>4</count>
<bucket_count>13</bucket_count>
<item_version>0</item_version>
<item>
<first>1</first>
<second>
<pos>
<x>-6.446937084e+00</x>
<y>1.184454536e+01</y>
<z>1.892855453e+01</z>
<w>1.995643044e+01</w>
</pos>
<color>
<r>1.000000000e+00</r>
<g>1.000000000e+00</g>
<b>1.000000000e+00</b>
<a>1.000000000e+00</a>
</color>
<texcoord>
<u>0.000000000e+00</u>
<v>1.000000000e+00</v>
</texcoord>
</second>
</item>
<item>
<first>5</first>
<second>
<pos>
<x>-6.225735188e+00</x>
<y>-1.033663750e-01</y>
<z>1.372576523e+01</z>
<w>1.500743675e+01</w>
</pos>
<color>
<r>1.000000000e+00</r>
<g>1.000000000e+00</g>
<b>1.000000000e+00</b>
<a>1.000000000e+00</a>
</color>
<texcoord>
<u>1.000000000e+00</u>
<v>0.000000000e+00</v>
</texcoord>
</second>
</item>
<item>
<first>0</first>
<second>
<pos>
<x>6.225735188e+00</x>
<y>1.205309677e+01</y>
<z>1.901936913e+01</z>
<w>2.004281616e+01</w>
</pos>
<color>
<r>1.000000000e+00</r>
<g>1.000000000e+00</g>
<b>1.000000000e+00</b>
<a>1.000000000e+00</a>
</color>
<texcoord>
<u>1.000000000e+00</u>
<v>1.000000000e+00</v>
</texcoord>
</second>
</item>
<item>
<first>4</first>
<second>
<pos>
<x>6.446937084e+00</x>
<y>1.051850319e-01</y>
<z>1.381658077e+01</z>
<w>1.509382248e+01</w>
</pos>
<color>
<r>1.000000000e+00</r>
<g>1.000000000e+00</g>
<b>1.000000000e+00</b>
<a>1.000000000e+00</a>
</color>
<texcoord>
<u>0.000000000e+00</u>
<v>0.000000000e+00</v>
</texcoord>
</second>
</item>
</vertices>
<primitives>
<count>2</count>
<item_version>0</item_version>
<item>
<i0>4</i0>
<i1>0</i1>
<i2>5</i2>
</item>
<item>
<i0>5</i0>
<i1>0</i1>
<i2>1</i2>
</item>
</primitives>
<viewport>
<left>0</left>
<right>800</right>
<top>0</top>
<bottom>600</bottom>
<near>0.000000000e+00</near>
<far>1.000000000e+00</far>
</viewport>
</item>
</drawcalls>
<textures class_id="13" tracking_level="0" version="0">
<count>1</count>
<bucket_count>13</bucket_count>
<item_version>0</item_version>
<item class_id="14" tracking_level="0" version="0">
<first>0</first>
<second class_id="15" tracking_level="0" version="0">
<format>4</format>
<width>128</width>
<height>128</height>
<pixels>
AAAAAAAAAAAgACAAIAAgACAAIAAgAEEIQQhBCEEIQQhhCGIIYghiCGEIYQhBCGEIYQhiCGIQgRCB
EGEIgQiBCGIIQghCCEIIYQhhCEEAQQBCCEIIQQgBACEAQQhhCCAAIQBiCCEIIQAhAAAAAAAAAAAA
AAAAAAAAIAAgAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAgACAAIAAgACAAQQhBCEEIIAAAAGEIgRBh
CCAAQQhiCGIIYghhCGEIYQhiCEIIQQhhCGEIYghhCEEIQQhBCEEIQQhBACEAQQhCCEEIYghBCEEI
QQhBCEEAQQBBAEEAYQhhCGEIYQhBCAAAYQiiEOMYRSkEIU1rLGPLWmlKilKqUklKKEJJSklKaUpp
SopSq1LLUqtSakppSmlKDGPve+978HsQfFGEcpRRjDCEEIQQhPCDEITPe85z73vve+97z3vPe/B7
rnPLWqpSLGPrWuQYq1ota8taq1KLUmpSSUooQihCKEIoQklKSUqqUkUpaUrLWqYxZSkIQqYxxzkI
QihCCEIIQihCaUpJSihCaUooQsMY5znrWitjLGONc857znvve+97UYwvhA+E73vwe/B7D4Tue69z
rnPvexCEjnNta21zbXMsY4pS7FpNay1j7FrsWi1rjnOOa21rLGMsY45zz3vPe/CDrntlKWlK5zll
KUlKCELTnO97DGPLWutaDGMMY8taqlKqWutaDGMsY01jbWMtYwxb7FqucxSd61onSoQxwhBFIWpS
yWLFOaMYJSmJWslaYymCECQhSEqJUgQhghCFMQtjUYwQhK5zMITLWsc5MYTwe+xa7FoMYwxjDGPr
WutaDGPrWstaDGPrWixjDGOqUmlKy1ppSopSqlKqUqpSqlLrWixjDGPrWstaZSnnOY5zbWvve1St
UIyJUsVB4iDDGMc5iFIHSsMgoxjHQala5kGCGIIQpjGJUsY5ghCiGOdBjXNRjI9z8Huvc49zr3MQ
hNOcFKWynHGUs5z0pBSt86RRlLKUpjHLWghChjFpSklKkpQsYyxjDGPrWgxjLGMMY6pS61oMW+xa
LGMtYwxbLWNNY897NKXHOQAAg0EkUmMxIAiBGKI5JEojKQAIgRjlQcNBIikAAEAIxTkHQuIYAADC
GMpakYzve45zMYRJSsc5r3Mtay1jDGMtY01rbWuuc65zjnMMY2lKq1LLWopSaUrLWgtjLGMMYwxj
bWssYwxjTWtNayxjTWvnOaYxTWtNa897E50whEAAgRAGUsVJgRgAAEQxBkqmQWEYIAhDMeVBZDmh
EAAA4hgGQoU5IAggCGUxr3NyjG5zr3vQe45z0HuOcwQh4yDDIMMgwyDDIAQpbXNxjEUpilIIQqYx
qlJJSjGETWtuc05rLWNNY25jTWPrWgxbDGMMW01jTWMtYy5jEXzSlCp7AjkgCMIYRVrocsVBYRBj
KehiSXMGQoEI4iimagd7pUnjIIQpSmtKc4Q5wxDGOQljUIzQe69zcowpQkUhxzEpQmpKakqrUqta
y1rLWklKhilqSilCCDroOQg6SULnQaY5ylrKWuxaDFvLUopSaUqrUgg6SUKuc45z8HvUlHKMqFKB
EAAAIzGHakZagRCBEOZByWJnWgMhoRAFSiiDp2pjOaEYxkGLe0dSYRDjIIlSD4RxjE1rjnNua/B7
7FoAAAAIAAgAEAAIAAggEI17kpRFKetaSUqmMctailJxjK9zz3vPe897r3Pwe45z62JNYy1jLWNt
a21rLWOPa9SUpzFjOSVaAikAAGAQ5EnHWkQpAADiGIdSyGJDKQAAohhoYudqw0FBEOIYqFrpauIo
QAhkMepicZQQfM9zEHyKSihCKDpJQmpK6DkpSmpSCEIpQq9zs5QwhFGEMYQwhHKMspTKWmlKKEIp
QucxKUKKSopKakqKSvB78Hvwe/Ocr3MkKclaRkphCAAAAilnWgZKQQgAAKUxylonSmEIIACEOcdy
6HJDMSAIxTkra+dBQQjiGGhKD3wxhG5rjmvwe9OUbXMsc01zLHMsa+ti62JRlHGMZSksY0lKpjGK
Uuta05xRjLOcUYxyjNOc05xxlG1zbWuOa45rjmtua25r05SJSgAAQBCEQUZaIzEgCMEYhlLIWmQx
AACBECVSZloCIQAAgRinammDxVFgGMEgh1qmYsEgIAhEMSprNKVyjK9z8HsQfBB8z3OOa8tabnNu
a8taEHxRhO978Huvc69zr3MQfBCEUYzPc+xar3PLUgxb8Hvve89zrnOOa45rkpRQlKIgAAgiKada
JUphEEAIZDGoYkhSgRAAAGUpyVoGQmAIIACDQQeD53IiKSAIhDnoYsRBIAChECdCMISzlG5rz3Nx
hJKUUYxRjFGMkpSzlJKUMYRxjGUp61ooQqYxaUoMY1WtjnMpSilC6EEoQghCB0ooSu97z3PPc45r
j3NRhKpSK2MGQiMpRDHqcsuTRlKEMUZayouvpApr4iBCMQh7q4snUgIppUGsk620KYOEOcU5q4PL
g6VBwiDnSa9zdq2SjM9z73vPc69zz3uzlBCEEYSzlBCE8HvPc89zjmuvc89zEHwxhBB8coz0nDGE
FJ2SjBB8EHzPc65zr3OSjG5ziVorc+ZJhDmGWguMSXuEOYQ5CXOwpE+cxUEDIahSTpRrg6U5pTnn
cmusC5QGSgIxZWLJkwhrwSAiMYhaspT0nK9zr3Ovc69rz3Ovc/B7MYRRjDGEspRFKetaSUqmMYpS
LGN1raY5AAAAAAAAAAAACAAAQRAxhO9zbmtNYzCEZSkAAEUppTnjIOMg4yDFSaVB4ijiKGQ5R1Jo
WgMpgBAjKUdKR0ojKcIgRDGIWqlaZDGiGGUxJ0rGOWEQYRDDEElC9JxxjNB7r3Ovc9B7UYTwe/B7
EHzwexB8EHwQfO97z3PPczGEcoxxjJKMs5RyjNOUkowxhFGEMYSvc3GMbXMgCIIYxkHGQYQ5ZDEm
Sqha5UECKUMxZ1Iqa2dS4iDiIAZKqFoGQkMpIykmSqha5kHBIKEgxUGGUkIpQBCCGEUhz3OSjM9z
z3vPezCEEIRyjFGEUYSSjPOcZSlta2lKxzlpSixjlrXvg0lSSVJJUklSCErnQUlScozvc25rbmsI
QqIQohCCEKIQohiiGIIQghCCEKIQghCCEIIQghCiEKMYghBiEIIQoxijGIIQghCiEKIY4yDCGKIY
wxjDGMIY5BjDEGlK05QxjPB7r3Nua89zz3PwexB8z3PvexB8MYQxhBB88HsxhHGMUYRxjJKMcYxy
jHKMcoxRhBB8UYQtY+MgohCCEKIQwhDDEKIQohCiEKIQwhCiEIIQghCiGMMYwxiCEIIQwxjDGKIY
ghCCEKIQ4xjjGKIQYhCjGKIYohjDECQhz3NRhM97EIQwhFGM05SSjBB8UYQ0pWUpTWuqUsc5SUoM
YzjGt712rfScs5TTnJKUMYRyjFGEz3Nua+xailKqUspay1rrWuxaDFvrWuxa7FrrWutaDFvsWstS
y1rLWgxj7GLsYg1jLWMNY8xaq1KqUopSq1LrWuxay1rLWo5rMHwQfNOU05yzlDGEr3Pwe+97EHwx
hBB8UYRRhHKMr3NRhDGEUYSSjJKMspSzlLOUkoySjLOUspRRhJKMbmstY01jTWuOa65rjmuua69r
jmuOa69rjmuPc45rjmuOc45rbmuOa01rTWsMYwxjDFstY25jbWNNY8xaq1LrWitjTWMsY45rUYQQ
hJKUkpRxjJKMcoxyjLKUda0kIU1rilLHOShCjXP73hjG1701pfScNaU1pRSd9JwUnbOUkoySjJKM
kpSSlJKUkoxyjJKMcoxxjHGMkZRRjFGMUYyRlHGUMIxRjFGMUYzTlPOcsoySjHKMUYQxfFGEUYSS
jLOUs5T0nHatt7WWrZa1dq0UpXKMkoyylNOU9Jw1pXat85wMWxB8TWP0nHatlq23tba1lq1VpXWt
lq12rZatlq2WrZatlq12rXatlq2VrXWtVK1VrVWtNKV1rVWtVaV1pVWlFJ00pVSlda11rXWlNJ00
pROd85xUpROl85zznPOU0pTSlBSdFJ0UnRSlFKVVrVWtNKX0nDWllq3XtXnOZSksY6pSSUrrWk1r
utY4xvi9lrV1rZa1da11rXWtlrV1rZa1lrW2tde117WWrXWtNaVVrVWtNK00rTSlNKU0pVWtVa00
rfOcFKU0pRSlNKU0pTSlNKVVpRSl85zznBSdNaVVpVWlNaVVrZa1t7W3vbe1drV2rba1lq2Wrba1
trX3vXWtqlK2tapSlrU4xhi+GL4Yvve91733vRjG173Xvfe99733vde9973Xvde91r2WtXWtlrW2
tba1tr2WtZa1lrWWtZa1trW2tZa1trW2tba1lbVUpTSlda2VtZW1da1VrVSlVa1VrVWtVa11rVWt
da3Xvde9173Xvfe9OMYYxmUpy1qqUopSLGOCEAhCpjHHOcc5pjEEIaIQJCFFKeMYJCHHOaYxpzHH
MccxxzGmMUUpZSmGMYYxhjHHMaYxpjHHOccxRSlhCEUpYQgkIWUxRSllKWUpZSlFKUUpZSllKYYp
himGKcc5xznHOUlKCELoOeg5CEIoQuc55zkIOghCCELHOcMYxznDGMc5xznnOec5xjGmMYYxxznH
OaYxpjGmMcc5pjGmMcc5xznHOcc5xzllKUUpZSlFKUUpBCFFKUUpBCGCEOIYIyEjIeMY4xjjGMMY
wxjjGOMYwhhhCIIQohCiEKIQghCiEKIQohCCEIIQohAkIUUpJCFFKUUpRSlBCOtaSUrLWo5zJCFN
ayxjDGOuc3GMCEJlKTCEz3vLWjCEEITPe69zr3Ovc65zDGPrWgxjTWtNayxjbWuOc21rjnMwhAxj
ghCOc4IQ61rPewxjy1osY65zTWssYyxjTWNua45rz3MxhHKMkpSznJKUUYwxhBCEcYwwhFGEspTT
nNOccYznOSxj5zlxjLKUcYwwhBB873uuc1GMkpRxjDCEEIQQhBCEEITve+97z3sQhFGMz3vrWsta
DGOOc01rMISylHGMghCFKUhKiVJoSihCilKKUopSSEKmMcIYAyHPe65zjnNta45zjnNtayxjDGNN
a2EIKEKucyxj61rrWk1rJCEsY0lKqlKOc2UpKEJlKeMYxznLWsc5JCHrWuc5ilJxjM9773sQfBB8
MYTwe897z3vve65zjnOuc65zz3vPe+97EITPeyxj73sMY89773uOc65zbWttayxjTWvrWk1rbmtu
a01jjnMQhFGMUYwxhBCEz3vPe+97znPvew98z3swhHGMbWsMY01r73sQhO97z3PPc65zjnOuc1GM
UYzve897z3uuc89773uuc21rjnOuc897jnNNayxjbWuqUk1rEISylGUpaUppSklKSUpJSklKKEJJ
SgdC5zkoQs97MITPe65zbmtuc69zEIRRjBCEUYzkIKYxy1oIQmUppjFpSmUpLGOKUmlKLGNlKUlK
KEIIQghCilKnMcMYilIMYzGE8HsQhBB8EHwxhHGMMYQxhK9zkpQxhM97z3sQhM9773vve+97z3uO
c65zz3vPezCEjnOOc21rTWvrWgxj61pNY45rjmuuc89zUYRxjFGEMYQQfBB8MITve+9773vvezCE
73swhFGEjmsxhK9z8HsQfBB8EHzPc+97EHwwhFGEMIQxhPB78Hvwe/B7jmsMW01rbWuuc65zrnMM
Y21rrnOuc21rcYyrUvSccowUnZKMkowxhDCEUYQwhDGEcYwwhM9z73vPc69zj3Ovc89zMYT0nNOc
5BhmKWlKCEIoQghCSUJFKexaq1JpSgxjZSkoQopSrnNpSmlKZjEpSrOUs5xxjBB8UYQQfO97MIRR
hDCEz3tuawxj7FrLWgxjDGMMYwxj61rrWuta61oMYwxj61oMY21rbWtta45zLGMsY+taLWOvc/B7
z3OOa3KMVaXznPB7z3PPcxB8spRVrTSlda3Xtba1da1VpRSdVaVVpba117W3tde117XXtfe9+L2W
rXatdq12rVWlkoxuay1j7FrrWixjjnNtayxjTWvPe45zjnOua1GEy1LTlJKMspQQfBB8z3MQfBB8
8HsQfDGE8HsQfPB7r3OvcxB8kozPc89zcYySjAQZxzFpSopSrnNpSmpKZikMW6tSilJNa2UpxzmG
MQhC5zksY4YxakqTlDGEMYSSjHKMcowQfBB8EHzPc69zj3NOa01rDGMsYyxjjnOOcyxjDGMsYyxj
LGMsYwxjDGMsY01rbWttawxjLGPrWm5rMIQxhI5rEHyWrZatkozPc89zz3Pwe21rbWsQhHGMspRx
jDCEMIQxhFGEUYSSjFGEUYSSjPScNKXznNOU05RxjHKMMYSylHatkoxNY8tSy1oMYwxjLGOKUixr
DGPDGGUpcoxyjKtS9JySjBB8MYQQfBB8MYRRhDGEUYTwe89z8Hvwe89zz3MPfAtbaVKKWstiqlqC
EMYxbWvnOcc5pjFJQmYp7FrLUqpSjnNFKcc5ZSmmMShCq1KCEGpK05xyjHGM05SzlHKMMYQwhPB7
rnOvc45zTmsMYwxjDGMMY45zjnMsYwxjDGMMY+tiDGMMY+taDGMsY21rTWuKUutaDGPPcxB873uy
lPe9NaUQfHKMz3Ovc69zUYQMYyhCLGNta21rDFssY0xjTWNNY89zz3Ovc69zr3Ovc+97z3Ovc45r
r3Pve89zjmvwe/Sc9Jxua6pSqlLLWm1rTWuuc+taaUpqSlGEcYzLUpKMMIQxhJKMMYQQfFGEcoyz
lJKMEHzwe/B7jmuvczGEiVIAAAAYACAAGAAYIBCiGGlKSUqGMWUpCDoEIexa61qKUm1rZSkIQmUp
xznLWuc5BSEpStScUYxyjJKMspSSjFGEMITwe45rj2uvc21z62LKWixjDGNNa21zLGsMY+ti62Lr
YgxjC2MLYwxjLGNNa45zilIMYyxjrnPvexSdGMZRhOc5LGOSjK9zjmuvc1GELGOKUixjbWtta45z
TWsMY0xrjnMxhDGEMITwexB8MYRxjFGEMYTPc45rMYQQfPB7r3Ouc5KMFJ1NY4pKDFvsWgQZy1Lw
exB8kozwexB8q1IxhDCEUYRxjFGEcoxRhHKMkoxyjPB7EHzPc25rrmvve+tiwyiCKIIoozCjKCAI
4xiGMexaxzmnMUlCZiktY+taSUosY+c5SUqmMec5LGNJSmYxCULTnHKUspRRhJKMkoxRhBB8z3Ou
c45rjmttc+tiaUoMYyxjjnONcyxrC2PrYgxr62LrYgtj61oMY21rbWuOc4pS61qKUs97Va1VrRCE
SUqmMSxjUYyvc45rz3MxhAxjaUoMY21rrnOuc01rLGNtc0xrz3tRhDGEMYRRhHKMcoxyjHKMMYQQ
fFGEEHzve/B7z3NNYzGEFJ1ua01jz3PrWs9zcoxRhJKM8HsxhKpSUYRyjHKMcYxRhLKUkoxRhHKM
coxRhDGE8Huvc45rjmsRhHKMr3Pve1KMEITkICUp6DnsWug56DmKUqcxLGPLUklKDGOmMUlKhjHn
OetaaUqGMSlK9JxyjHGMEHwxhDGEMYQQfI5rr3Ova01jDGOqWihC61pNa45zjXMsayxrLGssa+ti
62LrYgxjLGNta21rTWuKUmlKrnNVrdOcLGMMY8ta5znLWhB8bmtta65zMYQMY0lK61pNa65zjnNN
a45zbXNMa65zEHyOa89z73tRhFGEMYQQfDGEEHwwhDGEMYTPc89zjmtNYzCEdq2vc01jrnMxhFGE
8HtyjDGEkoyrUnKMcoxRhFGEUYQQfDGEMIQxhBB8UYQxhBB8z3PPc45r8HvsWklCilLLWstawxgF
Icg5akqnMcg5ikqGKS1j7FppSutapjEoQmUp5zmqUklKpzGLUlWtcowxhM9zrnMQfO9773uvc45r
bmtNYwtjaVLnQapSDGMsY21rC2MMYyxrC2PrYqpaylosY01jTWtNayxjaUrPe1WtEIQMY01rbWvr
WghCqlIwhI5rbmuOazCELGNJSgxjLGOOc65zTWsMYyxrbWvvezGEbmuOa25rbmsxhDCEUYRRhPB7
z3Pwe/B7rnOOa89zr3MMW45rs5SOawxb7FotY01jEHxRhHKMq1JRhDGEEHwxhPB7r3PPcxB88Huv
c89zz3Ovc89z73sQfElKAAAAGAAYABAAEAAIRSmGMUlKhjGGMUlCZSktYwxbaUrrWqYxCEJFKcc5
aUoIQkUpi1KWrXKEUYTPc05r8Hvwe89zz3Ovc25rTWvrWolSKEKKUutaDGOucyxjK2sLYwxjy1rL
WutaLGNNY25rbWtNa897EIRNa+tabWtta45zDGNJSsta73uOc25rr3MxhAxjSUosY45zjnOucyxj
LGNNa45zEIQQhBB88HvPc69zEHwQfFGEMYTwe89zz3Pwe65zbmuOa45zbWuqUstarnMtY8tSTWNN
Y/B7z3NRjKtSMIRRhBB8UYQQfK9zr3OOa89zr3OOa25rjmvPc45rr3MMWyUpxCikMOQw4yghCIYx
hjFJSmUpZSkoQkUpLGPrWihCilKGMcc5BCGGMShCpjEEIeg59JRxhDGEz3NNYxB8EHyOa89zr3OP
c01rC2OqUihCKEKKUutabWMLWytjC2MMY8tay1oMY01jTWOOa45rjnOSlIpSSUosY45zbWuOc01r
ilKqUs97jmuOa+97EHzsWklKDGOuc01rTWvrWuta61qOc+97z3sQhBB8MYQQfK9zEHxRhFGEEHzP
c89zrnOOay1j7FqOa01jaUooQk1rDFvsWm5rr3NyjDGMs5xqShB8MYQxhPB7jmuuc65zjmtua45r
jmtNYy1jr3Ouc45rrmuuawxjr3vvezCE4xhFIUUhKEKGMUUpKEJlKQxjy1ooQopSxzkIQiQhpjFp
SmUpwxiGMa57jnuNc0xrrWtQfM9zjmuvc89z0HuOc21rylrLWopSaUrrWk1jC1sLY+piDGPLWuta
DGNua45rr3Pve45zcYyqUmlKDGNta897z3tta6pSilKucyxjbmuvcxB8LGNpSixjrnNNayxjrnMM
Y+tajnOOc01rMYSvcxB8EHzPczGEEHzwe/B78Hvve89zjmstYwxbTWNNa2lKKEKOawxby1KvcxB8
LGPHSWU5CELve1GEcowQfK9zbmuOa69zjmuOa45rTWMMW25rr3OOa25jbWMMWxB8kYRQhAQZohBF
KetapjGGMYpShjGqUopSKEKqUqYxKEJlKcc5ilKKUkAQQBhBIEEoYShhIIIYjWuOa25rjmuvc9B7
r3ONc8paqlLLWqpSDGNNYwtj61rKWixjDGPrWi1jrnPPc+97z3OOc5KULGPLWm1rrnOuc21rLGNp
SklKjmvrWgxbjmsQfAxjaUoMY21rr3OuczCE73ssYyxjTWtta65zjmvPcxB8UYRxjBB873vwe/B7
8HvPc69zbmtta45rjnOKUklKjnMMY6tSbmuucyhCZUGGQUhCMHxyjJKMMYTPc65zjmvPc89zr3OO
a01jTWNua01jbWtNa45zTWsMWwxb61rDGAAASUqqUqYxhjFpSoYxSUpJSghCqlKGMQhCZSnHOec5
LGMHQiAQQSBBIEEoQSCCGG1rEHyua89zEHwwhBB8EHxNY6pSy1qqUgxjTWssYyxjTWstY01rLWNu
c69z8HsQfM9zrnNRjOtay1que++Dr3tucyxrSUppSq5zTWNNY25rEHzsWopSTWuNc69zjmuNc41z
bWtNawxjDGPrYk1rz3sQfHKMkoySjFGE8HsQfPB7z3Pwe49zLWNua45zqlJpSo5zLWOKUm1rjmtt
a45zrntpSlGEkoxyjJKMcoyvc89z73uuc65zjmtua25rj3NtYyxbLWOvc69zz3OOa69z5BinMeta
5zmGMWUpSUplKWlKKELHOapShjEIQoYxpjHHOctSaUJmMY5zz3NtawxjEYTxe45rbmuvc/B78Huv
c69zDFvrWstailIsY65zjnMwhO97DWMtY25rr3PPexB88HvPc897EISqUgxjrnsQhBCErnuOc4pS
y1rPe45rbmtua/B7zFpqSixjbGsQfK5zTWvrYgxjTWvLWgtjqlrrYq5zUYSylHGMcoxyjFGE8HvP
c/B78HuOc05rjnOOa6pSaUptayxjqlLrWs97CEJBGCEQCEKSjJKMs5SzlJKMMYTwexB8z3OOa21r
jmuucxB8jmstW01jrnNua+978HvPcwQZSUKKUqYxhjFFKUlKZSlpSihC5zmqUmUpCEKGMcc5pjGK
SihC6EEQhFCEz3tNYzGEMoSOa69zr3PPc89zjmuOa8tSy1rrWqpSTWuOc45zUYxRjA1jLWOPc897
EHzwe69zr3PvexCEqlIsY897UYxxlDCEz3uqUqpSznOOa45rbmsQfC1jakqqUqpSz3PPcyxrC2Pr
WgxjDGPrYopay1quczGEs5RyjHKMcowQfBB8EHwQfNB7bnNuc69zjnNpSmlKz3sMY4pS61osYyxr
imJJWmlSkoxRhHKMkoxRhDGEMYQxhBB88Hvwe897D4xQjO97bmuvc69zrnMQfPB7z3PkGAg6SUqG
MYYxRSkoQkUpaUooQsc5ilKGMQhChjGmMaYxSUonQiAQYShBKCEoQSBiGCxjr3Pwe89zrnOOay1j
bmvrWqpSy1qKUi1jbWttazGEUYxua25rjnPwexB8MITve/B7z3tRjOtaDGOvexCEEIQQhK5zqlKq
Uu97bmuOa45rMYRNa4pS61qJUk1jz3NMayxrLGPrWixjy1ppUutaTWvwe3GMcoySjHKMMYTve/B7
z3OOcwxjTWuOc45zSUrnOe97DGOKUuta7FptYyxrLGuKSlGEMIRRhPSc9JySjNOUkpRxjJKMkoyy
lJCckJxxlHKMkpRRhPB78Hvwe+975BjoOUlKhjGGMYYxSUqGMUlKKELHOYpSpjFJSqYxhjGGMShC
B0IgEGIoYjBCMIIowyBMY89zEHxRhK9zjnNtaxCEDGOJWsta7Fqvc/B7TmvwgzGEz3MxhPB7EHwx
hDGEEHwQfO97MITLWutajnOue++Dz3tNa4pS61rve25rLWOOcxCELWNpUspiSFLrWq5zbWtta45z
bWvsWqpSJ0qpUm1r73MwfFGEUYQQfDCEEHyvc45rbWvrWgxjbmuKUsMYhjHOe4lSKELLWutaTGMM
YyxjqlJxjDCEspRxjIUpBCEHSqx7qVrHOW1rhCkIayiDpEHDEK9rkozPc/B773vPc+MYCDpJSmUp
hjGGMShChjFJSghC5zlpSqYxKEKGMWUpZSkoQgdC5zkwjO+DznsMY857znPPczCEMYRNYwxjDGMM
Y0lKiVrKWqtSbmtuawxjMYQQhMta8HvPc1GEMYRRhPB78HswhFGM61rrWm1zjnPvg657LGOKUgxj
UYzPe01rbmsQhAxjKEppUgdKylquc21rTWttayxj61pJSqU5KErKUm1j73vwe89z8HvPc89zjmuO
a21rLGMsY6pS4xgkIU1rLGMoSudB7FrLUqpSDGNta8taMITPe7KUjnPCGAAAAADIWopzSFL0pIII
IzFGg8RyYRjsWlGEz3MQfPB7EHzkGClCaUokIUUpZSkoQoYxaUooQuc5aUqGMQhCRSmGMUUpSUoI
QmpKVaWyjLKMrmsQfPB78HvPc89zz3Nta21rjnPrWihKiVJNY45rj3NNa01rj3Ovc3KM8HtRhDGE
UYQQfM9zEIRxjKpSy1pNa697jnNNa01rilLrWjGMz3uve69zUYwsY0lKB0KFOaparnNta01rTWsM
Y+taCEIjKSMhZSkIOilCCDqKSstSikpJQilCSUIoQuc5hjHDGCQhbWssYyxjB0LGOeta7FrsWk1r
z3vLWlGMz3swhAxjpjlkMQAAYQgJa0xrNKUlIQAA4CgGcydaLWMxhPB7MYQxhBB85BiKSqpSZSll
KWUpSUqGMYpSKELnOapShjEpQmUpRSllKYpSCDpmKRCEjnOOc/B7MXwwfO97r3PPc69zbWtta45z
jnNtc0xrbWuOa69z0Huve897UYQwhBB8s5RRhDGEMYQxhPB7cYyKUutarnvve45zjXOOc4pSyloQ
hPB78HvQe3KULGMoQgdCZTEHQutaLGNNayxjLGMMY+c5oRCBEMIQBBnkGAQZJSEkIeQYwxDDEMMQ
wxiiEKIQZSlta01rqlLLWudBxjmqUstSLGNNa45zy1owhM97UYzLWmUxyVpDKQAAwhgLazOlCluF
MQAAZTGoWixjr3OOazGE8Huvc8MQakqqUmUpZSllKUlKhjFpSihC6DmrUmYpCEJFKSQhhjHLWilC
hjHPe45zz3vvexB8EHzwe01jr3Nua21rz3Pve65zjmuOc45rjmvPc+97EITwezCEUYRRhPSccoxR
hBB8UYRRhHKMqlpIUkhSKEoHSihK50kHQspaEIzvg++D8HtyjMpapTmFOUQxIyEHOqpS6lqqUmlK
ilKmMQQhMIRRjBCEr3MQfFGMUYxRjO97EIQQhO97rXNMazCEjXPKWutaylqGMaY5SEpJSgtjjXNM
a6pS73uuc3GMy1ogAGZSx2LjIAAAhjESpchaioMDMQAA4hAMYxCETGNta65zjXOiEChCilJlKUUp
ZSlpSqcxqlIpQug5alJmKQhCKEIsY0lKy1pJSqcx8Hvwe/B7z3PwexB8MYSOay1jbmvPcw98z3PO
c+9zEHwxhPB7z3PwezGEUYSSjJKMcYzTlHGMUYQxhBB8EHxRhAxjpjkgICAgACAAIAAQRCkLYw+M
zoPOexCEspSqWuMg4iCBEIEQ4xgkIWUpJCEkISQhwxhlKXWt05xxjHGMspSynNOcFKV1rVWtspQw
hCxjbWsPhMpaaVIMY+tiRCnGOcpaC2ONc857bWupUu9773uSlOtaAABgCGdSCEpBCGUpU60jKehy
6XKiIAAAi1JRjI1zbGuNc65z4hgoQqpSKEIsYyhCikqnMetaSUIIQopShjEpQsc5y1oHQstaSUqG
Ma9z8Huvc69zkoySjPSckowQfBB8UYQwhDCEEHxyjHKMUYRRhBB8UYRyjHKM05SylFGEs5SylHGM
UYRRhDGEUYQMYwtjC2sscyxzLHMsc8paLGNQjI17bXPwe5KMaUqCEGEIghDDGKMQoxBiEGEIYQhh
CEEIhjHTnG1rbWuuczCEUYwQhBCEkpQwhFGMznsoQmlKLGMoSgdC62LrYmUxKErvg21zC2POe65z
ylKuc+97spQMY0EIIQCCEOMgggjjGAtbgQihIEM54iAgAKtSkpTOe2xrC2POewMZKEKqUuc5y1oI
OopKxzEMW2pKCEKKUoYxSUqGMaYxhjHLWmpKpzGvc9B7z3uvc1GEUYRyjFGE8HsxhFGEUYRRhJKU
05SylLKUkoxxjHKMsozTlLOUcowxhPOcs5SSjJKMMYQQfFGEC2NFMaIgoijDKKIooiCmOSxj74ON
e657z3MxhM97LGNNY21rTmtuay1j7GJNayxjLGPrWo5z05zPe01rrnPve897rnNta897z3vznO97
aUrKWq5zCEIoSm1zy1qGMShKUIxQjAtjjXPve0xrEIRRjLKUEHxua05rr3vvg41rjmvPc/B7z3uN
a41rLmOPc25rbWtsa0xrspQjIQdCilKmMec5xzGKSscx7FqKSgg661qnMUlCpjHHOaYxDGOKUsc5
z3vwe/B70HtyjDGEcoySjDGEkoyylHGMcYySlHKM05SzlJKMspSSjJKMcoyTlFKM8HvTnHKMkoxy
jDB8EHxyjG1zKErHUcdRx1GnUaZBaVLrWs57bnPPe697z3sQfDGEcYySjJKU9JySjJKM05yznLOU
s5SylDCErnNNa65774OOc21zjXPvgyxrrnuKUu97TWuJUkhKrnuqWghCbGvqWixjjXMMY+97z3tt
a3nO970UpXGMspSzlLOUs5TTlBSd9JwUnRSdFJ3TlJKMEISue657MIzznFWtBCEoQqpSxzkIQsc5
akqnMexaikooQgxb5zmKSuc55znHOQxjilLnORCE73vPe/B7MYxyjJKUs5SSjFGEcYySlJKUMIRR
hNOU05TTlLOUkoySjFGE8HsxjDGEk5RRjHGMUYQxhDGEkpRtc8piaVpJUklSaVJJUmlKqlIQhI5z
z3vPe697r3OOa65zjmvPc/B7z3PPc697r3OOc45z73uOc25rbWuNc857rnuue657rnsMY21z62LX
tbKUKEKue3W162KqUnWt73vKWgtbDGPwexCEaUrLWutay1qqUopKq1KrUstS7FqrUopKq1KrUopK
ikppSgdCB0LnQQdC5znnOaIQSUqqUsc550HHOYpKxzHrWopKKDrLUug5qlIIQihCxznLWihCpjGu
c65zj3PQe69zMYT0nDWl9JxyjHGMcYxxjHGMcoyzlJKM05QUndOUs5RRhK9zkpRyjLOccoxRhFGE
UYRRhLOUjnOmOQAYISAhGCEYABBEKcpSD4SvcxCEz3uvc897r3PPc+97rnOvc89zr3OOc69zjnOO
c65zrnOuc21zTGttc41zrnvOe45zylque21rpjGKUstaCEJFKSxrylplKUhKrXONcwxjrnPPe897
73tRjDCEEIRRhJKMUYSSjLOUkozTlPScspRyjHKMMYTOe41zjnONc21rUYzHOShCqlKmMec55zmq
Uug5y1KKSug5akrnOatSCEIoQuc5qlJJSsc5jnOuc49z8Hv0pJOUcoyzlPOckoxxjFCMMIRRjDGE
UYQQfFGE05QwhO97MYT0nNOcFaX0nLOUMYRRhHKMcoQUnY5zy1rrYixrTHPraopaSEqqUhCE8HvP
e69zz3uOc65zr3Ouc45rz3PPc65zr3Ove45zjnNta01rjnOOc21zLGtMa21zrnuue6parnMMYwAA
hjGqWiQpAACqWkhKAACmMc5zrnMMY897bWuOc+97cYxRjFGMkoxRhDGEUYRyjJKMkoySjJKMUYRR
hPB7TWssayxjDGNta1GMpjEoQstaxzkIQghCq1LnOQxbikrnOWpKxzHLUihCKELHOctaSUoIQs97
z3vvexCEUYwxhDCEcYxxjLKUcYxRjNOckpQwhDCEMIRxjLKUcYRyjLKUcowUnXatFJ3TlLOUUYRR
hLOUNaWOcy1j8INRjJKU8INtc4pSqlIwhBCEEHzve69zbWtua45rjmuvc69z8Huvc697jnNuc21z
bXNtc657rnttc0xrLGuNc897rnvrYq5762IAAGUxqlpFKQAAaVJISgAAZSmNc657DGPPe21r73sQ
hHGMUYxxjJKMkoxyjHGMUYRRhFGEcoySjFGEEHzPc45rbWttayxjTWsQhKYxKELLWuc5CEIIQqtS
xzHsWopK6DlqSuc5q1LHOYYxpjEMY2lK5znve897z3vve65zz3swhHGMcYxxjFGMUYxxjFGMEIRR
jHGMUYyzlHGM9JwUnRSdNaVVpRSd05TTlFGEUYRyjBWlr3Ovc3GUkpSSlDCMrnOKUqpSMITwexB8
jmtNY21rLWNNY01jbmtua69zr3OOc21zjnNNa21zjXONc857rntNayxrjXPOe21zylqueyxrJCkH
Quti50HCGEhKiVIDIcdBjXOueyxjrnMsY45zEIQwhHGMUYRxjJKMcoySjHKMUYRyjFGEMYQwhBB8
z3OOa45rbWtNYyxjz3uGMUlK61qmMWUphjGrUug5y1JJQscxakroOapS6DkIQuc5DGNpSsc5jnOO
c+97rnOOc897z3vPe+97EIQQhDCEUYxRjM9773vvezCEEHwQfJKMs5TTlNOUFJ3znLOUcoxxjFGE
UYw1pfB78HuSlBSl05ySlO97ilKKUjCE73sQfM9zjmtNYy1jLWMtY01jbmuvc45rTWssay1rDGNN
a45zjnPOe857jXMMY0xrznsLY+tiD4SNc0hKaVLrYmlS50GJUolSB0JpUk1rbXPLWgxjbWuOc897
MIRxjHGMcoySjHKMcoySjHKMkozTlHKMEHwwhPB7rnPPc41rjmtNa45zZSkoQstaCEIoQuc5q1Lo
OatSSULnOYpK6DmKSmlKrnOqUgxjSUrnOY5zjnPve+97z3vPe897rnPPe+97EIQwhFGMEITve65z
rnMQhK9zcYSSjLOU05SzlLOUs5RyjDGEEHzwezGE9JzwexCE05wUpfSks5wQhOtailLve69zz3PP
c69zTWNNY25rDFstY01jTWNua01rLGsMYwxjTWttc65774POe41zTGtMazCMcZTOe1CMz3sLY+ti
LGvKWqpaC2MMY+tiC2NMa21rLGNRjM97rnOuc+97MIRxjJKMcoxRhHKMcYwQfDGEkoySjDCEMYQx
hM9zz3Ouc45rjnOuc2UpSUoMY6pSz3tJSopS6DnLUmlKyDlqUug5ilLHOShCKEJta2lKKEIQhO97
MYwwjO+DEITPe65zz3swhBCEEIQwhO978HvvexB88Hvwe1GEUYSylNOUcoxyjFGE73uuc45zz3sw
hLKUjnPve7OcspwUpZKUEITrWstaMISvc25rbmtua01rTWNta01jTWssYyxjbWttc21zbXNtc01r
LGuOc657bWttawxjDGOOcyxjrnN1rfOkspxxlO+DUIyRlHGUcZRxlHGUD4RQjE1rcYzve897jnOu
c897UYySjHKMkoxyjFGE8HvwexB88HvPc89zMYQxhBB88HuOa01rz3tlKUlKLWMJQglCxzmKSug5
q1JJQsc5akrHOYpS5zkIQghCLGNpSihCMIQQhDCMEITvg45zjnOOc65zEITPe897EIQwhBB8MYRR
hBB8EHwxhHKMspSSjBB8MYTwexCErnOOc897EISylI5zrnOSlJKU05xxlK5zy1rLWjCEjnNOa05r
bmtNY01jLWNta21rLGNNa21zjXNtc21zLGtNa01rbnNuc21rTWvrWk1rjnOOcyxjUYzvg857D4Rt
c21z74OOc45zjnMsa21zD4RNa+97jnOuc65zbWtta897MYSSjHKMEHwQfBB88HsQfDCEjmtua/B7
UYQxhBB8r3Nua897ZSlpSg1jCELoOcg5ikrnOctSSULHOUlK6DmrUghCCELnOQxjSUoIQs97z3vv
g657jnOue897z3uuc65zrnPPe897z3sQfDGEUYQxhBB8EHxRhFGEMYQQfBB8z3Ouc21rbWuuc897
spQsY21rcZTTnLOcMIyOc+taqlIwhI5zjnOOc25rTWtua21rbWtta01rTWtta21zjXNNayxrTWss
a21zbnNta01ry1pta45zjnOOc41zbXNtc21zTGtNayxrLGssawtjC2Msa41zjnOuc45zjnOuc45z
TWuOaxB8UYRyjDCEMYQQfBB8EHxRhM9zjmvPc3GMcYwxhM9zjnPPe4YxaUotYwhCCELHOYpS6DnL
UklCyDlqUsg5q1oIQghCKEIMYyhCCELPe+97z3uOc45zz3sQhBCEjnOOc+97z3vPe8978HvwexB8
8HvvezCEMITPc69zz3PPc89zrnOOc65z73vve5KULGNta1GMspySlFGMjnPLWqpScYyOc25rTWst
Yy1jbmstYyxjTWNta01rTWttc41zbXNtc41zTWuOc45zTWtta8tajnPPe897bWtta41zbXOue857
TWsMYyxrLGvrYo1zznvOe+97z3vPe+97EITPe65zz3sQhDGEUYQQfO978Hvwe+978HvPc45rjmvP
czGEEHzPc65z73umMSlCDGMpQghCxzmKSscxy1KKSug5y1roOatSCEIJQilKTWtJSuc5z3vve45z
bWtta21zjnOuc01rbWvPe65zz3uuc69zr3PPc89zEHxxhM9zjmuOa21rjnOOc45zznPvezCE73tx
jE1rjnNRjHGMcYwQhI5zilKqUlGMjnNua05rTWtNY21jDFsMW01jbmtua25rbXONc41zrnuue41z
jXNtc01rTWuqUm1rrnOuc21rLGNNa45zUYw0rTSl0pyynNKc86RUrROlMIwsYyxjLGMMY+tay1pt
azCE85xVrXWtVa0UpVSl85wUpTSlNKUwhG1rTWPPc/B7z3OOa/B7pzEpQgxjKUIpQghCqlLnOcta
aUroOctaxzmKUuhBKUJJSi1jSUrHOc97z3uOc21rbWtNa01rLGMsY01rjnNta01rbWtNY89zMYTT
lBSlNaX0nLKUkoxxjFGMkpRxjFCE73vPc+9705xNa21rEIRxjFGMMISOc8tay1owhG5zTWtua25r
TWMtYy1jDFstY01jjmtua01rbXNtc0xrbXONc41zrnttawxjilLrWk1rbWtNa01rTWuOc89zrnPv
e++D74Pvg857LGvOe857TWvveyxjqlJNa45zLGPve65zcYxxjHGMcYySlHGMEIQQhDCELGPPe01r
bWuvc89zz3MQfKcxSUIMYylCKUopQqtS5znLWklK6DnLWsg5q1LoQQlCSUoMYyhCxznve+97jnOO
c21rbWtta21rbWtta45zbWtNa01jbWuvcxB8t7WWtXa1drUUpTSlNKXznPOcspSSjCxjrnMQhNOc
TWttazCEUYxxjFGMjnPLWqpSUYzQe45zjnNNa01jbmuOay1jLWOOa45rbWtta21zbXNNayxrTGtt
c0xrLGPrWstaTWuOc65zbWssY45zrnMMY8c5SEqJUolSiVJpUqY5C2MPhG1rEISOc65zbWtNayxj
z3tJSqpSy1rrWixjjnOOc01r61pJSuc5z3uvc01jbWuvcxB8EHynMWpKLWMpQklKKUKqUuc561pJ
Sug5zFrHOatSxznHOSlC7FooQsc5z3sQhK5zjnNNa01rTWssY21rjnMsYyxjLGNNa21rz3PrWstS
yEGnOYc5ZjGmMec5y1IsYyxjy1JpSu97z3uSlCxjTGMQhDCEUYwQhG1rilKKUnGM8Huve25rbmtu
a69zz3Ouc45rEHyvc45rjXOuc657TGsMY0xrTWssY0xrTWvrWo5zjnOuc45zTWuucxCETWsoQsta
62JNa41zLGtISkxrMIwsY897rnOOc01rTWssY45zKEIMYyxjLGNtaxCEEISucyxjilIIQo5zrnOv
c25rr3PvexB8pjFqSi1jCELoOSlC61ooQk1raUroQcta6DmKUsc5pjEIQstaKELHOc97TWsMY21r
TWtNayxjLGNNawxjC2MsawtjC2Msa21zaUoDIQAQABgAGAAYABgAEKtSTWMtY6pSSULwexB8cYxt
a21rEHwwhDGEEHxta6pSilIwhK9zr3OOa/B7EHzPc89zr3Ouc89zjmuOa01rbnPQe25rjXNtayxj
C2MLY0xrC2Msa65zz3tta45zEIRRjI5zSUrrYixrTWttc01zSUqOa1GELGOOc45zTWtta21rLGNt
awdC61osY0xrjnPPexCEEISOc+taKEJta65zrnMsYwxbjnMwhIYxKEIMYyhCxzkIQgxbakpta2pK
6EGrUug5q1JpSo5zSUqqUihCpjGuc+tailJta21rbWtNa21rbWssYwxjLGvrYutiDGNMa4lSpjmi
KKMwgiiCKKMwwygMY25rLWNJQilC8Hvwe1GErnOOaxB8MYQQfPB7bWvLWqpSUYzPc69zr3NRhBSd
da1VpRSdNKU1pRSdNKUUpRSlk5TPe65zbGssY+taylosa8paTGvPe+9773sQhDCEMISOc0lKDGNM
a0xrbXNNayhKrnNxjE1rz3uuc65zjnNNayxjbWsHOqpSK2MsY01rTWvPe+97rnPrWghCTWtta45z
LGNpSm1z73tFKShC61qqUq5zqlIMW2pKDFspQug5ilLoOexaKEJpSuc5y1pJSsc573uylJKUz3vP
e45zbWtta01rLGNMa0xrLGsMYyxrbXOJUm1zjWuOa0xjTGOuc+5zrmtuYyxbSUIpQjCEEHwxhI5r
bWvwe1GEUYQQfI5zy1rLWnGMz3Ovc89zcYwQfBSd9JyzlJKMkoxyjDGE8HsxhI5zr3ONc0xrbWtM
YwtjLGuqWq57EIQwhFGMMIQQhDCErnNJSixrbXONc01ry1oIQm1rUIQsYzCEz3vPe45zDGMsY65z
5zmpUutayloMY01rjnNta45z61rnOSxjLGNtaxCEspQQhM97ZSlJSutaSUqqUmlKLWNJQuxaKULo
OYpS6DnsWghCxznHOctaSUqmMc97EIQQhK5zbWtta21rbWtMayxrLGssawxjLGssa41zaUrGOYIY
oyCjIKMggiCiKIMg6Elta0lCSUIQfPB7EHyOa45rMYRyjFGE8HuOa8tay1pyjM9zjmuvcxB8y1Ju
a69zjmtNY01jLWMMW+xay1opQm5rznuNc2xrTGssa21zC2Oue1GMcYwwhK97EIQQhK5zaUosa21z
jXPrYqpaCEJNa3GEDGPve897rnOucyxjDGOOc+c5yloLY8paC1ssY21rbWssY6pSxzksYwxjLGOO
c+97z3vPe4YxKELrWghCCEIpQuxaCDosY0lC5zlpSghC61rnOec55zmqUghCpjHPe897rnuOc01r
TWuNa41rjXOuc21rTGtMayxrTGvOe2lSJCkAICEoISghKAAoACAAGEQ5DGNpSklKUYTwezGErmuv
c1GMUYzwe897jnPLWstacYyvc25rr3MxhIpKTWPPc89zrnOuc45rjmtta+taKEJNaxB8jnNMa0xr
LGONc21rDWMRhDGEEISve65zz3uOc+c5SUqqUutaqlpJSoYxLGNxjCxj73uOc01rz3Ouc21rbWum
MYpSy1qqUqpSilKqUstay1oIQmUpTWtMYwxbTWOuc45rbWtlKUlK61ooQklKKELrWgg6TGNpSuc5
aUrnOctaCEIIQuc5ilLnOYYxjnPPe897jnuOc45zjXOOc857znuue21zTWssa0xrznsHQiQpQSBh
IGEgYSBBKEEgQBhEMctaSUpJSjCE73sQfK5rr3NRjDGMEITwe45zy1rLWnGMr3OOa69zMYRqSk1j
EHzwe69zr3Nua69zTWvLWihCLWMQfM9zLGssaytjjXNuc49z8Htuc01rbmtua65zy1qiEMMYBCFF
KUUpJCGCEChC73uuc3GMEIQxhM9zznNNY21rBCEEIWUppjGGMSQhJCEEIQQhohAEIW1rbWssY01j
znOOc01rRSkoQstaKEJJSihCDGMIQixjaUoIQopS5zkMY0lKSUrnOapSCEKGMW1rrnPvg/CDEITP
e++DrnvOg657rnttc0xrLGtNa857hTFlMSQxhjmnQadBKEqqWopS62LLWklKaUpQhM9zMYTPc69z
MYRRjDGEMYSOc8taqlIwhK9zjmvPczGEaUotYxB8EHzwe69zjmuOayxj61ooQutar3Ouc01rC2ON
a+97bmvwe45zLWMtYwxj61pNayxjilJpSihCSUqKUqpSilIsYxCETWvrWutaLGOOa21rbWttayhC
SUqqUopSSUooQklKilJpSklKilKuc41rTWNta89zjnNNayQhCELLWihCSUpJSgxjCEIsY2lKCELL
WghCLGNpSklKKEKqUuc5ZSmOc897z3swjDCMUYzvg457jnvOg657jXOue41zTGuue8Y5BCElIUUp
ZSlFKQg6y1IsYy1jy1ooQmlKUYzPc3KMz3OvczGEcoxyjDGErnPrWstaUYzPc45rr3MQfElC7Fqv
c89zr3PPc65zbmsMY8taCEIMY45rjmvrYutibWsPhG5rr3NNawxj7FrsYstaTWuOc65zrnNtayxj
bWvPexCEjnOOc45zTWtNa45z73vve85zLGPrWk1rbWtNa01rrnOue65zz3uOc65zjnOOa45rjmuO
a21rbWtFKQhCqlIIQihCaUosYwhCTWOKUghCy1ooQixjaUppSklKqlLHOYYxjnOuc+97MIQPjFCU
74POe++Dz3uOc45zrnttc25zz3tNa21rjnOuc+97D3zKWolS61rrWstaSUppSlGMr3MxhK9zrnMx
hJKMMIQwhK9zy1LLUnGMz3OOa69zEHxJQuxabmuOa45zjnNtawxjqlKKUqYx61qOc21r61oLW01j
rnNua21rDFsMWwxbLWPrWutay1rLWgxjDGPrWutaDWMNYy1j7FrrWgxjLGPrWk1jbWttayxj61rr
Wuta61rLWgtjC2PrYutaDGMMY01rjnOuc45zjnOOa65rZikpQstSKEIpQmpKLWMIQi1jikoIQsta
CEIsY0lKKEIoQutaKEKmMW1ry1rrWs5774NQlFCMMIzvg897TWttc21zbXvPg897bWvPc897EIRx
jJKUiVKGOedBqlrrWklKKEIQhK9zEHyOa25rEHwxhM9zz3Ovc+tay1JRhK9zjmuuc/B7CDqKSk1j
TWMtY+taqlKKUklKCEIEIctajnNNawxjLGOOa25ry1LsWuxa7FoMWwxbDGMMY8tay1osYyxjLGMs
Yy1jDGMMYwxjy1rrWixjDGNNY21rrnOOa21rTWtNayxj62LrYutiLGssYyxjLGNta45zrnNNa6pS
TWOOa2YpSULrWgg66DlJQixjCEItY6pSCEKqUsc5LGMIQoYx5znrWklKxzmOcyxjLGPve657zoNQ
jA+EbXONc25rbWuNe86DjntNayxjLGNta89773sPfChKIRhBGGlSy1ooQihCz3tNYzGEjmtNY/B7
EHwQfPB7r3PsWqtSEHyOa25rrnPwe6cxJSGnMQg6KEKmMWUpRSkEIcMYghDrWq5zjnNNa01rjmst
Y4pKTWNuay1jLFsMWyxjTWvrWstaLGMsY01rLWMtYy1jLWNNa01rLGMMYyxjz3NNY45rrnNta45z
jnMMY8paqlrrYgtjDGMMY01rbWtta01rTWssY01jjmuGKSlCy1LoOYYpCDotY+g5TWOqUihCqlLH
OSxjy1pNa4pS61pJSsY5rnNyjNOc73vOg+6DEIzPe21zbXNta21rrXsPjK97LGMsYyxbLGNta21r
z3soSkEgYiBpUstaKEJpSu97DFsQfI5rTWMQfPB7z3PPc45ry1KKShB8jmtua45rr3OqUmpKy1IM
W+tay1qqUmlKaUqKUmlKTWvvexCEjnNNa45rbmurUm5rbmstYy1jLGMsY01ry1qKUk1rjnNua01r
TmtNa01rbmtta21rLGMMW65zbWtNY45rbWtNa01r61rKWolSaVKqWixjDGMsY01rbWtNaxCEFKXw
e89zpjEpQstSq1ItY4pKbWsIQgxjikpJQstS6DktY4pSDGOKUi1jSErGOY5zjnOvc697r3sQhM97
rnNta21zbnNtc45z74OOcy1rLWMMYwxbLWMtY69zCEpBIGIgiVrLWghCSUrPeyxj73tNa01jEHzP
c65zrnOOa8tSikoxhK9zbWtNY69zz3PPc+978HsxhPB7z3OOa45zbWtNa01rz3vPc69zjmuOa69z
y1ItY01jTWMtY01rTWtNa6pSaUpua/B7z3OOa25rjnNuc05rbmuvc01jDFtNY01jTWNNYyxj61rL
WstaqlpIUuZBSFLrWgxjLGNNa21rbWvPe+97z3MQfKcxaUoMW4pK7FqKSm5rKUIMW4pKSULrWgg6
TWMpQghCKUoMYyhCxjmOc05rbmtOa01rr3OOc45zrnOuc45zjnOOc897r3NNay1jDGMtY01jTWOv
c0hCKEqKWutay1rnOQhCrnNtazCEDGPLWs9zz3OOa21rTWOKSmpK73uOa01jLWMxhPScFJ30nPOc
NaUUnbOUcoxxjDCEEIQQhPB7bmvvc89zjmuOa+xaTWNta45rbmtua21rbWvLWutaMYRRhDGEMYQQ
hBCE8HuOc69zr3Nua25rbmtNY01jTWMMYwxj61qqUolSKEoHSihK61osYyxjbWuOc897z3vPe45r
8HvHMUlCDFspQug5KUJNY0lCTWOKSilC61opQk1jKUIpSklKDGNIQqYxbWsNYwxj7FrsWi1jbWtt
a65z73uue25zjnOue05rDGPsWk1rbWtNYy1jjmtIQoY5B0qqUspSKEIoQq5zTWsQhMtailJua69z
bmtNY+xaikqrUs9zbmtNY01jz3MQfJKMkoxyjLOUs5SSjJKMMIQQhBCEz3uvc45rr3PPc45rbmvs
Wo5rjmtta01jTWNta45zEIQUpfi9973Xtde1l7WWtZKUz3vPc69zjmtua65zTWNNY69zcowUpRSl
05zzpPOkE6XTnNOc85xRjM97jnPve+97z3uOa45rhilqSgxbSUIIOgg6DFtJQk1jikopQutaKEIt
YylCKUpJSgxjaUqmMW5rbmtua01rLWNua897z3vPe+9773sQhM97z3vPe05r7Fpua01jbmtNY01j
CEoAECAYiVLrWihCSUrPe01rz3vLWmlKLGOOa25rLWPrWklCq1IQfK5zjmuOa01jxzGrUstS7FpN
Y01jLWNNY+taqlJJSuc5LWNua25rjmuOa45rDFuOa45rTWNNY25rTWuOc897LWNua25rjmstYy1j
LWsta69zz3Ovc65zjmuvc45rjmuOa+taLGMsYwxj61oMWwxbqlJpSmlKy1pNa21rz3vPe+97rnOO
a4YxikpNY2pKSUJJQm1rSULsWmpKKULMWghC7FooQilCaUosY0lCxzGvc25rjmtua01jbWuOc897
z3vwe/B7MITwe89zz3NNY8tSTWNNY25rTWuOa2lKYSBhIKpSDFtJSmlK73tNa45zqlJpSutaLGMs
Y8pSqlIoQmlKMISuc25rjmuOa2pKLWNNY25rr3Ovc45rbmstY+xaakqnMS1jjmtNY21rjmuvcwxb
bmuOa45rTWNNY01rbnNua4pSDWMtYwxj7FrsWopKy1LPc89zr3Ovc/B7EHyvc69zz3NpSopSy1rr
Wgxb7FrLUstSqlIIQghCbWtta65zrnPPe45zrnumOWlKTGNpSklCSUKvc2pS7FpKSglCi1JJSgxj
CDopQmpKLWNqSqcxr3Nua45rjmtNYy1jLWOOc69zr3sQfDB8EHzPc45rLWOrUk1jjmtua01jr3OK
SoEgoiDrWixjaUppSs97LGNtawhCRSnGOQc6B0LGOaYxRSkoQs97bmuuc45rbWurUm1rjmvPc+97
z3OOa45rTWMtY4pKxzFNY25rbmuOa69zz3MMW25rjmuOa25rbWtua05rDGNKSi1jTmtNay1jDGOK
SqtSz3Ovc89z8Hvwe1GE8Huvc+97qlLLWgxjTWNNY45rjmuOa01rilIoQm1rjnOuc45zjnOOc897
pzlJSixjaUoIQghCTWuKUk1rakoIQklKSUotYylCSUJJQi1jakqnMc9zy1KKSm5rLWNNY25rbmuO
c69zEHwwfPB78Huvcy1jy1JNY25rbWtua/B7zFoLY0xrLWssY4pSaUqOcwxjTWumMSQh5BjkGCUh
oxiCEIIQpjHPe45rr3Ovc01jikpua45rrnPwe69zbmuOa01jLWOKSqcxbmuOa45rbmuuc+97DFuO
a45rbmuOa45rjnNuawxji1JNa25rbnNOay1jy1LLUo5rr3PPc69zz3Pwe/B78Huvc4pSqlIMYyxj
TWOOa65zbmtNa8taKEKOc65zbWvLWihCTWvPe8c5SUosa2lKKEooSgtjaUpNa2lKKUJqSilCDGMp
QklCSUJNY4pKxzGvc89zr3OOay1jLWNRjBWldq2WtRSdUYTwe65zjmstY8tSLWNta25rjmsQfKxa
5zkoQg1jTWuKUklKjnNNa41raUppSilC6DlJSuhBCEIIQihCrnOvc69z8Htua4pKbmuvc/B7EHzP
c45rbmtNYwxbikqnMY5rz3OOa25rr3Pvey1jjmtua45rjmuOa45zjnNNa6tSbmuOc25rLWMMY6pS
y1Kuc45rz3OOa01jEHwQfK9zr3OrUsta61oMWwxbbWNta01jLGPLWihCz3uuc21rrnPPe897rnvH
OWpSbXNJUghCSVJMa2lKbWtpSghCSkpKSi1jKELHMShCTWOKSqcxz3MxhJKMjmttY21rDFurUm5r
bmtNY1GEjmtNY25rTWPrWk1jjmtua45rz3MIQgAYYijrYstSxzFJQq9zjmuOa45rbWtuay1jbmvP
c89zz3PPc89zrnOOaxB8jmuKSk1jjmsQfBB88Huvc01jLGPsWopKhimuc89zrnNuay1jjmstY89z
jmuOa69zjmtua65zbWvLWm1rjnNta01r61qqUqpSjmuOa21rbWttazCE73uOa89zy1LLUuxa7Fos
Yy1jLGMMW8tailIoQq5zz3uuezCMcZSNc657xjmJUixrKErGOWlSbWuKUo5zilIpQopSSUoMY+xa
jmvLUi1jikrHMe97r3Ovc45rjmvPc+ta6DmrUopKakrwey1jLWNua01jDFtNY25rjmuOa45rRCmj
KEU5hTEEGaMQpjHwe65zjmtNY01jjmtNY21rjmuvc+97z3Pwe65zr3MQfI5rikotY21r73sQfPB7
z3Nuay1j7FqKSqYxrnPPc45rLWPsWm1r7Fqvc65zjmuOa45rbWuuc21ry1pta21rbWssYwxjilLL
Wq5zrnOuc21rrnPPc/B7z3PPc8tSDFsMWy1jLWMsYwxb7FrLWmlKCEJtc657rnvPe657jXPvg6Y5
aVIsY+tibXPrYk1ry1quc4pSKUqrWklK7FqKSgxbakpNY6tSxznPc89zz3Ouc65zz3MMW0lCLWPL
UmpKjmssYwxbTWNNYwxbjmuOa25rbmuvc8tSKUJqUmpKikqKSqtSr3OOa45rbmtua45rLWNua45r
r3Pwe/B78HvPc89zMISOa6pSLFtua/B7EHwQfM9zrnMtY+taakrHMY5rz3Ovc25rTWOOa+xajmuO
a25rbmuOa45zjnNta4pSTWtta01rDGMMY2lKqlKuc85zrnOOa65zr3Pwe89zjmuKSi1jTWNta01j
TWNNYy1j61ppSuc5bWttc21zjXNtc657MIzHOWlSbXNpUqpaSUosY8tarnOKUilCq1opSgxjKULo
OShCbWuKSscxrnOvc89zz3PPc89zDFtqSk1jq1JqSs9zLWNNY45rTWPsWo5rjmuOa45rbmuvc89z
EHQxhBB88Huvc65zr3Ovc25rTWOOayxjbWuuc45rr3Pve69zr3OOa+97rnOrUk1jTWOOa89zz3Ov
c45rDFurUmpK6DnPc89zz3OOa01jjmstY45rr3OOa45rbWuOa65zTWNpSm1rjnNtayxjDGNJSqpS
jnOuc+97z3Pve89zr3Ovc89zakoMW01jbmuOa25rTWMtY+xailIIQkxrLGssayxrTGttc657xjmJ
Um1zKErGOShKTWuKUm1rilJJQstSSUJNY0lCCDopQk1jakrHMY5rrnOvc45rz3Pwe+xaikpNY6tS
aUoQfE1jTWOOa01j61qOa65zjmuOa45rbmuvc45rjmvPc/B7z3Ovc89zr3NNYy1jjmtNY45rr3Ou
c69z8HvPc89zrnPwe89zq1JNay1jjnMQfM9zz3Oucy1j7FppSscxz3PPc89zjmuOa89zbWvve89z
z3OOa45rrnPPc21rakotY01rbmstY+xaakrLUq5zz3Pve/B78Hvve89zz3PPc4pKDFtNY25rbmuO
a25rTWPrWklKxzkMYyxr62ILY+tiDGOue6Y5ilJMawhCCEIoSgxjKEJNa6pSakrLUklKjmuKSmlK
aUpNY4pK5znPc69zz3Ovc89zz3PsWopKbmsMW2pK8Htua45rr3NNY8tSz3Pwe45rz3PPc45rjmuO
a25rjmvPc/B7z3Pwe+97r3Ovc45rTWOvc89zz3PPc/B773vwexB8MYTPc8taTWstY45z8HvPc69z
jmsMW+xaakrnOc9zz3Pve89zz3Pwe25rr3Ovc/B7r3PPc69zjmtNY4pK7FotY25rDWPrWopKy1Kv
c89zz3PPc69z8Huvc89zz3NqSuxabWtua21rbWuOa01jqlIIQoYx61oMY+ti62LrYutibXOmOWlS
TGtISihKSEoMYwhCTWuqUklC7FpJQo5rqlKKSopKjmurUug58HstYy1jz3Pve89zDFuKSm1rLWOr
UvB7DFtNY45rbmtNY25rjmuvc45rbWstYy1jLGMMWwxbLWNNY25rbmuuc69zbmsMWy1jz3PPc69z
z3PwexB8EHyOa1GE8HsMY05rTWuvc+97z3Ovc45rLGMMW2pKxzHPc+97EHzwexB8z3MtY69zr3Pv
e89zz3Ovc45rLWNJQuxaDGNNawxj7FpqSqtSrnOOa69zrnNuaxB8z3PPc89zikotY25rjmtua01j
bmtNY8taCEKGMQtjLGvrYqpaiVILY21zhjGJUm1zSEpISolSTWsIQm1rqlJJQuxaCDpua8tSq1Kr
Us9zy1LHMfB7DFurUhB88HvPcwxbikotY+xaikoQfI5rjmtua45rr3Ouc89zz3PPc65zTWNua25r
jmuOa45rjmuuc45rjmvPc89zz3OOa89zz3Ovc69z8HswhBB873uSjPB77FqOc45zr3PPc21rLWMt
Ywxb7FqKSgg6r3Pwe/B7z3PPc89zTWPPc/B7EHwQfO97r3PPcy1jakrsWuxa7FoMW+xaSUKKSm5r
bmuuc45rr3MQfPB78Hvwe4pKLWNua25rjmuuc65zbWsMY2lKpjEMYwtjLGvKWkhK62JMa6Y5iVJN
a0lKiVLKWm1rCEJta6pSakrsWgg6TWOqUopKikrvc+xa6DkQfLOU05QxhM9zz3NNY6pSTWPLUuta
9Jz0nLKUcoxRhFGEcoySjHKMs5SSjDGEMYRRhFGEMYRRhFGEUYRRjHKMcowQhM9zr3Ovc89zz3Ov
c89zEHwQfHGMs5TPc8tar3PPe/B7rnNNa+tay1rrWutailIIQo9z0HMRfPB78Hvwe45rz3OvcxB8
8HuOa65zr3MMWylC7FrsWutaDFsMWyhCaUpNa45zz3uOc21r8HswfM9zz3OrUi1jrnNua45rz3Ov
c01jDGOrUug5DGNNayxrz3uRlI5zjnPHOapSjnNpSklKilIsY+c5LGOKUmlKy1rHMS1jSULHMSlC
z3PsWgg6UYRRhHKMEHzPc89zLWOrUm5rDFuucxSds5SzlPB7jmvznFGEEHzTlNOUs5SzlLOUs5Sy
lJKMkoySjLOU05yzlLOcr3Nta69zr3Pwe7OUlq33vfe917XXtRi+kozsWo5zj3Pwe+97bWsMY+ta
y1rrWopSCEKOa69zEXwxhPB7EHyOaxB8EHwxhM9zr3Ovc69zDFtJQstS7FoMWwxbDGNpSklKbWvP
e897jnNuaxCEz3OOa89zy1ItY65zjmtta45rjmtNYwxjq1LoOQxjjnOOc65zrnOuc+975znLWo5z
KEKmMUlKTWvnOSxjilJJQstSpzHsWstSq1KKSq9zy1IIOlGEMYRRhBB873vvey1jy1Jua01jjmtu
awxbDFsoQstSFJ3veyhCTWMtYwxbDFstYwxbDFsMW+xaDFsMW+xay1qKUghCTWsQfO978HvsWq5z
rnPPcxB88HsxhDCEjnOvc45zr3Pve45zTWssYwxj61qKUuc5TWOOa65zEHzPcxB8TWPve/B7MYQw
hM9zbmtua+xaSULsWgxbDFsMW8taaUqKUm1rrnOOc45zjnPwe65rbmvwe+taDFtua25rjmvPc69z
bmsMY4pSxzlNa45zz3vve897EIQQhOc5qlKOc8taqlKqUm1r5zksY4pSakrsWscxDFsMW/B77FqO
a6tSKUIxhDGEUYQQfM9z8HstY6tSjmtNY01jTWNNY01jKEKKSnKMbmtJQi1jLGMtYwxbLWNNYyxj
LWMMWwxbDFvsWuxay1opQo5rEHwQfM9zaUoMW+xaDFssYyxjLWNua45zjnOPc25r73vPe21rLGMM
Y6pSilIIQo1rjWtMY21jz3Pwe45rz3PPc89zEHyvc01jTWMMW2pK7FoMWwxbDFvLWghCikpta65z
z3tta8978HuOa45rEHwMWwxbTWNNY65zz3Ovc65zbmvLWsg5bmvPexCEUYxRjDCEMIQIQstajnMs
Y897y1osY+c5bWuqUmlKqlLnOSxjikqqUqpSr3PLUgg6koxyjFGEEHzPc89zLGOKSo5rbmuOa25r
bmtua6pSy1oxhG5rikpua01jjmtta25rjmtua45rTWNNY01jDGPsWqtS6Dlua89zMITve2pKTWMM
Wy1jLWNNY01jbWtta21rbmuuc+9773ssYyxjDGPrWopSKEIQfPB7z3PPczGEEHyOa/B7EHwQfPB7
z3Nua01j7FpJQgxbLWMtYwxby1IIOopKjmvwexB88HsxhBB8z3OvcxB8DFtNYy1jTWOvc65zz3PP
c45rDFvoOa5zMYRyjHKMUYTwexCE5znLWo5zqlKqUmlK61rnOU1rilJJQopKxzEMW4pKSUJJQm5r
q1LoOTGEUYQxhPB78HsQfC1jikqvc25rbmuOa45rbmvLWk1rUoyPc4pKjmuOa25rbmuOa25rjmuu
c01jbWNta+xi7GKLUocxjmtRhDGEz3NqSi1jLWNNY21rTWOOa25rTWtta01rrnNta21r61qqUopS
qlJpSsc5z3MQfFGEMITwezGErnPwe1GEEHzwe69zbmssY4pKSULrWuxa7FoMW+xaakqqUo5rr3Pw
e89z73sQfBB8z3Pwe+taLWNNY21rrnOvc69zr3OOawxbCDrPc1GEcoySjDGE8HsQhOc5qlJta4pS
ilJpSgxjCEIMY4pSSUKqUscxy1JJQklCSUIMW4pK6DkQfI5rbmvPc89zMIRNY+taz3Ovc45rjmuO
a45ry1quc7Ocr3NqSq5zjmtta25rTWNNY01jrnNta01jbmsMYy1jy1qrUvSc+L34vXatLWMtY01j
bWtNY25rjmuOa45zrnNta897TWsMY8tailJpSqpSSUqGMY5rjmvPcxB873NRhI5rr3MQfFGEMYSv
c21rLWPLUmpKDFsMWwxbTWMMW4pKy1KOa21rr3Ovc69zz3PwexB88HurUi1jbmtua69zr3PPc89z
jmsMWwg68HtRhHGMEHyOa65zMIQIQopSLGNpSmlKaUoMYwhCDGNpSmpKq1KGKctSSUIpQihC7FpJ
Qug5EHwMW8tSz3PwexB8TWPsWs9zz3PPc45rjmtua+tajnPTnNB7ilKOa45rjmuOa25rLWNta21r
jmuOa01jLWtNa01rbmtua/B773tNYyxjTWMtY01jbmuOa45rbWuOc65zTWtta21rDGMMY6pSilJp
SihCpjFNY21jjmvwexB88HstY65zEHwQfPB7jmtua45rLWOKSi1jDFsMW01jLGOqUstSbmtNY45r
z3PPczGEEHwxhDGEy1IMW21rbmvPc89zr3Ovc45rDFvHMa9zUYRRhK9zq1KucxB85zlJSgxjaUqK
UopSDGMIQi1jSUpqSqtSZinLUmpKSUIoQgxbSULoOTGE9JzTlBB8z3Pwe45rDFvve+978HuOa25r
jmvsWq5z05Twe6tSr3Ovc25rbmuvc01jTWNta25rr3NNY01jTWNua01jy1JNY01jCDqKSi1jTWOO
a45rrnOuc21rrnOuc45zbWtNa+ta61qqUqpSaUrnOYYxDGNta65zrnOuc45rLGOuc89zr3PPc69z
jmuucyxjSUIsYwxbDFttay1jikqKSm1rbWtua45rrnNRhDCEEHxyjC1jLWNta45rr3PPc65zjmtu
a+xaxzGOazCEEHyylBSdMYTve6cxCDrrWihCaUqKUuxapzkta0lKaUqrUqYx7FqKSmpKSUIMW0lC
CDoxhBB8MYTwe89zz3Nta+xar3Ovc89zr3PPc89zLWOOa5KMz3OrUs9zjmuOa45rrnOOa21rjmtt
a01jbmtuay1jTWNNYwxbjmtNYylCy1JNY45rjmuOc45zrnNta21rbWtta65zTWssY+tailKKUmlK
xzllKQxjLGNta65zz3OucwxbjmuOa69zz3PPc45rbmstY2pK7FosY01jjmstY4pKikqOa25rbmtN
Y21rEHzwe/B7MYQtY01jTWNua65zrnOvc69zjmsMW+g5r3MQfBB8UYSSjDGEEHzHMSlCDGNISopS
ilLsWsg5LWNKSmpKy1KnMQxbikpqSklCTWMpQghCMYQQfM9zrnOOa89zLWMMW/B7jmuuc69zbmtt
a8tSjmuSjO97ikrPc45rjmuOa45rjmuOa45rjmstY01jjmuOa21rTWPsWs9zjmtpSgxbTWOOa45r
rnOuc45zbWtta21rTWvrWgxj61rLWqpSaUpJSuc5pjEsYyxjbWvvezCE73ssY41rr3PwexB8z3OO
a45rTWNpSgxbLWNua65zbmurUstSr3Ovc25rLWNta/B7EHzPcxB8LWMtY21rz3Ovc25rjmuOa69z
LGMIOq9zEHxxjDGEMYQQfBB8xzEpQixjSUpJSmlK7GLoOS1jSUqKSstShinsWklCxzFJQi1jSUIp
QnKMMYQQfPB7rnPPcwxbDFvPc25rbmtua25rTWOKSo5r05TPc6pSz3OOa45rjmtua25rr3Ouc45r
TWNNY45rr3Nta01jDFvPc89zy1ItY01jbmuOa25rz3uOc45zbWtNawxjLGNNawxjy1rrWqpSSUoI
QoYxLGMMY21zMIRxjDCEbWvPexCEEHwQfM9zbmuvc45rikpNY01jbmuOa25rikrrWs9zz3Ovc25r
TWMQfDGEz3PvewxbDFtNY89zz3OOa45rjmuOa01jKUIQfHGMcoxRhDCE8Hvwe8cxKUJNa0hCxzFJ
SuxapzHsWklKakqrUqcx61rLWm1rqlLrWgg6SUKSjHGMMYQQfK5zz3PrWug5LWNNY01jrnOuc25r
akquc9OUz3OrUs9zr3OOa45rbmtNY45rz3OOa25rTWNNa45zbWtNa+xiz3vvg6paDGMsYyxjbWtt
a65zbWtNa45zrnNta01rbWtNawxjDGMMY4pSCEKGMSxjC2Ntcw+EEIQwhG1rz3vwe/CDEITPe69z
z3OOa2pKTWNNY01jjmtua4pKy1KOa45rrnOOa01j8Hvwe69z73vLUi1jbWuvc/B78Hvwe89zr3Nt
ayhCz3NyjLKUkoxyjDGEEHzoOSlCLWPsWo5r7FrsWqcxDFtqSmpKikqnMexailIMY0lK61ooQilC
MYRxjHKMEHyuc45rjmunMYYp7FpNY89zEHyvc4pKrnOzlM9zqlKuc69zz3OOa25rTWOOa45rbmtu
a25rTWtta21rbWsta++D8IPLWi1rDGMMYyxjbWuOcyxjTWuOc65zbWuOc65zTWssY+tay1ppSuc5
hjHrYgtjLGuNc+97z3ssYxCEz3sQhDGEEITPc/B7jmuKSk1jbWtua45rbWurUqtSr3Ovc65zjmtu
a+97z3Pwe1GEDFtNY45rz3MQfBB8MYQQfM9zLWMIOq9zMYSSjNOUkowQfBB86DkoQi1jy1Jta8tS
DFunMexaSUJJQopKpzEMW0lCCEIoQgxjSUIIOhB8z3PwexB8r3Ouc69zbmuGKWYpy1KOazGEEHzL
Uq9z05QQfKtSr3PPc89zz3OOa25rTWNNY01jbWtua21rjnNNawxjjnNRjFGMy1osYwxjDGMsY21r
bWsMY01rbWtNa01rbWuOc21rTWsMY8taSUoIQqYxDGMLYyxjbXOuc21rDGPvexGE8HvQezGEEHzw
e25rikotY01jr3OOa21rq1KKSs9zEHzPc69zjmvPc69zz3MQfAxbTWPPc/B7EHzwexB8EHzve01j
5zluaxB8UYQxhM9z73sQfOg5SUIMW2pKSUKKSgxbpzHsWklCSUKKSqcxTWOqUmlKSUrsWihCCDrv
e8tSqlLPc89zjmuOa69zTWNFIYYpLWPwe/B77FrPc9OUEHzLUq9zr3PPc69zz3Nua25rTWNNY25r
bmuuc897jnMta21zUYwxjOtabWtNawxjLGMMYwxjDGNta21rTWuuc45zbWtNawxjDGOqUopSKELH
OSxrC2MMY41zjnPPeyxjrnMQhPB7z3sRhBB8z3Nua2pKDFtua69zjmtua8tSy1Lwe89z8HswhI5r
z3OOa69zr3PLUk1jr3PPc/B7EHwQfBB8z3Nta+g5jmvwexB8bmtqSo5rEHzoOWpKLWNJQklCikpN
Y8cx7FpJQopKDFsIOm5rikpqSmlKy1LoOSlCEHxRhHKM8HvPc89zr3PPc89zTWNFIQg6bmvve+xa
jmuSjM9zikpua45rz3NNY25rr3Nua25rbmtua45z8HsxhBCEjnNNczCM74PLWo5zbWttayxjLGMM
YwxjTWssY01rbXOOc897jnMMYwxjy1qKUihCxzltcyxrLGttc65zEIRNa89773sQhBCEEIQQfM9z
TWOKSixjTWNua25rTWPLUgxbEHzwe89zz3OOa89zbmuOa89zy1IMW69zMIQxhDGE8Hvwe69zLWPo
OW1r8Huvc3GMkozwe/B7xzEpQgxbSUJqSopKLWMIOgxbikqrUgxbKUJua4pKikppSgxbKUIIOlGE
coxxjBB88HvPc89z8HvwexB8LWMlIUlCjmvLUk1j05TPc2pKLWNNY01jbWuOa45rbWstYwxj7FoM
Y05rj3NuawxjLWsxjO+DilKOc45zbWtNawxj61rrWk1rbXOOcyxrjnMQhK5zLGPLWutaqlIIQqYx
bXMsayxrbXOucxB8bWvPe897EIQQhO97z3Pwe25rikpNY25rTWNNYyxjqlIMWxB88HsQfO97z3PP
c45rbmvwe+taDFuOaxB8MYQxhPB78Huvcy1jCDpuaxB88HsxhHGM8Hvwe6cxCDoMW2pKaUqKSk1j
SUJNY6tSq1ItYwg6bmurUmpKakotYwg6KUIxhBB8EHwQfBB88HsQfPB7MIQxhDGE7FolIYpKikpN
YxSdEHyKSu978HuOa69z73uvc69zbnNua01rTmuvc69zj3Nua657cZQQhIpSLGNNa01rbWtNawxj
61pta45zjnMta21z73ttayxjy1qqUopSKELHOSxrLGtNc21zz3swhI5zEIQQhO97MITPe89zz3NN
Y2pKLGMtY01jTWMMW4pK7Frwe89zz3PPc69zz3Ovc45rz3OrUgxbjmsQfBB88HsQfM9zjmvsWscx
TWMQfFGEEHzPc89z73vHMccxDFtqSmpKqlJuaylCbmvLUopKLWMIOm5rikpJQmlKDFsIOilCMYQQ
fDCE8HsQfDCEEHwQfPB7UYRyjFGEq1JFIacxbmv0nBB87FoxhBB8r3Ovc69zr3PPc45zbnNuc25r
TmuOc897bmtNazCMz3tJSstaDGMsY45zjnNta01rjnNtc45zbXMta45zjmtNa01r61ppUihC5zks
awxjTXONc897UYzPe3GMUYwwhFGM73vPe45ry1LoOctS7FosY01jTWOKSstSz3Pwe/B7r3Ouc69z
jmtua65zikotY45rr3Pwe89zr3Nuay1jy1LHMQxbr3Pwe+97z3Ovc69zpzHoOexaakqKSopKbmsp
Qm1ry1JpSgxjCDoMYwhCpjEIQutaCDoIOlGEMYQwhBB8MITwexB8UYQQfDGEcoxRhFGEikqjEE1j
FJ3wewxbMYQxhM9zz3Ovc89z8Huvc45zz3uvc45zjnOvc21rbWtRjM97KEIIQihC61pta21rbWtN
ayxjLGtNa01ry1pNa21zLGMMY8taiVLnQedBTGsrY41zrnPPe1GM73txjJKUcYxxjBCE73vPe+ta
5zmqUutaLWNta21ry1LsWu978Hvwe65zbmvPc45rbmvPc4pKDFtta45rz3MwhPB7r3MtY6tSpzEs
Y45rjmvPc65zr3PPc6cx6DnsWilC6DlJQk1jSUJua8tSikpNYylCDFsIQsc5KEIsYylCKUJyjDGE
MIQxhDCEMIQwhFGEUYRRhHKMspRyjFGExzHoObOUMIQMW1GEMITwe89zr3PPc/B7z3uvc897z3uu
c65zrnNta21rUYzve+c55zkIQopS61osY01rDGPLWstaDGMMY6pa62Isa+tiilJpUmlSZTGFMW1r
TGtta65zz3swhO97MISSlHGMUYxRjFGMMIRNa+c5ilKqUgxjLWNNY8tS7FrPc+978Huvc25rz3OO
a25rrnNpSstSbWuOaxB8MYQQfM9zTWOrUscxDFuOa69z8Huvc89zz3OnMSlC7FopQuc5KUItY0lC
bmurUmpKDFsoQixjy1quc+taTWtJQklCMYQxhDGEMYQxhFGEcYxyjJKMcoySjLOUkoySjDCEZimK
ShB8DFtxjBB8z3Ovc89zr3PPc897z3vPe/B7z3vPe897jnMsY1GMEIRpSm1r73tRjHGMkpSylJKU
kpRxlFGMMYyve++DUYwQhBCErnuNc2lS5zmNc21rjXPOe+97MISOczCEUYxxjFGMcYwQhM97LGMo
QopSaUqKUutaDFurUstSz3Ovc89zr3Nua/B7r3Nua25rSULLUk1jbmuOa89zz3Ouc01jy1KnMS1j
jmvPc/B7r3Ovc89zpzEoQuxa7FrPc6pS7FopQk1jikopQopKKEJNa4pS61rrWixjaUpJQhB8DFvs
WhB8MYRxjFGEUYSSjHKMkoySjHKMkoySjPB7JCEIOuxa8HvPc/B7EHzwexB88HvwexCEz3uvexB8
z3uOc01rLGNRjBCEilJNa897z3vvezCEMIRRjDCEMYxRjM97TWttc6578IPPe41zLGtpUihK73tt
a65zznvvezCErnMwhBCEUYySlJKUMISOc+ta5zmKUopSaVLLWgxbikrsWhB8z3Ovc89zTWPwe89z
jmuvc4pK7FpNY45rbmuOa89zz3NNY6tSpzFNY65zr3MsY2pKbmvPc6cxKELsWmpKq1JqSgxbKEJN
Y4pKKEKKUghCLGNpSopSy1osY0lKSUoxhBCEEHxRhPB78HsQfBB8MYRyjJKMUYRRhDGEz3MQfC1j
oxDnOY5rz3PwexB8MIQQfDGEEHzwe69zz3NRhDGEr3Nua01rUYwQhMtabWuuc65zz3sQhFGMUYwx
jBCEMIzPewxj74POe1GM74ONc21zylooSvB7jnOOc89zEHxRhO9zcYyRjJKUkpRRjBCErnPrWsc5
iVLKWolSylosY4pK61rve+97z3PPc45rEHzwe89zEHzLUgxbjmtua45rjmuOa89zbmvLUscxbmvv
e89z8Hvwe89zz3OnMeg5y1JpSklCSUIsY0lKLGNpSghCaUoIQgxjSUpJSopSLGNJSklKUYz0nPSc
cowQfDGEkoySjHKMkozTlNOUcoxxjM9z8HsxhAxbZSmrUs9zz3PvexB8EHwQfPB7EHzve89z8HsQ
fBB8EHyuc3GMz3vLWo5zjnNNa45zz3sQhDCMMIzvg897r3uOc857z3txlK57jnMsa4lSB0KOc45z
znvvgw98cYzve1GEkZRxjBCEEIQQhK5z61oIQqpaqlqqWutiTGPLUuta73vwe+97jmuOa/B7z3Pw
e/B7qlIsY45rbWtua45rTWNNY01jy1LHMU1j8Hvwe1GEs5Twe89zhinoOctSSUJJQklKLGNJSuta
CEIoQklKKEJNa2lKaUqKUo5zilJJSlGMUoxyjFGEEHwxhJKMkoySjHKMs5TTlLOUkozTlHKM73uS
jMtSZinsWq9zz3PPc/B7EHzwexB8EHwQfPB7EHwQfBB8jnOylM97qlJNa21rTWuNa697z3vPexCE
EISOc657rntMa++DMIzOe21zDGOqWsY5bXONc41z7oMPhFCMznsvhBCEEISuc65zjnOOc+taCEKq
Wqpay1rrYgxbylKqUs9z8Hvwe65zjmvwe89zz3Pwe+taTWOOa25rbWOOa21rLWMtY8tSpzHsWq9z
8HsQfM9zr3Pve4YpKELLUilCKUJJQuxaKELLWihCSUrLWghCTWuKUmlKilKOc6tSKUIxhFGMkpRy
jBB88HuSjLOUkoxyjHKMkoxyjHKMUYTwe/B7UYQQfIpKhiksY45rr3PPc/B78HvPc/B7EHzPc89z
8Huuc01rspQwhKpSDGNNa01rTWuOc21zjnMwjBCEz3uve45zjnPOe++DbXMsa+tiiVLGOa17jXuN
e6177oMPhO6DUIwwhBCE74Ouc01rbWsMYyhCqlrKWutiC2MsY6pSqlKuc89z8HvPc69z8HvPc89z
EHzsWm5rjmtua21rbmtua01jLWNJQkUh7Fquc+9zEHzPc25rjmuGKShC61opQihCKULrWghC61pp
SklKDGMIQixjqlKKUopSTWuKUilCEIRRjLOUcowxjDGEcoySjHKMkoyzlJKMkoySjFGE8Hvwe3GM
MIQxhAg6pzEtY25rz3Ovc25rbnOOc/B773uuc69zjnOOc7KcspwMYyxjLWtuc21zTWssa41zMIzP
e21zjnOOc45zjXOue01rC2vrYmlS50HOg62DjXtte++DMIwPhFCMMIQwhDCMjXtMa01rC2MHQspa
62ILYyxrLGPLWutarnOuc89zrnOuc/B78HvwexB8LWOOa25rbmtta01rbWsMY+c5wxgEIY5rz3vP
c69zr3NNY25rhikpQutaSUopQmlKDGMIQutaaUpJSutaKEIsY2lKKEJJSixjKEIoQu97MYRyjFKM
UYwQhDCEUYRRhHKMkoySjJKMcoxRhBB88HtRhDGE8HvPc6cxpzEMW45rjmuOc25zbmvQezCE73uu
c45zbnOznHGULGsMY01rjnOOc21zTGttc657z3vvg657jnNtc657jXMsa+ti62KJWudBD4yue217
zoPvgzCMzntQjFCMUIwPjI57bXNtcwtjB0LrYgtjC2MLYyxjy1rrYs9773sQfM9zz3MwhBB8EHwQ
fC1jz3Ouc25rTWssY8tahjHjGKYxDGOuc69zr3OOa25rTWNua4YpCDrLWihCKEJJSgxjCEIMY2lK
KELLWihCDGMoQoYxCELLWuc5KEIQhBCEMYQxjBGEEIQxhFGEMYRyjFGEcoxyjFGEUYQQfPB7UYQx
hDGEcYzve0Uh5zktY25rbmtua05rjnPvexCErnNtc01r05ySlAxj62Jtc45zjnNtcyxrTGvvgxCE
8IPPe++DjXPvg41zC2PrYspiilrnQe+D74OOe657rnvvg657EIRxjFCMEIwQjO+DjXMLYyhK62Is
a+tiC2MsY8ta61rvew98MIQwhBB8MYQQfM9z73vrWm5rjmtuawxjSUokISQhSUosY21rbWtua45r
bmtua65zr3NmKccxqlLnOYYxCEIMYyhCDGOKUihCqlIIQutaqlIsY4pSilLHOUlKMIQsY8taEIQx
hHKMkoySjBB8MYSSjFGEcoxRhDCE8HvwexB8EHxRhDGEEHwMWyQhCDpNYy1jLWNua45zr3MQhO97
rnNNa7KckpRNay1rjnOOc25zbXNtc0xrrnsQhO+DbnPPewxjLGuNc21zLGvKYola50HPe65zbnOO
c65774OOcw+EcYyRlFCMMIwPhK5762IIQspa62Isa+ti61qqUgxjrnPvezCEUYQQfDGE8Huvc69z
y1JNY25r7FoIQkUppjGqUgxjTWuuc45zLWNNY+taSUJta69zZinHMapSy1osY4pS61pJSixjilII
QmlKCELrWqpSLGOKUqpSxzkoQu9773vPe/B7z3sQhFGEUYTPcxB8cYwxhDGEMYQxhBB88HsQfBB8
UYQwhK9zrnPLUgQZKUIsYwxjTWuOc21rjnOuc897LGMwhDGMTWsMY21rrnNtc0xrLGuue21zrnvP
e657rnsMY8ta62JJSklKKEKmOaYxz3uOc45zrnuue697bXMQhJGUspRwjDCEMIzvgwtjB0KKUsta
61rsYgxjqlLLWo5zrnPve+97z3sQfPB7rnOOa6pSy1ppSmUpZTFpUutiDGNta45zjnNNawxjTWtt
a65zTWNta4YxpjGKUopSLGOKUgxjSUosY4pS5zlJSuc5y1ooQihCSUrLWsc5SUrve5KUspTve65z
73vwe/B7z3PPc+9773vPc1GEUYTwe89zUYQQfM9z8Huvc25rjmtpSuQYSUrrWutaDGMMYyxjbWtt
a8ta73vPewxj61pta65zbXNMayxrjXNMa01rjnOue25zLGuqWqY5ZTFlKSQhBCFFKY5zjnPPe897
jnPPe01rMISylLKUkZRQjDCM74PLYqY5xzkIQopSilJpSsc5SUqOc45zz3vve65z73vwe89zrnMI
QkUpJCHHOetiLGsLYyxrbXOuc65zjnNta21rz3tRjG1rTWuGMaYxilIoQihCKEIMYyhCDGNpSuc5
SUrHOapSCEIIQghCilLHOQhCz3vPe897z3uuc45zrnOOa45rbmuOa89zr3MxhHKMEHzPczGEz3Ov
c45rrnOOay1jTWNJQsMYZSmmMcc55znnOQhCCEKGMShCKEKmMec5SUpJSopSylqqWopSaVJJSklK
aVIoSudBhjHHOU1rDGPLWstay1osa21zbnOue21zrntNaxCEkZSylJGMUIwwjK57iVLjGGEIghDD
GKIQohBhCElKrnOuc+9773uucxB88Hvwe45rRSkEIWlKTWssayxrC2NNa45zrnPPe+97rnOuc65z
jnOOc897hjGmMWlKKEJJSghCqlIIQstaSUrnOWlKxzmKUghCKEIIQqpSCELnOY5zz3uuc01rbWtN
a45rjmtNYy1jLWNNY45rr3PPc89zjmuvc25rTWNNY01jbmtNY+xabmsIOuMY4xjjGAQhJCEkISQh
4xgEIQQhBCEEIQQhJCFlKcc550GmOWUxRSlFKSUpBCEEISQpKEowhM97DGNNa01rbXNta21zrntu
c21zLGvPezCEkZSylJGUUIzOe0xrqlpJSklKilLLWutaLGOuc897z3uuc45z73swhM9zz3Nta6pS
bWtNayxjC2NMayxrLGtta21rbWuOc45zbWuOc45zbWuOc4YxxzmKUihCSUoIQsta5znLWklK5zlp
Ssc5qlIIQghCKEKqUghCpjEsY45zbmstYy1jTWMtYy1jLWMMW01jLWMtYy1jTWMtYy1jbmtNYy1j
DFsMWwxbTWMsY+xaLGPrWqpSakqrUstSy1LrWgxbLGMMW+xay1rLWqpSylptc657jXNtcyxrTGtN
ayxrLGuNc857D4QwhFGMrnNta45zbWtNa21rjnNtawxjbWtNazCEkpRxjFCMD4Sue41zLGMsY01r
jnOuc+97z3vPe897bWvrWq5zEHzPc45rbWtta01rLGMsYwxjbWssYyxjTWtta01rbWtta01rTWtN
a01rTWuGMec5qlJJSihCCELLWuc5qlJpSghCilLnOetaKEIIQghCilLnOWUpDGMMYy1jDFsMWwxb
DFsMWyxjDFstYwxb7FoMWyxj7FrLUi1jDFsMW+xay1LsWgxbLWMMW8tS61LsWgxbLWNNY69zz3Ov
c69zrnOOa25rTWssY21rrnvvg++Dznttc21zznttc45z74POe657EIQQhM97jnNtayxjLGMsYyxj
TWvLWm1rEIQQhBCE73sQhO+DjXNMayxjDGMsY45zjnOOc65zrnOuc+97EISuc+9773uOa45rTWsM
Y01rDGMsYyxjLGMsY21rrnOuc21rbWtta01rbWtta21rZSnnOapSKEIIQuc561rnOapSSUrHOWlK
xznLWuc5xznnOYpS5zllKQxj61rLWstSy1LLUuxaDFsMWwxb7FrLUstS7FrsWstSakrLUstSy1LL
UstSy1LrWstS7FrLUqtSy1LsWgxbLWNua45rbmuuc69zjmuOc21rbWtta21zrnvOe45zjnONc21z
jXONc41zjXOuc89773vPe21rbWtNawxj61rrWgxjqlKqUsta61rLWstaylqqWmlSiVJpSmlKqlLL
WqpSqlLLWqpSqlLrWstailLrWgxb61oLW8taqlKKUmlKaUppSqpSqlLLWuta61qKUopSqlKqUqpS
qlJtawQhCEKqUuc5xznHOctaxzmKUihChjEoQqYxaUplKUUpZSlJSqYxJCGqUihCCDpqSopKikqK
SqtSq1LLUstSqlKKSqtSq1KKSmpKikqKSopKilKKSmlKakqKSopKikpqSqtSy1LrWgxbbmuOa21r
jmuvc65zjnOOc65zjnNta41zznuue41zrnttc41zjnNtc0xrTGtNa21rjnNNa01rbWtNa+tay1rL
WopSilKKUopSilJpSkhKSEpISklKaVJJSmlKaUppSmlKSUooQklKSUppSmlKiUqJSopKikppSklK
aUpJSmlKaUppSopSaUqKUmlKSUooQihC5zmmMQhCy1oEIcc5aUqmMUUphjGKUqYxSUrnOSQhxzll
KQhCpjGmMaYxCEKGMQQhSUqqUqpSKEIoQihCKEIoQihCSUpJQklCSUJJQklCKEIoOgg6KEJJSihC
KELnOcc55znHOec55zlJSmlKikqKUstaLGMsYyxjTWtta45zjnOOc01rTWtNa21rbWtNa01rLGss
a0xrTWsMY+tay1qqUstaDGPrWuta61rLWopSaUppSklKSUppSklKKEIoQihCKEIIQihCKEIoQklK
SUpJSklKKEIoQihCSUpJSmlKSUpJSmlKSUooQihCCEIIQihCKEIoQihCKEJJSihCKEIIQklKqlIo
QihCwxhlKQhCpjGmMaYxSUqGMQhChjGiEAQhRSnnOQhCTWvnOaYxJCGiEIYxilKqUoYxhjGGMYYx
hjGmMaYxpzGnMacxpzGnMaYxpjGmMaYxpjGmMaYxhjFlKUUpJCFFKWUppjHHOedBCEIoQklKaUqK
UopSilLLWstay1qqUopSaUqKUopSilKKUmlKaUqKUmlKSUppSklKCELnOShCKEIIQghCCEIIQghC
5znnOec55znnOec55znnOcc5xznHOcc5CELnOec55znnOQhC5zkIQghCCEIIQuc55znHOcc5xznn
OaYxpjHHOcc5pjHHOaYxpjGmMaYxhjEoQgxjpjGGMWEIJCHHOShCTWsIQghCZSnHOUUpQQhhCCQh
pjEkIWUpJCGGMQQhIACCEIIQghCiEKIQohDDGMMY4xgEIQQhBBklISUhJSElIQQZBBkEIQQhBCEE
IQQh4xjjGOMY4xjjGAQhJCEkIUUpZSlFKSQhRSllKWUphjGGMYYxpjGGMYYxhjGGMYYxhjGGMYYx
hjFlKYYxhjGGMYYxZSllKWUpRSlFKUUpRSllKWUpZSllKaYxxzmmMYYxZSlFKSQhJCEkIUUpRSlF
KSQhRSlFKUUpRSlFKUUpJCEkISQhBCEEIQQhJCHjGOMYBCEkISQhJCEEIQQhBCEEIQQhBCHjGOMY
BCFBCOMYZSkkIYYxZSmmMSQhBCHjGEEIQQiiEMMYghCCEKIQwxiCEAAAQQhBCEEIQQhBCEEIQQhB
CGEIgQiCCIIIggiCCIIIggiCEIIQghCCEIIQghCCEIIQghCCEIIQghCiEKIQohCiEKIQghCCEIIQ
ghCiEKIQohCiEKIQwxjDGKIQohDDGMMYwxiiEKIQohCiEMMYwxjjGMMYwxjDGKIQohCiEKIQohDD
GMMYwxjjGOMYwxjDGKIQghBhCGEIYQhhCGEIYQhhCGEIYQhhCGEIYQhhCGEIYQhhCGEIQQhBCGEI
QQhhCGEIYQiCEIIQghBhCGEIghCCEKIQohCiEKIQIACCEMMYghCCEKIQwxiiEEEIQQg=
</pixels>
</second>
</item>
</textures>
</cgltrace>
</boost_serialization>

View file

@ -63,15 +63,23 @@ typedef struct {
} rast_tile_header_t;
typedef struct {
bool depth_enabled;
bool color_enabled;
bool tex_enabled;
uint32_t prim_addr;
uint32_t dst_addr;
uint32_t dst_width;
uint32_t dst_height;
uint8_t dst_stride;
uint32_t dst_pitch;
uint32_t cbuf_addr;
uint8_t cbuf_stride;
uint32_t cbuf_pitch;
uint32_t zbuf_addr;
uint8_t zbuf_stride;
uint32_t zbuf_pitch;
uint32_t prim_addr;
bool depth_enabled;
bool color_enabled;
bool tex_enabled;
bool tex_modulate;
} kernel_arg_t;
#endif

View file

@ -4,6 +4,8 @@
#include <cocogfx/include/color.hpp>
#include <cocogfx/include/math.hpp>
using fixeduv_t = cocogfx::TFixed<TEX_FXD_FRAC>;
#define DEFAULTS_i(i) \
z[i] = fixed24_t(0.0f); \
r[i] = fixed24_t(1.0f); \
@ -48,10 +50,10 @@
INTERPOLATE_i(3, dst, src);
#define TEXTURING(dst, u, v) \
dst[0] = vx_tex(0, u[0].data(), v[0].data(), 0); \
dst[1] = vx_tex(0, u[1].data(), v[1].data(), 0); \
dst[2] = vx_tex(0, u[2].data(), v[2].data(), 0); \
dst[3] = vx_tex(0, u[3].data(), v[3].data(), 0)
dst[0] = vx_tex(0, fixeduv_t(u[0]).data(), fixeduv_t(v[0]).data(), 0); \
dst[1] = vx_tex(0, fixeduv_t(u[1]).data(), fixeduv_t(v[1]).data(), 0); \
dst[2] = vx_tex(0, fixeduv_t(u[2]).data(), fixeduv_t(v[2]).data(), 0); \
dst[3] = vx_tex(0, fixeduv_t(u[3]).data(), fixeduv_t(v[3]).data(), 0)
#define MODULATE_i(i, dst_r, dst_g, dst_b, dst_a, src) \
dst_r[i] = fixed24_t::make(cocogfx::Mul8(dst_r[i].data(), src[i].r)); \
@ -65,6 +67,12 @@
MODULATE_i(2, dst_r, dst_g, dst_b, dst_a, src); \
MODULATE_i(3, dst_r, dst_g, dst_b, dst_a, src)
#define REPLACE(dst, src) \
dst[0] = src[0]; \
dst[1] = src[1]; \
dst[2] = src[2]; \
dst[3] = src[3]
#define TO_RGBA_i(i, dst, src_r, src_g, src_b, src_a) \
dst[i].r = static_cast<uint8_t>((src_r[i].data() * 255) >> fixed24_t::FRAC); \
dst[i].g = static_cast<uint8_t>((src_g[i].data() * 255) >> fixed24_t::FRAC); \
@ -119,12 +127,16 @@ void shader_function(int task_id, kernel_arg_t* kernel_arg) {
if (kernel_arg->tex_enabled) {
INTERPOLATE(u, attribs.u);
INTERPOLATE(v, attribs.v);
TEXTURING(tex_color, u, v);
MODULATE(r, g, b, a, tex_color);
TEXTURING(tex_color, u, v);
if (kernel_arg->tex_modulate) {
MODULATE(r, g, b, a, tex_color);
} else {
REPLACE(out_color, tex_color);
}
} else {
TO_RGBA(out_color, r, g, b, a);
}
TO_RGBA(out_color, r, g, b, a);
OUTPUT(out_color, z);
}
}

View file

@ -9,8 +9,7 @@
#include <vortex.h>
#include "common.h"
#include "utils.h"
#include "model_cube.h"
#include "model_triangle.h"
#include <cocogfx/include/cgltrace.hpp>
using namespace cocogfx;
@ -27,40 +26,48 @@ using namespace cocogfx;
///////////////////////////////////////////////////////////////////////////////
const char* kernel_file = "kernel.bin";
const char* input_file = "fire.png";
const char* trace_file = "triangle.cgltrace";
const char* output_file = "output.png";
const char* reference_file = nullptr;
uint32_t clear_color = 0x00000000;
uint32_t clear_depth = 0x00000000;
int tex_format = TEX_FORMAT_A8R8G8B8;
ePixelFormat tex_eformat = FORMAT_A8R8G8B8;
int tex_wrap = TEX_WRAP_CLAMP;
int tex_filter = TEX_FILTER_POINT;
uint32_t clear_color = 0xFFFFFFFF;
uint32_t clear_depth = 0xFFFFFFFF;
uint32_t dst_width = 128;
uint32_t dst_height = 128;
const model_t& model = model_triangle;
bool blend_enabled = false;
uint32_t zbuf_stride = 4;
uint32_t zbuf_pitch = dst_width * zbuf_stride;
uint32_t zbuf_size = dst_height * zbuf_pitch;
uint32_t cbuf_stride = 4;
uint32_t cbuf_pitch = dst_width * cbuf_stride;
uint32_t cbuf_size = dst_width * cbuf_pitch;
vx_device_h device = nullptr;
vx_buffer_h staging_buf = nullptr;
uint64_t tilebuf_addr;
uint64_t primbuf_addr;
uint64_t tbuf_addr;
uint64_t zbuf_addr;
uint64_t cbuf_addr;
uint64_t zbuf_addr = -1;
uint64_t cbuf_addr = -1;
uint64_t texbuf_addr = -1;
uint64_t tilebuf_addr = -1;
uint64_t primbuf_addr = -1;
kernel_arg_t kernel_arg;
uint32_t tile_size = 1 << RASTER_TILE_LOGSIZE;
static void show_usage() {
std::cout << "Vortex 3D Rendering Test." << std::endl;
std::cout << "Usage: [-i texture] [-o output] [-r reference] [-w width] [-h height]" << std::endl;
std::cout << "Usage: [-t trace] [-o output] [-r reference] [-w width] [-h height]" << std::endl;
}
static void parse_args(int argc, char **argv) {
int c;
while ((c = getopt(argc, argv, "i:o:r:w:h:t:f:g:?")) != -1) {
while ((c = getopt(argc, argv, "t:i:o:r:w:h:t:?")) != -1) {
switch (c) {
case 'i':
input_file = optarg;
case 't':
trace_file = optarg;
break;
case 'o':
output_file = optarg;
@ -74,24 +81,6 @@ static void parse_args(int argc, char **argv) {
case 'h':
dst_height = std::atoi(optarg);
break;
case 'f':
tex_format = std::atoi(optarg);
switch (tex_format) {
case TEX_FORMAT_A8R8G8B8: tex_eformat = FORMAT_A8R8G8B8; break;
case TEX_FORMAT_R5G6B5: tex_eformat = FORMAT_R5G6B5; break;
case TEX_FORMAT_A1R5G5B5: tex_eformat = FORMAT_A1R5G5B5; break;
case TEX_FORMAT_A4R4G4B4: tex_eformat = FORMAT_A4R4G4B4; break;
case TEX_FORMAT_A8L8: tex_eformat = FORMAT_A8L8; break;
case TEX_FORMAT_L8: tex_eformat = FORMAT_L8; break;
case TEX_FORMAT_A8: tex_eformat = FORMAT_A8; break;
default:
std::cout << "Error: invalid texture format: " << tex_format << std::endl;
exit(1);
}
break;
case 'g':
tex_filter = std::atoi(optarg);
break;
case '?': {
show_usage();
exit(0);
@ -107,69 +96,224 @@ void cleanup() {
if (staging_buf) {
vx_buf_free(staging_buf);
}
if (device) {
vx_mem_free(device, tilebuf_addr);
vx_mem_free(device, primbuf_addr);
if (model.tex_enabled)
vx_mem_free(device, tbuf_addr);
vx_mem_free(device, zbuf_addr);
vx_mem_free(device, cbuf_addr);
if (device) {
if (zbuf_addr != -1ull) vx_mem_free(device, zbuf_addr);
if (cbuf_addr != -1ull) vx_mem_free(device, cbuf_addr);
if (texbuf_addr != -1ull) vx_mem_free(device, texbuf_addr);
if (tilebuf_addr != -1ull) vx_mem_free(device, tilebuf_addr);
if (primbuf_addr != -1ull) vx_mem_free(device, primbuf_addr);
vx_dev_close(device);
}
}
int render(uint32_t buf_addr, uint32_t buf_size, uint32_t width, uint32_t height) {
auto time_start = std::chrono::high_resolution_clock::now();
int render(const CGLTrace& trace) {
// render each draw call
for (auto& drawcall : trace.drawcalls) {
auto& states = drawcall.states;
// start device
std::cout << "start device" << std::endl;
RT_CHECK(vx_start(device));
if (states.texture_enabled) {
std::vector<uint8_t> texbuf;
std::vector<uint32_t> mip_offsets;
// wait for completion
std::cout << "wait for completion" << std::endl;
RT_CHECK(vx_ready_wait(device, MAX_TIMEOUT));
auto time_end = std::chrono::high_resolution_clock::now();
double elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(time_end - time_start).count();
printf("Elapsed time: %lg ms\n", elapsed);
auto& texture = trace.textures.at(drawcall.texture_id);
auto tex_bpp = Format::GetInfo(texture.format).BytePerPixel;
auto tex_pitch = texture.width * tex_bpp;
// generate mipmaps
RT_CHECK(GenerateMipmaps(texbuf, mip_offsets, texture.pixels.data(), texture.format, texture.width, texture.height, tex_pitch));
uint32_t tex_logwidth = log2ceil(texture.width);
uint32_t tex_logheight = log2ceil(texture.height);
int tex_format = toVXFormat(texture.format);
int tex_filter = (states.texture_magfilter != CGLTrace::FILTER_NEAREST)
|| (states.texture_magfilter != CGLTrace::FILTER_NEAREST);
int tex_wrapU = (states.texture_addressU == CGLTrace::ADDRESS_WRAP);
int tex_wrapV = (states.texture_addressU == CGLTrace::ADDRESS_WRAP);
// allocate texture memory
if (texbuf_addr != -1ull) vx_mem_free(device, texbuf_addr);
RT_CHECK(vx_mem_alloc(device, texbuf.size(), &texbuf_addr));
std::cout << "texbuf_addr=0x" << std::hex << texbuf_addr << std::endl;
// upload texture data
std::cout << "upload texture buffer" << std::endl;
{
RT_CHECK(vx_buf_alloc(device, texbuf.size(), &staging_buf));
auto buf_ptr = (uint8_t*)vx_host_ptr(staging_buf);
memcpy(buf_ptr, texbuf.data(), texbuf.size());
RT_CHECK(vx_copy_to_dev(staging_buf, texbuf_addr, texbuf.size(), 0));
vx_buf_free(staging_buf);
staging_buf = nullptr;
}
// configure texture units
vx_dcr_write(device, DCR_TEX_STAGE, 0);
vx_dcr_write(device, DCR_TEX_LOGDIM, (tex_logheight << 16) | tex_logwidth);
vx_dcr_write(device, DCR_TEX_FORMAT, tex_format);
vx_dcr_write(device, DCR_TEX_WRAP, (tex_wrapV << 16) | tex_wrapU);
vx_dcr_write(device, DCR_TEX_FILTER, tex_filter);
vx_dcr_write(device, DCR_TEX_ADDR, texbuf_addr);
for (uint32_t i = 0; i < mip_offsets.size(); ++i) {
assert(i < TEX_LOD_MAX);
vx_dcr_write(device, DCR_TEX_MIPOFF(i), mip_offsets.at(i));
};
}
std::vector<uint8_t> tilebuf;
std::vector<uint8_t> primbuf;
// Perform tile binning
auto num_tiles = Binning(tilebuf, primbuf, drawcall.vertices, drawcall.primitives, dst_width, dst_height, drawcall.viewport.near, drawcall.viewport.far, tile_size);
std::cout << "Binning allocated " << num_tiles << " tiles and " << primbuf.size() << " primitives." << std::endl;
// allocate tile memory
if (tilebuf_addr != -1ull) vx_mem_free(device, tilebuf_addr);
if (primbuf_addr != -1ull) vx_mem_free(device, primbuf_addr);
RT_CHECK(vx_mem_alloc(device, tilebuf.size(), &tilebuf_addr));
RT_CHECK(vx_mem_alloc(device, primbuf.size(), &primbuf_addr));
std::cout << "tilebuf_addr=0x" << std::hex << tilebuf_addr << std::endl;
std::cout << "primbuf_addr=0x" << std::hex << primbuf_addr << std::endl;
uint32_t alloc_size = std::max({tilebuf.size(), primbuf.size(), sizeof(kernel_arg_t)});
RT_CHECK(vx_buf_alloc(device, alloc_size, &staging_buf));
// upload tiles buffer
std::cout << "upload tiles buffer" << std::endl;
{
auto buf_ptr = (uint8_t*)vx_host_ptr(staging_buf);
memcpy(buf_ptr, tilebuf.data(), tilebuf.size());
RT_CHECK(vx_copy_to_dev(staging_buf, tilebuf_addr, tilebuf.size(), 0));
}
// upload primitives buffer
std::cout << "upload primitives buffer" << std::endl;
{
auto buf_ptr = (uint8_t*)vx_host_ptr(staging_buf);
memcpy(buf_ptr, primbuf.data(), primbuf.size());
RT_CHECK(vx_copy_to_dev(staging_buf, primbuf_addr, primbuf.size(), 0));
}
// upload kernel argument
std::cout << "upload kernel argument" << std::endl;
{
kernel_arg.depth_enabled = states.depth_test;
kernel_arg.color_enabled = states.color_enabled;
kernel_arg.tex_enabled = states.texture_enabled;
kernel_arg.tex_modulate = (states.texture_enabled && states.texture_envmode == CGLTrace::ENVMODE_MODULATE);
kernel_arg.prim_addr = primbuf_addr;
if (kernel_arg.tex_modulate)
kernel_arg.color_enabled = false;
auto buf_ptr = (uint8_t*)vx_host_ptr(staging_buf);
memcpy(buf_ptr, &kernel_arg, sizeof(kernel_arg_t));
RT_CHECK(vx_copy_to_dev(staging_buf, KERNEL_ARG_DEV_MEM_ADDR, sizeof(kernel_arg_t), 0));
}
vx_buf_free(staging_buf);
staging_buf = nullptr;
uint32_t primbuf_stride = sizeof(rast_prim_t);
// configure raster units
vx_dcr_write(device, DCR_RASTER_TBUF_ADDR, tilebuf_addr);
vx_dcr_write(device, DCR_RASTER_TILE_COUNT, num_tiles);
vx_dcr_write(device, DCR_RASTER_PBUF_ADDR, primbuf_addr);
vx_dcr_write(device, DCR_RASTER_PBUF_STRIDE, primbuf_stride);
// configure rop color buffer
vx_dcr_write(device, DCR_ROP_CBUF_ADDR, cbuf_addr);
vx_dcr_write(device, DCR_ROP_CBUF_PITCH, cbuf_pitch);
vx_dcr_write(device, DCR_ROP_CBUF_MASK, states.color_writemask);
if (states.depth_test || states.stencil_test) {
// configure rop depth buffer
vx_dcr_write(device, DCR_ROP_ZBUF_ADDR, zbuf_addr);
vx_dcr_write(device, DCR_ROP_ZBUF_PITCH, zbuf_pitch);
}
if (states.depth_test) {
// configure rop depth states
auto depth_func = toVXCompare(states.depth_func);
vx_dcr_write(device, DCR_ROP_DEPTH_FUNC, depth_func);
vx_dcr_write(device, DCR_ROP_DEPTH_MASK, states.depth_writemask);
}
if (states.stencil_test) {
// configure rop stencil states
auto stencil_func = toVXCompare(states.stencil_func);
auto stencil_zpass = toVXStencilOp(states.stencil_zpass);
auto stencil_zfail = toVXStencilOp(states.stencil_zfail);
auto stencil_fail = toVXStencilOp(states.stencil_fail);
vx_dcr_write(device, DCR_ROP_STENCIL_FUNC, stencil_func);
vx_dcr_write(device, DCR_ROP_STENCIL_ZPASS, stencil_zpass);
vx_dcr_write(device, DCR_ROP_STENCIL_ZPASS, stencil_zfail);
vx_dcr_write(device, DCR_ROP_STENCIL_FAIL, stencil_fail);
vx_dcr_write(device, DCR_ROP_STENCIL_MASK, states.stencil_mask);
vx_dcr_write(device, DCR_ROP_STENCIL_REF, states.stencil_ref);
}
if (states.blend_enabled) {
// configure rop blend states
auto blend_src = toVXBlendFunc(states.blend_src);
auto blend_dst = toVXBlendFunc(states.blend_dst);
vx_dcr_write(device, DCR_ROP_BLEND_MODE, (ROP_BLEND_MODE_ADD << 16) // DST
| (ROP_BLEND_MODE_ADD << 0)); // SRC
vx_dcr_write(device, DCR_ROP_BLEND_FUNC, (blend_dst << 24) // DST_A
| (blend_dst << 16) // DST_RGB
| (blend_src << 8) // SRC_A
| (blend_src << 0)); // SRC_RGB
}
auto time_start = std::chrono::high_resolution_clock::now();
// start device
std::cout << "start device" << std::endl;
RT_CHECK(vx_start(device));
// wait for completion
std::cout << "wait for completion" << std::endl;
RT_CHECK(vx_ready_wait(device, MAX_TIMEOUT));
auto time_end = std::chrono::high_resolution_clock::now();
double elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(time_end - time_start).count();
printf("Elapsed time: %lg ms\n", elapsed);
}
// download destination buffer
std::cout << "download destination buffer" << std::endl;
RT_CHECK(vx_copy_from_dev(staging_buf, buf_addr, buf_size, 0));
std::vector<uint8_t> dst_pixels(buf_size);
auto buf_ptr = (uint8_t*)vx_host_ptr(staging_buf);
memcpy(dst_pixels.data(), buf_ptr, buf_size);
std::vector<uint8_t> dst_pixels(cbuf_size);
{
std::cout << "download destination buffer" << std::endl;
RT_CHECK(vx_buf_alloc(device, cbuf_size, &staging_buf));
RT_CHECK(vx_copy_from_dev(staging_buf, cbuf_addr, cbuf_size, 0));
auto buf_ptr = (uint8_t*)vx_host_ptr(staging_buf);
memcpy(dst_pixels.data(), buf_ptr, cbuf_size);
vx_buf_free(staging_buf);
staging_buf = nullptr;
}
// save output image
std::cout << "save output image" << std::endl;
//dump_image(dst_pixels, width, height, 4);
{
// the image is flipped
auto pitch = width * 4;
auto bits = dst_pixels.data() + (height-1) * pitch;
RT_CHECK(SaveImage(output_file, FORMAT_A8R8G8B8, bits, width, height, -pitch));
// save image upside down
auto bits = dst_pixels.data() + (dst_height-1) * cbuf_pitch;
RT_CHECK(SaveImage(output_file, FORMAT_A8R8G8B8, bits, dst_width, dst_height, -cbuf_pitch));
}
return 0;
}
int main(int argc, char *argv[]) {
std::vector<uint8_t> tilebuf;
std::vector<uint8_t> primbuf;
std::vector<uint8_t> texbuf;
std::vector<uint32_t> mip_offsets;
uint32_t tex_width;
uint32_t tex_height;
int main(int argc, char *argv[]) {
// parse command arguments
parse_args(argc, argv);
uint32_t tile_size = 1 << RASTER_TILE_LOGSIZE;
if (!ispow2(dst_width)) {
std::cout << "Error: only power of two dst_width supported: dst_width=" << dst_width << std::endl;
std::cout << "Error: only power of two dst_width supported: dst_width=" << dst_width << std::endl;
return -1;
}
@ -196,112 +340,37 @@ int main(int argc, char *argv[]) {
RT_CHECK(vx_dev_caps(device, VX_CAPS_ISA_FLAGS, &isa_flags));
if (0 == (isa_flags & (VX_ISA_EXT_RASTER | VX_ISA_EXT_ROP))) {
std::cout << "raster or rop extensions not supported!" << std::endl;
cleanup();
return -1;
}
if (model.tex_enabled) {
std::vector<uint8_t> staging;
RT_CHECK(LoadImage(input_file, tex_eformat, staging, &tex_width, &tex_height));
// check power of two support
if (!ispow2(tex_width) || !ispow2(tex_height)) {
std::cout << "Error: only power of two textures supported: width=" << tex_width << ", heigth=" << tex_height << std::endl;
return -1;
}
uint32_t tex_bpp = Format::GetInfo(tex_eformat).BytePerPixel;
uint32_t tex_pitch = tex_width * tex_bpp;
RT_CHECK(GenerateMipmaps(texbuf, mip_offsets, staging.data(), tex_eformat, tex_width, tex_height, tex_pitch));
}
CGLTrace trace;
RT_CHECK(trace.load(trace_file));
uint32_t primbuf_stride = sizeof(rast_prim_t);
uint32_t tex_logwidth = log2ceil(tex_width);
uint32_t tex_logheight = log2ceil(tex_height);
uint32_t zbuf_stride = 4;
uint32_t zbuf_pitch = dst_width * zbuf_stride;
uint32_t zbuf_size = dst_height * zbuf_pitch;
uint32_t cbuf_stride = 4;
uint32_t cbuf_pitch = dst_width * cbuf_stride;
uint32_t cbuf_size = dst_width * cbuf_pitch;
// Perform tile binning
auto num_tiles = Binning(tilebuf, primbuf, model, dst_width, dst_height, tile_size);
std::cout << "Binning allocated " << num_tiles << " tiles." << std::endl;
// upload program
std::cout << "upload program" << std::endl;
RT_CHECK(vx_upload_kernel_file(device, kernel_file));
// allocate device memory
std::cout << "allocate device memory" << std::endl;
RT_CHECK(vx_mem_alloc(device, tilebuf.size(), &tilebuf_addr));
RT_CHECK(vx_mem_alloc(device, primbuf.size(), &primbuf_addr));
if (model.tex_enabled)
RT_CHECK(vx_mem_alloc(device, texbuf.size(), &tbuf_addr));
// allocate device memory
RT_CHECK(vx_mem_alloc(device, zbuf_size, &zbuf_addr));
RT_CHECK(vx_mem_alloc(device, cbuf_size, &cbuf_addr));
std::cout << "tilebuf_addr=0x" << std::hex << tilebuf_addr << std::endl;
std::cout << "primbuf_addr=0x" << std::hex << primbuf_addr << std::endl;
std::cout << "tbuf_addr=0x" << std::hex << tbuf_addr << std::endl;
std::cout << "zbuf_addr=0x" << std::hex << zbuf_addr << std::endl;
std::cout << "cbuf_addr=0x" << std::hex << cbuf_addr << std::endl;
// allocate staging buffer
std::cout << "allocate staging buffer" << std::endl;
uint32_t alloc_size = std::max<uint32_t>({
sizeof(kernel_arg_t),
(uint32_t)tilebuf.size(),
(uint32_t)primbuf.size(),
(uint32_t)texbuf.size(),
zbuf_size,
cbuf_size
});
uint32_t alloc_size = std::max(zbuf_size, cbuf_size);
RT_CHECK(vx_buf_alloc(device, alloc_size, &staging_buf));
// upload kernel argument
std::cout << "upload kernel argument" << std::endl;
{
kernel_arg.depth_enabled= model.depth_enabled;
kernel_arg.color_enabled= model.color_enabled;
kernel_arg.tex_enabled = model.tex_enabled;
kernel_arg.prim_addr = primbuf_addr;
kernel_arg.dst_width = dst_width;
kernel_arg.dst_height = dst_height;
kernel_arg.dst_stride = cbuf_stride;
kernel_arg.dst_pitch = cbuf_pitch;
kernel_arg.dst_addr = cbuf_addr;
auto buf_ptr = (uint8_t*)vx_host_ptr(staging_buf);
memcpy(buf_ptr, &kernel_arg, sizeof(kernel_arg_t));
RT_CHECK(vx_copy_to_dev(staging_buf, KERNEL_ARG_DEV_MEM_ADDR, sizeof(kernel_arg_t), 0));
}
// upload tiles buffer
std::cout << "upload tiles buffer" << std::endl;
{
auto buf_ptr = (uint8_t*)vx_host_ptr(staging_buf);
memcpy(buf_ptr, tilebuf.data(), tilebuf.size());
RT_CHECK(vx_copy_to_dev(staging_buf, tilebuf_addr, tilebuf.size(), 0));
}
// upload primitives buffer
std::cout << "upload primitives buffer" << std::endl;
{
auto buf_ptr = (uint8_t*)vx_host_ptr(staging_buf);
memcpy(buf_ptr, primbuf.data(), primbuf.size());
RT_CHECK(vx_copy_to_dev(staging_buf, primbuf_addr, primbuf.size(), 0));
}
// clear depth buffer
std::cout << "clear depth buffer" << std::endl;
{
auto buf_ptr = (uint32_t*)vx_host_ptr(staging_buf);
for (uint32_t i = 0; i < (cbuf_size/4); ++i) {
for (uint32_t i = 0; i < (zbuf_size/4); ++i) {
buf_ptr[i] = clear_depth;
}
RT_CHECK(vx_copy_to_dev(staging_buf, kernel_arg.dst_addr, cbuf_size, 0));
RT_CHECK(vx_copy_to_dev(staging_buf, zbuf_addr, zbuf_size, 0));
}
// clear destination buffer
@ -311,70 +380,27 @@ int main(int argc, char *argv[]) {
for (uint32_t i = 0; i < (cbuf_size/4); ++i) {
buf_ptr[i] = clear_color;
}
RT_CHECK(vx_copy_to_dev(staging_buf, kernel_arg.dst_addr, cbuf_size, 0));
}
RT_CHECK(vx_copy_to_dev(staging_buf, cbuf_addr, cbuf_size, 0));
}
if (model.tex_enabled) {
// configure texture units
vx_dcr_write(device, DCR_TEX_STAGE, 0);
vx_dcr_write(device, DCR_TEX_LOGDIM, (tex_logheight << 16) | tex_logwidth);
vx_dcr_write(device, DCR_TEX_FORMAT, tex_format);
vx_dcr_write(device, DCR_TEX_WRAP, (tex_wrap << 16) | tex_wrap);
vx_dcr_write(device, DCR_TEX_FILTER, tex_filter);
vx_dcr_write(device, DCR_TEX_ADDR, tbuf_addr);
for (uint32_t i = 0; i < mip_offsets.size(); ++i) {
assert(i < TEX_LOD_MAX);
vx_dcr_write(device, DCR_TEX_MIPOFF(i), mip_offsets.at(i));
};
}
vx_buf_free(staging_buf);
staging_buf = nullptr;
// configure raster units
vx_dcr_write(device, DCR_RASTER_TBUF_ADDR, tilebuf_addr);
vx_dcr_write(device, DCR_RASTER_TILE_COUNT, num_tiles);
vx_dcr_write(device, DCR_RASTER_PBUF_ADDR, primbuf_addr);
vx_dcr_write(device, DCR_RASTER_PBUF_STRIDE, primbuf_stride);
// update kernel arguments
kernel_arg.dst_width = dst_width;
kernel_arg.dst_height = dst_height;
// configure rop color buffer
vx_dcr_write(device, DCR_ROP_CBUF_ADDR, cbuf_addr);
vx_dcr_write(device, DCR_ROP_CBUF_PITCH, cbuf_pitch);
vx_dcr_write(device, DCR_ROP_CBUF_MASK, 0xffffffff);
kernel_arg.cbuf_stride = cbuf_stride;
kernel_arg.cbuf_pitch = cbuf_pitch;
kernel_arg.cbuf_addr = cbuf_addr;
if (model.depth_enabled) {
// configure rop depth buffer
vx_dcr_write(device, DCR_ROP_ZBUF_ADDR, zbuf_addr);
vx_dcr_write(device, DCR_ROP_ZBUF_PITCH, zbuf_pitch);
// configure rop depth states
vx_dcr_write(device, DCR_ROP_DEPTH_FUNC, ROP_DEPTH_FUNC_LESS);
vx_dcr_write(device, DCR_ROP_DEPTH_MASK, 1);
vx_dcr_write(device, DCR_ROP_STENCIL_FUNC, (ROP_DEPTH_FUNC_ALWAYS << 16) // back
| (ROP_DEPTH_FUNC_ALWAYS << 0)); // front
vx_dcr_write(device, DCR_ROP_STENCIL_ZPASS, (ROP_STENCIL_OP_KEEP << 16) // back
| (ROP_STENCIL_OP_KEEP << 0)); // front
vx_dcr_write(device, DCR_ROP_STENCIL_ZPASS, (ROP_STENCIL_OP_KEEP << 16) // back
| (ROP_STENCIL_OP_KEEP << 0)); // front
vx_dcr_write(device, DCR_ROP_STENCIL_FAIL, (ROP_STENCIL_OP_KEEP << 16) // back
| (ROP_STENCIL_OP_KEEP << 0)); // front
vx_dcr_write(device, DCR_ROP_STENCIL_MASK, (0xff << 16) // back
| (0xff << 0)); // front
vx_dcr_write(device, DCR_ROP_STENCIL_REF, (0 << 16) // back
| (0 << 0)); // front
}
if (blend_enabled) {
// configure rop blending
vx_dcr_write(device, DCR_ROP_BLEND_MODE, (ROP_BLEND_MODE_ADD << 16) // DST
| (ROP_BLEND_MODE_ADD << 0)); // SRC
vx_dcr_write(device, DCR_ROP_BLEND_FUNC, (ROP_BLEND_FUNC_ZERO << 24) // DST_A
| (ROP_BLEND_FUNC_ONE_MINUS_SRC_A << 16) // DST_RGB
| (ROP_BLEND_FUNC_ONE << 8) // SRC_A
| (ROP_BLEND_FUNC_SRC_A << 0)); // SRC_RGB
vx_dcr_write(device, DCR_ROP_LOGIC_OP, ROP_LOGIC_OP_COPY);
}
kernel_arg.zbuf_stride = zbuf_stride;
kernel_arg.zbuf_pitch = zbuf_pitch;
kernel_arg.zbuf_addr = zbuf_addr;
// run tests
std::cout << "render" << std::endl;
RT_CHECK(render(cbuf_addr, cbuf_size, dst_width, dst_height));
RT_CHECK(render(trace));
// cleanup
std::cout << "cleanup" << std::endl;

View file

@ -1,30 +0,0 @@
#pragma once
#include "common.h"
#include <vector>
#include <string>
typedef struct {
float x;
float y;
float z;
float w;
uint32_t c;
float u;
float v;
} vertex_t;
typedef struct {
uint32_t i0;
uint32_t i1;
uint32_t i2;
} primitive_t;
struct model_t {
bool depth_enabled;
bool color_enabled;
bool tex_enabled;
std::vector<vertex_t> vertives;
std::vector<primitive_t> primitives;
std::string texture;
};

View file

@ -1,60 +0,0 @@
#pragma once
#include "model.h"
const model_t model_cube = {
true, // depth
false, // color
true, // tex
{ // vertices
{-6.337301, 0.000000, 24.177938, 24.949747, 0xffffffff, 0.000000, 0.000000},
{6.337301, 0.000000, 24.177938, 24.949747, 0xffffffff, 1.000000, 0.000000},
{6.337301, 11.949731, 18.974358, 20.000000, 0xffffffff, 1.000000, 1.000000},
{-6.337301, 11.949731, 18.974358, 20.000000, 0xffffffff, 0.000000, 1.000000},
{-6.337301, -11.949731, 18.974358, 20.000000, 0xffffffff, 1.000000, 1.000000},
{6.337301, -11.949731, 18.974358, 20.000000, 0xffffffff, 0.000000, 1.000000},
{6.337301, 0.000000, 13.770777, 15.050253, 0xffffffff, 0.000000, 0.000000},
{-6.337301, 0.000000, 13.770777, 15.050253, 0xffffffff, 1.000000, 0.000000},
{6.337301, 11.949731, 18.974358, 20.000000, 0xffffffff, 1.000000, 1.000000},
{-6.337301, 11.949731, 18.974358, 20.000000, 0xffffffff, 0.000000, 1.000000},
{-6.337301, -11.949731, 18.974358, 20.000000, 0xffffffff, 1.000000, 1.000000},
{6.337301, -11.949731, 18.974358, 20.000000, 0xffffffff, 0.000000, 1.000000},
{6.337301, 0.000000, 13.770777, 15.050253, 0xffffffff, 0.000000, 0.000000},
{-6.337301, 0.000000, 13.770777, 15.050253, 0xffffffff, 1.000000, 0.000000},
{-6.337301, 0.000000, 24.177938, 24.949747, 0xffffffff, 0.000000, 0.000000},
{6.337301, 0.000000, 24.177938, 24.949747, 0xffffffff, 1.000000, 0.000000},
{6.337301, 11.949731, 18.974358, 20.000000, 0xffffffff, 1.000000, 1.000000},
{-6.337301, 11.949731, 18.974358, 20.000000, 0xffffffff, 0.000000, 1.000000},
{-6.337301, -11.949731, 18.974358, 20.000000, 0xffffffff, 1.000000, 1.000000},
{6.337301, -11.949731, 18.974358, 20.000000, 0xffffffff, 0.000000, 1.000000},
{-6.337301, 0.000000, 24.177938, 24.949747, 0xffffffff, 0.000000, 0.000000},
{6.337301, 0.000000, 24.177938, 24.949747, 0xffffffff, 1.000000, 0.000000},
{6.337301, 11.949731, 18.974358, 20.000000, 0xffffffff, 1.000000, 1.000000},
{-6.337301, 11.949731, 18.974358, 20.000000, 0xffffffff, 0.000000, 1.000000},
{-6.337301, -11.949731, 18.974358, 20.000000, 0xffffffff, 1.000000, 1.000000},
{6.337301, -11.949731, 18.974358, 20.000000, 0xffffffff, 0.000000, 1.000000},
{6.337301, 0.000000, 13.770777, 15.050253, 0xffffffff, 0.000000, 0.000000},
{-6.337301, 0.000000, 13.770777, 15.050253, 0xffffffff, 1.000000, 0.000000},
{6.337301, 0.000000, 24.177938, 24.949747, 0xffffffff, 1.000000, 0.000000},
{6.337301, 11.949731, 18.974358, 20.000000, 0xffffffff, 1.000000, 1.000000},
{-6.337301, 11.949731, 18.974358, 20.000000, 0xffffffff, 0.000000, 1.000000},
{-6.337301, -11.949731, 18.974358, 20.000000, 0xffffffff, 1.000000, 1.000000},
{6.337301, -11.949731, 18.974358, 20.000000, 0xffffffff, 0.000000, 1.000000},
{6.337301, 0.000000, 13.770777, 15.050253, 0xffffffff, 0.000000, 0.000000}
},
{ // primitives
{2, 1, 3},
{3, 1, 0},
{1, 2, 0},
{0, 2, 3},
{4, 0, 5},
{5, 0, 1},
{1, 5, 0},
{0, 5, 4},
{3, 0, 7},
{7, 0, 4},
{5, 4, 1},
{1, 4, 0}
},
"fire.png"
};

View file

@ -1,18 +0,0 @@
#pragma once
#include "model.h"
const model_t model_triangle = {
false, // depth
true, // color
false, // tex
{ // vertices
{-0.5f,-0.5f, 0.0f, 1.0, 0xffff0000, 0.000000, 0.000000},
{ 0.5f,-0.5f, 0.0f, 1.0, 0xff00ff00, 0.000000, 0.000000},
{ 0.0f, 0.5f, 0.0f, 1.0, 0xff0000ff, 0.000000, 0.000000}
},
{ // primitves
{0, 1, 2},
},
""
};

View file

@ -0,0 +1,137 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<!DOCTYPE boost_serialization>
<boost_serialization signature="serialization::archive" version="15">
<cgltrace class_id="0" tracking_level="0" version="0">
<version>0</version>
<drawcalls class_id="1" tracking_level="0" version="0">
<count>1</count>
<item_version>0</item_version>
<item class_id="2" tracking_level="0" version="0">
<states class_id="3" tracking_level="0" version="0">
<color_enabled>1</color_enabled>
<color_format>5</color_format>
<color_writemask>4294967295</color_writemask>
<depth_test>0</depth_test>
<depth_writemask>0</depth_writemask>
<depth_format>13</depth_format>
<depth_func>0</depth_func>
<stencil_test>0</stencil_test>
<stencil_func>0</stencil_func>
<stencil_zpass>0</stencil_zpass>
<stencil_zfail>0</stencil_zfail>
<stencil_fail>0</stencil_fail>
<stencil_ref>0</stencil_ref>
<stencil_mask>255</stencil_mask>
<stencil_writemask>0</stencil_writemask>
<texture_enabled>0</texture_enabled>
<texture_envcolor class_id="4" tracking_level="0" version="0">
<r>0.000000000e+00</r>
<g>0.000000000e+00</g>
<b>0.000000000e+00</b>
<a>1.000000000e+00</a>
</texture_envcolor>
<texture_envmode>0</texture_envmode>
<texture_minfilter>0</texture_minfilter>
<texture_magfilter>0</texture_magfilter>
<texture_addressU>0</texture_addressU>
<texture_addressV>0</texture_addressV>
<blend_enabled>0</blend_enabled>
<blend_src>0</blend_src>
<blend_dst>0</blend_dst>
</states>
<texture_id>0</texture_id>
<vertices class_id="5" tracking_level="0" version="0">
<count>3</count>
<bucket_count>13</bucket_count>
<item_version>0</item_version>
<item class_id="6" tracking_level="0" version="0">
<first>2</first>
<second class_id="7" tracking_level="0" version="0">
<pos class_id="8" tracking_level="0" version="0">
<x>0.000000000e+00</x>
<y>5.000000000e-01</y>
<z>1.999999881e-01</z>
<w>1.000000000e+00</w>
</pos>
<color>
<r>0.000000000e+00</r>
<g>0.000000000e+00</g>
<b>1.000000000e+00</b>
<a>1.000000000e+00</a>
</color>
<texcoord class_id="9" tracking_level="0" version="0">
<u>0.000000000e+00</u>
<v>0.000000000e+00</v>
</texcoord>
</second>
</item>
<item>
<first>1</first>
<second>
<pos>
<x>5.000000000e-01</x>
<y>-5.000000000e-01</y>
<z>1.999999881e-01</z>
<w>1.000000000e+00</w>
</pos>
<color>
<r>0.000000000e+00</r>
<g>1.000000000e+00</g>
<b>0.000000000e+00</b>
<a>1.000000000e+00</a>
</color>
<texcoord>
<u>0.000000000e+00</u>
<v>0.000000000e+00</v>
</texcoord>
</second>
</item>
<item>
<first>0</first>
<second>
<pos>
<x>-5.000000000e-01</x>
<y>-5.000000000e-01</y>
<z>1.999999881e-01</z>
<w>1.000000000e+00</w>
</pos>
<color>
<r>1.000000000e+00</r>
<g>0.000000000e+00</g>
<b>0.000000000e+00</b>
<a>1.000000000e+00</a>
</color>
<texcoord>
<u>0.000000000e+00</u>
<v>0.000000000e+00</v>
</texcoord>
</second>
</item>
</vertices>
<primitives class_id="10" tracking_level="0" version="0">
<count>1</count>
<item_version>0</item_version>
<item class_id="11" tracking_level="0" version="0">
<i0>0</i0>
<i1>1</i1>
<i2>2</i2>
</item>
</primitives>
<viewport class_id="12" tracking_level="0" version="0">
<left>0</left>
<right>800</right>
<top>0</top>
<bottom>600</bottom>
<near>0.000000000e+00</near>
<far>1.000000000e+00</far>
</viewport>
</item>
</drawcalls>
<textures class_id="13" tracking_level="0" version="0">
<count>0</count>
<bucket_count>1</bucket_count>
<item_version>0</item_version>
</textures>
</cgltrace>
</boost_serialization>

View file

@ -11,6 +11,7 @@
#include <cocogfx/include/bmp.hpp>
#include <cocogfx/include/fixed.hpp>
#include <cocogfx/include/math.hpp>
#include "common.h"
using namespace cocogfx;
@ -77,19 +78,15 @@ static float EdgeEquation(rast_edge_t edges[3],
return det;
}
static void ColorToFloat(float out[4], uint32_t color) {
out[0] = ((color >> 0) & 0xFF) / 255.0f;
out[1] = ((color >> 8) & 0xFF) / 255.0f;
out[2] = ((color >> 16) & 0xFF) / 255.0f;
out[3] = ((color >> 24) & 0xFF) / 255.0f;
}
// traverse model primitives and do tile assignment
uint32_t Binning(std::vector<uint8_t>& tilebuf,
std::vector<uint8_t>& primbuf,
const model_t& model,
const std::unordered_map<uint32_t, CGLTrace::vertex_t>& vertices,
const std::vector<CGLTrace::primitive_t>& primitives,
uint32_t width,
uint32_t height,
float near,
float far,
uint32_t tileSize) {
uint32_t tileLogSize = log2ceil(tileSize);
@ -97,50 +94,51 @@ uint32_t Binning(std::vector<uint8_t>& tilebuf,
std::map<uint32_t, std::vector<uint32_t>> tiles;
std::vector<rast_prim_t> rast_prims;
rast_prims.reserve(model.primitives.size());
rast_prims.reserve(primitives.size());
uint32_t num_prims = 0;
for (auto& primitive : model.primitives) {
for (auto& primitive : primitives) {
// get primitive vertices
auto& v0 = model.vertives.at(primitive.i0);
auto& v1 = model.vertives.at(primitive.i1);
auto& v2 = model.vertives.at(primitive.i2);
auto& v0 = vertices.at(primitive.i0);
auto& v1 = vertices.at(primitive.i1);
auto& v2 = vertices.at(primitive.i2);
auto& p0 = *(vec4d_f_t*)&v0;
auto& p1 = *(vec4d_f_t*)&v1;
auto& p2 = *(vec4d_f_t*)&v2;
auto& p0 = *(vec4d_f_t*)&v0.pos;
auto& p1 = *(vec4d_f_t*)&v1.pos;
auto& p2 = *(vec4d_f_t*)&v2.pos;
rast_edge_t edges[3];
rast_bbox_t bbox;
vec4d_f_t pn0, pn1, pn2;
vec4d_f_t ps0, ps1, ps2;
{
// Convert position from clip to 2D homogenous device space
vec4d_f_t q0, q1, q2;
ClipTo2DH(&q0, p0, width, height);
ClipTo2DH(&q1, p1, width, height);
ClipTo2DH(&q2, p2, width, height);
// Convert position from clip to 2D homogenous device space
ClipToHDC(&pn0, p0, 0, width, 0, height, near, far);
ClipToHDC(&pn1, p1, 0, width, 0, height, near, far);
ClipToHDC(&pn2, p2, 0, width, 0, height, near, far);
// Calculate edge equation
auto det = EdgeEquation(edges, q0, q1, q2);
auto det = EdgeEquation(edges, pn0, pn1, pn2);
if (det <= 0) {
// reject back-facing or degenerate triangles
continue;
}
}
}
{
// Convert position from clip to screen space
vec4d_f_t q0, q1, q2;
ClipToScreen(&q0, p0, width, height);
ClipToScreen(&q1, p1, width, height);
ClipToScreen(&q2, p2, width, height);
// Convert position from clip to screen space
ClipToScreen(&ps0, p0, 0, width, 0, height, near, far);
ClipToScreen(&ps1, p1, 0, width, 0, height, near, far);
ClipToScreen(&ps2, p2, 0, width, 0, height, near, far);
// Calculate bounding box
rect_f_t tmp;
auto _q0 = (vec2d_f_t*)&q0;
auto _q1 = (vec2d_f_t*)&q1;
auto _q2 = (vec2d_f_t*)&q2;
auto _q0 = (vec2d_f_t*)&ps0;
auto _q1 = (vec2d_f_t*)&ps1;
auto _q2 = (vec2d_f_t*)&ps2;
CalcBoundingBox(&tmp, *_q0, *_q1, *_q2);
bbox.left = std::max<int32_t>(0, tmp.left);
bbox.right = std::min<int32_t>(width, tmp.right);
@ -160,21 +158,20 @@ uint32_t Binning(std::vector<uint8_t>& tilebuf,
rast_prim.edges[0] = edges[0];
rast_prim.edges[1] = edges[1];
rast_prim.edges[2] = edges[2];
//printf("*** edge0=(%d, %d, %d)\n", edges[0].x.data(), edges[0].y.data(), edges[0].z.data());
//printf("*** edge1=(%d, %d, %d)\n", edges[1].x.data(), edges[1].y.data(), edges[1].z.data());
//printf("*** edge2=(%d, %d, %d)\n", edges[2].x.data(), edges[2].y.data(), edges[2].z.data());
rast_prim.bbox = bbox;
float colors[3][4];
ColorToFloat(colors[0], v0.c);
ColorToFloat(colors[1], v1.c);
ColorToFloat(colors[2], v2.c);
ATTRIBUTE_DELTA(rast_prim.attribs.z, v0.z, v1.z, v2.z);
ATTRIBUTE_DELTA(rast_prim.attribs.r, colors[0][0], colors[1][0], colors[2][0]);
ATTRIBUTE_DELTA(rast_prim.attribs.g, colors[0][1], colors[1][1], colors[2][1]);
ATTRIBUTE_DELTA(rast_prim.attribs.b, colors[0][2], colors[1][2], colors[2][2]);
ATTRIBUTE_DELTA(rast_prim.attribs.a, colors[0][3], colors[1][3], colors[2][3]);
ATTRIBUTE_DELTA(rast_prim.attribs.u, v0.u, v1.u, v2.u);
ATTRIBUTE_DELTA(rast_prim.attribs.v, v0.v, v1.v, v2.v);
ATTRIBUTE_DELTA(rast_prim.attribs.z, ps0.z, ps1.z, ps2.z);
ATTRIBUTE_DELTA(rast_prim.attribs.r, v0.color.r, v1.color.r, v2.color.r);
ATTRIBUTE_DELTA(rast_prim.attribs.g, v0.color.g, v1.color.g, v2.color.g);
ATTRIBUTE_DELTA(rast_prim.attribs.b, v0.color.b, v1.color.b, v2.color.b);
ATTRIBUTE_DELTA(rast_prim.attribs.a, v0.color.a, v1.color.a, v2.color.a);
ATTRIBUTE_DELTA(rast_prim.attribs.u, v0.texcoord.u, v1.texcoord.u, v2.texcoord.u);
ATTRIBUTE_DELTA(rast_prim.attribs.v, v0.texcoord.v, v1.texcoord.v, v2.texcoord.v);
p = rast_prims.size();
rast_prims.push_back(rast_prim);
@ -306,7 +303,7 @@ int LoadImage(const char *filename,
img_format = FORMAT_A8;
break;
case 2:
img_format = FORMAT_A1R5G5B5;
img_format = FORMAT_R5G6B5;
break;
case 3:
img_format = FORMAT_R8G8B8;
@ -424,4 +421,72 @@ int CompareImages(const char* filename1,
}
return errors;
}
uint32_t toVXFormat(ePixelFormat format) {
switch (format) {
case FORMAT_A8R8G8B8: return TEX_FORMAT_A8R8G8B8; break;
case FORMAT_R5G6B5: return TEX_FORMAT_R5G6B5; break;
case FORMAT_A1R5G5B5: return TEX_FORMAT_A1R5G5B5; break;
case FORMAT_A4R4G4B4: return TEX_FORMAT_A4R4G4B4; break;
case FORMAT_A8L8: return TEX_FORMAT_A8L8; break;
case FORMAT_L8: return TEX_FORMAT_L8; break;
case FORMAT_A8: return TEX_FORMAT_A8; break;
default:
std::cout << "Error: invalid format: " << format << std::endl;
exit(1);
}
return 0;
}
uint32_t toVXCompare(CGLTrace::ecompare compare) {
switch (compare) {
case CGLTrace::COMPARE_NEVER: return ROP_DEPTH_FUNC_NEVER; break;
case CGLTrace::COMPARE_LESS: return ROP_DEPTH_FUNC_LESS; break;
case CGLTrace::COMPARE_EQUAL: return ROP_DEPTH_FUNC_EQUAL; break;
case CGLTrace::COMPARE_LEQUAL: return ROP_DEPTH_FUNC_LEQUAL; break;
case CGLTrace::COMPARE_GREATER: return ROP_DEPTH_FUNC_GREATER; break;
case CGLTrace::COMPARE_NOTEQUAL: return ROP_DEPTH_FUNC_NOTEQUAL; break;
case CGLTrace::COMPARE_GEQUAL: return ROP_DEPTH_FUNC_GEQUAL; break;
case CGLTrace::COMPARE_ALWAYS: return ROP_DEPTH_FUNC_ALWAYS; break;
default:
std::cout << "Error: invalid compare function: " << compare << std::endl;
exit(1);
}
return 0;
}
uint32_t toVXStencilOp(CGLTrace::eStencilOp op) {
switch (op) {
case CGLTrace::STENCIL_KEEP: return ROP_STENCIL_OP_KEEP; break;
case CGLTrace::STENCIL_REPLACE: return ROP_STENCIL_OP_REPLACE; break;
case CGLTrace::STENCIL_INCR: return ROP_STENCIL_OP_INCR; break;
case CGLTrace::STENCIL_DECR: return ROP_STENCIL_OP_DECR; break;
case CGLTrace::STENCIL_ZERO: return ROP_STENCIL_OP_ZERO; break;
case CGLTrace::STENCIL_INVERT: return ROP_STENCIL_OP_INVERT; break;
default:
std::cout << "Error: invalid stencil operation: " << op << std::endl;
exit(1);
}
return 0;
}
uint32_t toVXBlendFunc(CGLTrace::eBlendOp op) {
switch (op) {
case CGLTrace::BLEND_ZERO: return ROP_BLEND_FUNC_ZERO;
case CGLTrace::BLEND_ONE: return ROP_BLEND_FUNC_ONE;
case CGLTrace::BLEND_SRC_COLOR: return ROP_BLEND_FUNC_SRC_RGB;
case CGLTrace::BLEND_ONE_MINUS_SRC_COLOR: return ROP_BLEND_FUNC_ONE_MINUS_SRC_RGB;
case CGLTrace::BLEND_SRC_ALPHA: return ROP_BLEND_FUNC_SRC_A;
case CGLTrace::BLEND_ONE_MINUS_SRC_ALPHA: return ROP_BLEND_FUNC_ONE_MINUS_SRC_A;
case CGLTrace::BLEND_DST_ALPHA: return ROP_BLEND_FUNC_DST_A;
case CGLTrace::BLEND_ONE_MINUS_DST_ALPHA: return ROP_BLEND_FUNC_ONE_MINUS_DST_A;
case CGLTrace::BLEND_DST_COLOR: return ROP_BLEND_FUNC_DST_RGB;
case CGLTrace::BLEND_ONE_MINUS_DST_COLOR: return ROP_BLEND_FUNC_ONE_MINUS_DST_RGB;
case CGLTrace::BLEND_SRC_ALPHA_SATURATE: return ROP_BLEND_FUNC_ALPHA_SAT;
default:
std::cout << "Error: invalid blend function: " << op << std::endl;
exit(1);
}
return 0;
}

View file

@ -3,13 +3,24 @@
#include <bitmanip.h>
#include <cocogfx/include/format.hpp>
#include <cocogfx/include/blitter.hpp>
#include "model.h"
#include <cocogfx/include/cgltrace.hpp>
uint32_t toVXFormat(cocogfx::ePixelFormat format);
uint32_t toVXCompare(cocogfx::CGLTrace::ecompare compare);
uint32_t toVXStencilOp(cocogfx::CGLTrace::eStencilOp op);
uint32_t toVXBlendFunc(cocogfx::CGLTrace::eBlendOp op);
uint32_t Binning(std::vector<uint8_t>& tilebuf,
std::vector<uint8_t>& primbuf,
const model_t& model,
const std::unordered_map<uint32_t, cocogfx::CGLTrace::vertex_t>& vertices,
const std::vector<cocogfx::CGLTrace::primitive_t>& primitives,
uint32_t width,
uint32_t height,
float near,
float far,
uint32_t tileSize);
int LoadImage(const char *filename,

View file

@ -160,6 +160,7 @@ int main(int argc, char *argv[]) {
// check power of two support
if (!ispow2(src_width) || !ispow2(src_height)) {
std::cout << "Error: only power of two textures supported: width=" << src_width << ", heigth=" << src_height << std::endl;
cleanup();
return -1;
}
uint32_t src_bpp = Format::GetInfo(eformat).BytePerPixel;
@ -186,6 +187,7 @@ int main(int argc, char *argv[]) {
RT_CHECK(vx_dev_caps(device, VX_CAPS_ISA_FLAGS, &isa_flags));
if (0 == (isa_flags & VX_ISA_EXT_TEX)) {
std::cout << "texture extension not supported!" << std::endl;
cleanup();
return -1;
}

2
third_party/cocogfx vendored

@ -1 +1 @@
Subproject commit eb441b788174e9bbc09c7207319e1bf87fc6b16f
Subproject commit 419de75711dc7f76c2be1eaec55b22d6195a2f29