xref: /aosp_15_r20/external/skia/tools/gpu/gl/GLTestContext.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1*c8dee2aaSAndroid Build Coastguard Worker /*
2*c8dee2aaSAndroid Build Coastguard Worker  * Copyright 2013 Google Inc.
3*c8dee2aaSAndroid Build Coastguard Worker  *
4*c8dee2aaSAndroid Build Coastguard Worker  * Use of this source code is governed by a BSD-style license that can be
5*c8dee2aaSAndroid Build Coastguard Worker  * found in the LICENSE file.
6*c8dee2aaSAndroid Build Coastguard Worker  */
7*c8dee2aaSAndroid Build Coastguard Worker 
8*c8dee2aaSAndroid Build Coastguard Worker #include "tools/gpu/gl/GLTestContext.h"
9*c8dee2aaSAndroid Build Coastguard Worker 
10*c8dee2aaSAndroid Build Coastguard Worker #include "include/gpu/ganesh/GrDirectContext.h"
11*c8dee2aaSAndroid Build Coastguard Worker #include "include/gpu/ganesh/gl/GrGLDirectContext.h"
12*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/ganesh/gl/GrGLUtil.h"
13*c8dee2aaSAndroid Build Coastguard Worker #include "tools/gpu/GpuTimer.h"
14*c8dee2aaSAndroid Build Coastguard Worker 
15*c8dee2aaSAndroid Build Coastguard Worker namespace {
16*c8dee2aaSAndroid Build Coastguard Worker 
17*c8dee2aaSAndroid Build Coastguard Worker class GLGpuTimer : public sk_gpu_test::GpuTimer {
18*c8dee2aaSAndroid Build Coastguard Worker public:
19*c8dee2aaSAndroid Build Coastguard Worker     static std::unique_ptr<GLGpuTimer> MakeIfSupported(const sk_gpu_test::GLTestContext*);
20*c8dee2aaSAndroid Build Coastguard Worker 
21*c8dee2aaSAndroid Build Coastguard Worker     QueryStatus checkQueryStatus(sk_gpu_test::PlatformTimerQuery) override;
22*c8dee2aaSAndroid Build Coastguard Worker     std::chrono::nanoseconds getTimeElapsed(sk_gpu_test::PlatformTimerQuery) override;
23*c8dee2aaSAndroid Build Coastguard Worker     void deleteQuery(sk_gpu_test::PlatformTimerQuery) override;
24*c8dee2aaSAndroid Build Coastguard Worker 
25*c8dee2aaSAndroid Build Coastguard Worker private:
26*c8dee2aaSAndroid Build Coastguard Worker     GLGpuTimer(bool disjointSupport, const sk_gpu_test::GLTestContext*, const char* ext = "");
27*c8dee2aaSAndroid Build Coastguard Worker     bool validate() const;
28*c8dee2aaSAndroid Build Coastguard Worker 
29*c8dee2aaSAndroid Build Coastguard Worker     sk_gpu_test::PlatformTimerQuery onQueueTimerStart() const override;
30*c8dee2aaSAndroid Build Coastguard Worker     void onQueueTimerStop(sk_gpu_test::PlatformTimerQuery) const override;
31*c8dee2aaSAndroid Build Coastguard Worker 
32*c8dee2aaSAndroid Build Coastguard Worker     inline static constexpr GrGLenum GL_QUERY_RESULT            = 0x8866;
33*c8dee2aaSAndroid Build Coastguard Worker     inline static constexpr GrGLenum GL_QUERY_RESULT_AVAILABLE  = 0x8867;
34*c8dee2aaSAndroid Build Coastguard Worker     inline static constexpr GrGLenum GL_TIME_ELAPSED            = 0x88bf;
35*c8dee2aaSAndroid Build Coastguard Worker     inline static constexpr GrGLenum GL_GPU_DISJOINT            = 0x8fbb;
36*c8dee2aaSAndroid Build Coastguard Worker 
37*c8dee2aaSAndroid Build Coastguard Worker     typedef void (GR_GL_FUNCTION_TYPE* GLGetIntegervProc) (GrGLenum, GrGLint*);
38*c8dee2aaSAndroid Build Coastguard Worker     typedef void (GR_GL_FUNCTION_TYPE* GLGenQueriesProc) (GrGLsizei, GrGLuint*);
39*c8dee2aaSAndroid Build Coastguard Worker     typedef void (GR_GL_FUNCTION_TYPE* GLDeleteQueriesProc) (GrGLsizei, const GrGLuint*);
40*c8dee2aaSAndroid Build Coastguard Worker     typedef void (GR_GL_FUNCTION_TYPE* GLBeginQueryProc) (GrGLenum, GrGLuint);
41*c8dee2aaSAndroid Build Coastguard Worker     typedef void (GR_GL_FUNCTION_TYPE* GLEndQueryProc) (GrGLenum);
42*c8dee2aaSAndroid Build Coastguard Worker     typedef void (GR_GL_FUNCTION_TYPE* GLGetQueryObjectuivProc) (GrGLuint, GrGLenum, GrGLuint*);
43*c8dee2aaSAndroid Build Coastguard Worker     typedef void (GR_GL_FUNCTION_TYPE* GLGetQueryObjectui64vProc) (GrGLuint, GrGLenum, GrGLuint64*);
44*c8dee2aaSAndroid Build Coastguard Worker 
45*c8dee2aaSAndroid Build Coastguard Worker     GLGetIntegervProc           fGLGetIntegerv;
46*c8dee2aaSAndroid Build Coastguard Worker     GLGenQueriesProc            fGLGenQueries;
47*c8dee2aaSAndroid Build Coastguard Worker     GLDeleteQueriesProc         fGLDeleteQueries;
48*c8dee2aaSAndroid Build Coastguard Worker     GLBeginQueryProc            fGLBeginQuery;
49*c8dee2aaSAndroid Build Coastguard Worker     GLEndQueryProc              fGLEndQuery;
50*c8dee2aaSAndroid Build Coastguard Worker     GLGetQueryObjectuivProc     fGLGetQueryObjectuiv;
51*c8dee2aaSAndroid Build Coastguard Worker     GLGetQueryObjectui64vProc   fGLGetQueryObjectui64v;
52*c8dee2aaSAndroid Build Coastguard Worker 
53*c8dee2aaSAndroid Build Coastguard Worker 
54*c8dee2aaSAndroid Build Coastguard Worker     using INHERITED = sk_gpu_test::GpuTimer;
55*c8dee2aaSAndroid Build Coastguard Worker };
56*c8dee2aaSAndroid Build Coastguard Worker 
MakeIfSupported(const sk_gpu_test::GLTestContext * ctx)57*c8dee2aaSAndroid Build Coastguard Worker std::unique_ptr<GLGpuTimer> GLGpuTimer::MakeIfSupported(const sk_gpu_test::GLTestContext* ctx) {
58*c8dee2aaSAndroid Build Coastguard Worker     std::unique_ptr<GLGpuTimer> ret;
59*c8dee2aaSAndroid Build Coastguard Worker     const GrGLInterface* gl = ctx->gl();
60*c8dee2aaSAndroid Build Coastguard Worker     if (gl->fExtensions.has("GL_EXT_disjoint_timer_query")) {
61*c8dee2aaSAndroid Build Coastguard Worker         ret.reset(new GLGpuTimer(true, ctx, "EXT"));
62*c8dee2aaSAndroid Build Coastguard Worker     } else if (kGL_GrGLStandard == gl->fStandard &&
63*c8dee2aaSAndroid Build Coastguard Worker                (GrGLGetVersion(gl) > GR_GL_VER(3,3) || gl->fExtensions.has("GL_ARB_timer_query"))) {
64*c8dee2aaSAndroid Build Coastguard Worker         ret.reset(new GLGpuTimer(false, ctx));
65*c8dee2aaSAndroid Build Coastguard Worker     } else if (gl->fExtensions.has("GL_EXT_timer_query")) {
66*c8dee2aaSAndroid Build Coastguard Worker         ret.reset(new GLGpuTimer(false, ctx, "EXT"));
67*c8dee2aaSAndroid Build Coastguard Worker     }
68*c8dee2aaSAndroid Build Coastguard Worker     if (ret && !ret->validate()) {
69*c8dee2aaSAndroid Build Coastguard Worker         ret = nullptr;
70*c8dee2aaSAndroid Build Coastguard Worker     }
71*c8dee2aaSAndroid Build Coastguard Worker     return ret;
72*c8dee2aaSAndroid Build Coastguard Worker }
73*c8dee2aaSAndroid Build Coastguard Worker 
GLGpuTimer(bool disjointSupport,const sk_gpu_test::GLTestContext * ctx,const char * ext)74*c8dee2aaSAndroid Build Coastguard Worker GLGpuTimer::GLGpuTimer(bool disjointSupport, const sk_gpu_test::GLTestContext* ctx, const char* ext)
75*c8dee2aaSAndroid Build Coastguard Worker     : INHERITED(disjointSupport) {
76*c8dee2aaSAndroid Build Coastguard Worker     ctx->getGLProcAddress(&fGLGetIntegerv, "glGetIntegerv");
77*c8dee2aaSAndroid Build Coastguard Worker     ctx->getGLProcAddress(&fGLGenQueries, "glGenQueries", ext);
78*c8dee2aaSAndroid Build Coastguard Worker     ctx->getGLProcAddress(&fGLDeleteQueries, "glDeleteQueries", ext);
79*c8dee2aaSAndroid Build Coastguard Worker     ctx->getGLProcAddress(&fGLBeginQuery, "glBeginQuery", ext);
80*c8dee2aaSAndroid Build Coastguard Worker     ctx->getGLProcAddress(&fGLEndQuery, "glEndQuery", ext);
81*c8dee2aaSAndroid Build Coastguard Worker     ctx->getGLProcAddress(&fGLGetQueryObjectuiv, "glGetQueryObjectuiv", ext);
82*c8dee2aaSAndroid Build Coastguard Worker     ctx->getGLProcAddress(&fGLGetQueryObjectui64v, "glGetQueryObjectui64v", ext);
83*c8dee2aaSAndroid Build Coastguard Worker }
84*c8dee2aaSAndroid Build Coastguard Worker 
validate() const85*c8dee2aaSAndroid Build Coastguard Worker bool GLGpuTimer::validate() const {
86*c8dee2aaSAndroid Build Coastguard Worker     return fGLGetIntegerv && fGLGenQueries && fGLDeleteQueries && fGLBeginQuery && fGLEndQuery &&
87*c8dee2aaSAndroid Build Coastguard Worker            fGLGetQueryObjectuiv && fGLGetQueryObjectui64v;
88*c8dee2aaSAndroid Build Coastguard Worker }
89*c8dee2aaSAndroid Build Coastguard Worker 
onQueueTimerStart() const90*c8dee2aaSAndroid Build Coastguard Worker sk_gpu_test::PlatformTimerQuery GLGpuTimer::onQueueTimerStart() const {
91*c8dee2aaSAndroid Build Coastguard Worker     GrGLuint queryID;
92*c8dee2aaSAndroid Build Coastguard Worker     fGLGenQueries(1, &queryID);
93*c8dee2aaSAndroid Build Coastguard Worker     if (!queryID) {
94*c8dee2aaSAndroid Build Coastguard Worker         return sk_gpu_test::kInvalidTimerQuery;
95*c8dee2aaSAndroid Build Coastguard Worker     }
96*c8dee2aaSAndroid Build Coastguard Worker     if (this->disjointSupport()) {
97*c8dee2aaSAndroid Build Coastguard Worker         // Clear the disjoint flag.
98*c8dee2aaSAndroid Build Coastguard Worker         GrGLint disjoint;
99*c8dee2aaSAndroid Build Coastguard Worker         fGLGetIntegerv(GL_GPU_DISJOINT, &disjoint);
100*c8dee2aaSAndroid Build Coastguard Worker     }
101*c8dee2aaSAndroid Build Coastguard Worker     fGLBeginQuery(GL_TIME_ELAPSED, queryID);
102*c8dee2aaSAndroid Build Coastguard Worker     return static_cast<sk_gpu_test::PlatformTimerQuery>(queryID);
103*c8dee2aaSAndroid Build Coastguard Worker }
104*c8dee2aaSAndroid Build Coastguard Worker 
onQueueTimerStop(sk_gpu_test::PlatformTimerQuery platformTimer) const105*c8dee2aaSAndroid Build Coastguard Worker void GLGpuTimer::onQueueTimerStop(sk_gpu_test::PlatformTimerQuery platformTimer) const {
106*c8dee2aaSAndroid Build Coastguard Worker     if (sk_gpu_test::kInvalidTimerQuery == platformTimer) {
107*c8dee2aaSAndroid Build Coastguard Worker         return;
108*c8dee2aaSAndroid Build Coastguard Worker     }
109*c8dee2aaSAndroid Build Coastguard Worker     fGLEndQuery(GL_TIME_ELAPSED);
110*c8dee2aaSAndroid Build Coastguard Worker }
111*c8dee2aaSAndroid Build Coastguard Worker 
112*c8dee2aaSAndroid Build Coastguard Worker sk_gpu_test::GpuTimer::QueryStatus
checkQueryStatus(sk_gpu_test::PlatformTimerQuery platformTimer)113*c8dee2aaSAndroid Build Coastguard Worker GLGpuTimer::checkQueryStatus(sk_gpu_test::PlatformTimerQuery platformTimer) {
114*c8dee2aaSAndroid Build Coastguard Worker     const GrGLuint queryID = static_cast<GrGLuint>(platformTimer);
115*c8dee2aaSAndroid Build Coastguard Worker     if (!queryID) {
116*c8dee2aaSAndroid Build Coastguard Worker         return QueryStatus::kInvalid;
117*c8dee2aaSAndroid Build Coastguard Worker     }
118*c8dee2aaSAndroid Build Coastguard Worker     GrGLuint available = 0;
119*c8dee2aaSAndroid Build Coastguard Worker     fGLGetQueryObjectuiv(queryID, GL_QUERY_RESULT_AVAILABLE, &available);
120*c8dee2aaSAndroid Build Coastguard Worker     if (!available) {
121*c8dee2aaSAndroid Build Coastguard Worker         return QueryStatus::kPending;
122*c8dee2aaSAndroid Build Coastguard Worker     }
123*c8dee2aaSAndroid Build Coastguard Worker     if (this->disjointSupport()) {
124*c8dee2aaSAndroid Build Coastguard Worker         GrGLint disjoint = 1;
125*c8dee2aaSAndroid Build Coastguard Worker         fGLGetIntegerv(GL_GPU_DISJOINT, &disjoint);
126*c8dee2aaSAndroid Build Coastguard Worker         if (disjoint) {
127*c8dee2aaSAndroid Build Coastguard Worker             return QueryStatus::kDisjoint;
128*c8dee2aaSAndroid Build Coastguard Worker         }
129*c8dee2aaSAndroid Build Coastguard Worker     }
130*c8dee2aaSAndroid Build Coastguard Worker     return QueryStatus::kAccurate;
131*c8dee2aaSAndroid Build Coastguard Worker }
132*c8dee2aaSAndroid Build Coastguard Worker 
getTimeElapsed(sk_gpu_test::PlatformTimerQuery platformTimer)133*c8dee2aaSAndroid Build Coastguard Worker std::chrono::nanoseconds GLGpuTimer::getTimeElapsed(sk_gpu_test::PlatformTimerQuery platformTimer) {
134*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(this->checkQueryStatus(platformTimer) >= QueryStatus::kDisjoint);
135*c8dee2aaSAndroid Build Coastguard Worker     const GrGLuint queryID = static_cast<GrGLuint>(platformTimer);
136*c8dee2aaSAndroid Build Coastguard Worker     GrGLuint64 nanoseconds;
137*c8dee2aaSAndroid Build Coastguard Worker     fGLGetQueryObjectui64v(queryID, GL_QUERY_RESULT, &nanoseconds);
138*c8dee2aaSAndroid Build Coastguard Worker     return std::chrono::nanoseconds(nanoseconds);
139*c8dee2aaSAndroid Build Coastguard Worker }
140*c8dee2aaSAndroid Build Coastguard Worker 
deleteQuery(sk_gpu_test::PlatformTimerQuery platformTimer)141*c8dee2aaSAndroid Build Coastguard Worker void GLGpuTimer::deleteQuery(sk_gpu_test::PlatformTimerQuery platformTimer) {
142*c8dee2aaSAndroid Build Coastguard Worker     const GrGLuint queryID = static_cast<GrGLuint>(platformTimer);
143*c8dee2aaSAndroid Build Coastguard Worker     fGLDeleteQueries(1, &queryID);
144*c8dee2aaSAndroid Build Coastguard Worker }
145*c8dee2aaSAndroid Build Coastguard Worker 
146*c8dee2aaSAndroid Build Coastguard Worker static_assert(sizeof(GrGLuint) <= sizeof(sk_gpu_test::PlatformTimerQuery));
147*c8dee2aaSAndroid Build Coastguard Worker 
148*c8dee2aaSAndroid Build Coastguard Worker }  // anonymous namespace
149*c8dee2aaSAndroid Build Coastguard Worker 
150*c8dee2aaSAndroid Build Coastguard Worker namespace sk_gpu_test {
151*c8dee2aaSAndroid Build Coastguard Worker 
GLTestContext()152*c8dee2aaSAndroid Build Coastguard Worker GLTestContext::GLTestContext() : TestContext() {}
153*c8dee2aaSAndroid Build Coastguard Worker 
~GLTestContext()154*c8dee2aaSAndroid Build Coastguard Worker GLTestContext::~GLTestContext() {
155*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(!fGLInterface);
156*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(!fOriginalGLInterface);
157*c8dee2aaSAndroid Build Coastguard Worker }
158*c8dee2aaSAndroid Build Coastguard Worker 
isValid() const159*c8dee2aaSAndroid Build Coastguard Worker bool GLTestContext::isValid() const {
160*c8dee2aaSAndroid Build Coastguard Worker     return SkToBool(this->gl());
161*c8dee2aaSAndroid Build Coastguard Worker }
162*c8dee2aaSAndroid Build Coastguard Worker 
fence_is_supported(const GLTestContext * ctx)163*c8dee2aaSAndroid Build Coastguard Worker static bool fence_is_supported(const GLTestContext* ctx) {
164*c8dee2aaSAndroid Build Coastguard Worker     if (kGL_GrGLStandard == ctx->gl()->fStandard) {
165*c8dee2aaSAndroid Build Coastguard Worker         if (GrGLGetVersion(ctx->gl()) < GR_GL_VER(3, 2) &&
166*c8dee2aaSAndroid Build Coastguard Worker             !ctx->gl()->hasExtension("GL_ARB_sync")) {
167*c8dee2aaSAndroid Build Coastguard Worker             return false;
168*c8dee2aaSAndroid Build Coastguard Worker         }
169*c8dee2aaSAndroid Build Coastguard Worker         return true;
170*c8dee2aaSAndroid Build Coastguard Worker     } else {
171*c8dee2aaSAndroid Build Coastguard Worker         if (ctx->gl()->hasExtension("GL_APPLE_sync")) {
172*c8dee2aaSAndroid Build Coastguard Worker             return true;
173*c8dee2aaSAndroid Build Coastguard Worker         } else if (ctx->gl()->hasExtension("GL_NV_fence")) {
174*c8dee2aaSAndroid Build Coastguard Worker             return true;
175*c8dee2aaSAndroid Build Coastguard Worker         } else if (GrGLGetVersion(ctx->gl()) >= GR_GL_VER(3, 0)) {
176*c8dee2aaSAndroid Build Coastguard Worker             return true;
177*c8dee2aaSAndroid Build Coastguard Worker         } else {
178*c8dee2aaSAndroid Build Coastguard Worker             return false;
179*c8dee2aaSAndroid Build Coastguard Worker         }
180*c8dee2aaSAndroid Build Coastguard Worker     }
181*c8dee2aaSAndroid Build Coastguard Worker }
182*c8dee2aaSAndroid Build Coastguard Worker 
init(sk_sp<const GrGLInterface> gl)183*c8dee2aaSAndroid Build Coastguard Worker void GLTestContext::init(sk_sp<const GrGLInterface> gl) {
184*c8dee2aaSAndroid Build Coastguard Worker     fGLInterface = std::move(gl);
185*c8dee2aaSAndroid Build Coastguard Worker     fOriginalGLInterface = fGLInterface;
186*c8dee2aaSAndroid Build Coastguard Worker     fFenceSupport = fence_is_supported(this);
187*c8dee2aaSAndroid Build Coastguard Worker     fGpuTimer = GLGpuTimer::MakeIfSupported(this);
188*c8dee2aaSAndroid Build Coastguard Worker }
189*c8dee2aaSAndroid Build Coastguard Worker 
teardown()190*c8dee2aaSAndroid Build Coastguard Worker void GLTestContext::teardown() {
191*c8dee2aaSAndroid Build Coastguard Worker     fGLInterface.reset();
192*c8dee2aaSAndroid Build Coastguard Worker     fOriginalGLInterface.reset();
193*c8dee2aaSAndroid Build Coastguard Worker     INHERITED::teardown();
194*c8dee2aaSAndroid Build Coastguard Worker }
195*c8dee2aaSAndroid Build Coastguard Worker 
testAbandon()196*c8dee2aaSAndroid Build Coastguard Worker void GLTestContext::testAbandon() {
197*c8dee2aaSAndroid Build Coastguard Worker     INHERITED::testAbandon();
198*c8dee2aaSAndroid Build Coastguard Worker #if defined(GPU_TEST_UTILS)
199*c8dee2aaSAndroid Build Coastguard Worker     if (fGLInterface) {
200*c8dee2aaSAndroid Build Coastguard Worker         fGLInterface->abandon();
201*c8dee2aaSAndroid Build Coastguard Worker         fOriginalGLInterface->abandon();
202*c8dee2aaSAndroid Build Coastguard Worker     }
203*c8dee2aaSAndroid Build Coastguard Worker #endif
204*c8dee2aaSAndroid Build Coastguard Worker }
205*c8dee2aaSAndroid Build Coastguard Worker 
overrideVersion(const char * version,const char * shadingLanguageVersion)206*c8dee2aaSAndroid Build Coastguard Worker void GLTestContext::overrideVersion(const char* version, const char* shadingLanguageVersion) {
207*c8dee2aaSAndroid Build Coastguard Worker     // GrGLFunction has both a limited capture size and doesn't call a destructor when it is
208*c8dee2aaSAndroid Build Coastguard Worker     // initialized with a lambda. So here we're trusting fOriginalGLInterface will be kept alive.
209*c8dee2aaSAndroid Build Coastguard Worker     auto getString = [wrapped = &fOriginalGLInterface->fFunctions.fGetString,
210*c8dee2aaSAndroid Build Coastguard Worker                       version,
211*c8dee2aaSAndroid Build Coastguard Worker                       shadingLanguageVersion](GrGLenum name) {
212*c8dee2aaSAndroid Build Coastguard Worker         if (name == GR_GL_VERSION) {
213*c8dee2aaSAndroid Build Coastguard Worker             return reinterpret_cast<const GrGLubyte*>(version);
214*c8dee2aaSAndroid Build Coastguard Worker         } else if (name == GR_GL_SHADING_LANGUAGE_VERSION) {
215*c8dee2aaSAndroid Build Coastguard Worker             return reinterpret_cast<const GrGLubyte*>(shadingLanguageVersion);
216*c8dee2aaSAndroid Build Coastguard Worker         }
217*c8dee2aaSAndroid Build Coastguard Worker         return (*wrapped)(name);
218*c8dee2aaSAndroid Build Coastguard Worker     };
219*c8dee2aaSAndroid Build Coastguard Worker     auto newInterface = sk_make_sp<GrGLInterface>(*fOriginalGLInterface);
220*c8dee2aaSAndroid Build Coastguard Worker     newInterface->fFunctions.fGetString = getString;
221*c8dee2aaSAndroid Build Coastguard Worker     fGLInterface = std::move(newInterface);
222*c8dee2aaSAndroid Build Coastguard Worker }
223*c8dee2aaSAndroid Build Coastguard Worker 
makeContext(const GrContextOptions & options)224*c8dee2aaSAndroid Build Coastguard Worker sk_sp<GrDirectContext> GLTestContext::makeContext(const GrContextOptions& options) {
225*c8dee2aaSAndroid Build Coastguard Worker     return GrDirectContexts::MakeGL(fGLInterface, options);
226*c8dee2aaSAndroid Build Coastguard Worker }
227*c8dee2aaSAndroid Build Coastguard Worker 
228*c8dee2aaSAndroid Build Coastguard Worker }  // namespace sk_gpu_test
229