pax_global_header00006660000000000000000000000064150717206640014522gustar00rootroot0000000000000052 comment=9ba67e6680f03f31f2b1741a53e8fd549be82cbe raspberrypi-libpisp-9ba67e6/000077500000000000000000000000001507172066400161515ustar00rootroot00000000000000raspberrypi-libpisp-9ba67e6/.clang-format000066400000000000000000000062521507172066400205310ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # # clang-format configuration file. Intended for clang-format >= 7. # # For more information, see: # # Documentation/process/clang-format.rst # https://clang.llvm.org/docs/ClangFormat.html # https://clang.llvm.org/docs/ClangFormatStyleOptions.html # --- Language: Cpp AccessModifierOffset: -4 AlignAfterOpenBracket: Align AlignConsecutiveAssignments: false AlignConsecutiveDeclarations: false AlignEscapedNewlines: Right AlignOperands: true AlignTrailingComments: false AllowAllParametersOfDeclarationOnNextLine: false AllowShortBlocksOnASingleLine: false AllowShortCaseLabelsOnASingleLine: false AllowShortLambdasOnASingleLine: Inline AllowShortFunctionsOnASingleLine: InlineOnly AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: Yes BinPackArguments: true BinPackParameters: true BreakBeforeBraces: Allman BraceWrapping: SplitEmptyFunction: true SplitEmptyRecord: true SplitEmptyNamespace: true AfterNamespace: false AfterClass: true BreakBeforeBinaryOperators: None BreakBeforeInheritanceComma: false BreakInheritanceList: BeforeColon BreakBeforeTernaryOperators: true BreakConstructorInitializers: BeforeColon BreakAfterJavaFieldAnnotations: false BreakStringLiterals: false CommentPragmas: '^ IWYU pragma:' CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: false Cpp11BracedListStyle: false DerivePointerAlignment: false DisableFormat: false ExperimentalAutoDetectBinPacking: false FixNamespaceComments: true ForEachMacros: - 'udev_list_entry_foreach' IncludeBlocks: Preserve IncludeCategories: - Regex: '.*' Priority: 1 IncludeIsMainRegex: '(_test)?$' IndentCaseLabels: false IndentPPDirectives: None IndentWrappedFunctionNames: false JavaScriptQuotes: Leave JavaScriptWrapImports: true KeepEmptyLinesAtTheStartOfBlocks: false MacroBlockBegin: '' MacroBlockEnd: '' MaxEmptyLinesToKeep: 1 NamespaceIndentation: None ObjCBinPackProtocolList: Auto ObjCBlockIndentWidth: 8 ObjCSpaceAfterProperty: true ObjCSpaceBeforeProtocolList: true # Taken from git's rules PenaltyBreakAssignment: 10 PenaltyBreakBeforeFirstCallParameter: 30 PenaltyBreakComment: 10 PenaltyBreakFirstLessLess: 0 PenaltyBreakString: 10 PenaltyBreakTemplateDeclaration: 10 PenaltyExcessCharacter: 100 PenaltyReturnTypeOnItsOwnLine: 60 PointerAlignment: Right ReflowComments: false SortIncludes: true SortUsingDeclarations: true SpaceAfterCStyleCast: false SpaceAfterTemplateKeyword: true SpaceAfterLogicalNot: false #SpaceBeforeCaseColon: false SpaceBeforeAssignmentOperators: true SpaceBeforeCpp11BracedList: true SpaceBeforeCtorInitializerColon: true SpaceBeforeInheritanceColon: true SpaceBeforeParens: ControlStatements SpaceBeforeRangeBasedForLoopColon: true SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 SpacesInAngles: false SpacesInContainerLiterals: false SpacesInCStyleCastParentheses: false SpacesInParentheses: false SpacesInSquareBrackets: false IndentWidth: 4 TabWidth: 4 UseTab: Always ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 ColumnLimit: 120 ... raspberrypi-libpisp-9ba67e6/.github/000077500000000000000000000000001507172066400175115ustar00rootroot00000000000000raspberrypi-libpisp-9ba67e6/.github/workflows/000077500000000000000000000000001507172066400215465ustar00rootroot00000000000000raspberrypi-libpisp-9ba67e6/.github/workflows/gen_orig.yml000066400000000000000000000025221507172066400240630ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (C) 2023, Raspberry Pi Ltd name: Generate source release tarball run-name: Generating source release tarball on: push: tags: # vX.Y.Z - 'v[0-9]+.[0-9]+.[0-9]+' workflow_dispatch: jobs: publish_tarball: permissions: contents: write runs-on: ubuntu-latest steps: - name: Install dependencies run: | sudo apt-get update sudo apt-get install -y meson pkgconf libboost-log-dev libboost-thread-dev nlohmann-json3-dev - name: Check out repository code uses: actions/checkout@v4 with: fetch-depth: 0 # Required for 'git describe' to work - name: Generate tarball run: | meson setup build meson dist --no-tests --include-subprojects -C build - name: Check for output tarball run: | TARBALL="libpisp-$(echo "$GITHUB_REF_NAME" | sed 's/^v//').tar.xz" if ! [ -f "build/meson-dist/$TARBALL" ]; then echo "Expected tarball not found - $TARBALL" echo "Does 'version' in meson.build match the tag?" exit 1 fi - name: Release tarball uses: softprops/action-gh-release@v1 with: files: build/meson-dist/*.tar.xz - if: failure() run: cat build/meson-logs/meson-log.txt raspberrypi-libpisp-9ba67e6/.github/workflows/libpisp-build-test.yaml000066400000000000000000000017241507172066400261520ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (C) 2023, Raspberry Pi Ltd name: libpisp build test on: workflow_dispatch: pull_request: branches: [ main ] jobs: build-test: strategy: matrix: os: [ ubuntu-latest, ubuntu-24.04-arm ] compiler: [ g++, clang++ ] build_type: [ release, debug ] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v3 with: fetch-depth: 1 clean: true - name: Install meson and ninja run: pip3 install --user meson ninja - name: Install boost dependencies run: sudo apt install -y libboost-log-dev libboost-thread-dev - name: Configure meson run: CXX=${{matrix.compiler}} meson setup build_${{matrix.compiler}}_${{matrix.build_type}} -Dbuildtype=${{matrix.build_type}} -Dexamples=true timeout-minutes: 5 - name: Build run: ninja -C build_${{matrix.compiler}}_${{matrix.build_type}} timeout-minutes: 10 raspberrypi-libpisp-9ba67e6/.github/workflows/libpisp-style-checker.yml000066400000000000000000000006631507172066400265000ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (C) 2023, Raspberry Pi Ltd name: libpisp style checker on: pull_request: branches: [ main ] jobs: style-check: runs-on: [ ubuntu-latest ] steps: - uses: actions/checkout@v3 with: fetch-depth: 0 clean: true - name: Check style run: ${{github.workspace}}/utils/checkstyle.py $(git log --format=%P -1 | awk '{print $1 ".." $2}') raspberrypi-libpisp-9ba67e6/.github/workflows/pisp-verification.test.yaml000066400000000000000000000026371507172066400270530ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (C) 2023, Raspberry Pi Ltd name: pisp verification tests on: workflow_dispatch: pull_request: branches: [ main ] env: BE_TEST_DIR: "${{github.workspace}}/../be_test" LKG_DIR: "${{github.workspace}}/../lkg" TESTS_DIR: "/home/pi/pisp_tests" jobs: build-test: runs-on: [ self-hosted, pi5 ] steps: - uses: actions/checkout@v3 with: fetch-depth: 1 clean: true - name: Configure meson run: meson setup build -Dbuildtype=debug -Dexamples=true timeout-minutes: 5 - name: Build run: ninja -C build timeout-minutes: 10 run-test: runs-on: [ self-hosted, pi5 ] needs: build-test steps: - name: Run verification tests run: LD_LIBRARY_PATH=${{github.workspace}}/build/src LIBPISP_BE_CONFIG_FILE="${{github.workspace}}/src/libpisp/backend/backend_default_config.json" ${{env.BE_TEST_DIR}}/run_be_tests.py --hw --logall --test ${{env.BE_TEST_DIR}}/be_test ${{env.TESTS_DIR}}/back_end timeout-minutes: 20 - name: Run convert tests run: LD_LIBRARY_PATH=${{github.workspace}}/build/src LIBPISP_BE_CONFIG_FILE="${{github.workspace}}/src/libpisp/backend/backend_default_config.json" python3 ${{github.workspace}}/utils/test_convert.py ${{github.workspace}}/build/src/examples/convert --out /tmp/ --in $HOME/libpisp_conv/ --ref ~/libpisp_conv/ref/ timeout-minutes: 10 raspberrypi-libpisp-9ba67e6/.gitignore000066400000000000000000000000051507172066400201340ustar00rootroot00000000000000buildraspberrypi-libpisp-9ba67e6/LICENSE000077700000000000000000000000001507172066400231002LICENSES/BSD-2-Clause.txtustar00rootroot00000000000000raspberrypi-libpisp-9ba67e6/LICENSES/000077500000000000000000000000001507172066400173565ustar00rootroot00000000000000raspberrypi-libpisp-9ba67e6/LICENSES/BSD-2-Clause.txt000066400000000000000000000024451507172066400221050ustar00rootroot00000000000000BSD 2-Clause License Copyright (c) 2023, Raspberry Pi Ltd All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. raspberrypi-libpisp-9ba67e6/LICENSES/CC0-1.0.txt000066400000000000000000000154041507172066400207640ustar00rootroot00000000000000Creative Commons Legal Code CC0 1.0 Universal CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER. Statement of Purpose The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work"). Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others. For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights. 1. Copyright and Related Rights. A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following: i. the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work; ii. moral rights retained by the original author(s) and/or performer(s); iii. publicity and privacy rights pertaining to a person's image or likeness depicted in a Work; iv. rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below; v. rights protecting the extraction, dissemination, use and reuse of data in a Work; vi. database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and vii. other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof. 2. Waiver. To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose. 3. Public License Fallback. Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose. 4. Limitations and Disclaimers. a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document. b. Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law. c. Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work. d. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work. raspberrypi-libpisp-9ba67e6/LICENSES/GPL-2.0-only.txt000066400000000000000000000432541507172066400220250ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. raspberrypi-libpisp-9ba67e6/LICENSES/GPL-2.0-or-later.txt000066400000000000000000000423261507172066400225700ustar00rootroot00000000000000GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. raspberrypi-libpisp-9ba67e6/LICENSES/Linux-syscall-note.txt000066400000000000000000000011571507172066400236350ustar00rootroot00000000000000NOTE! This copyright does *not* cover user programs that use kernel services by normal system calls - this is merely considered normal use of the kernel, and does *not* fall under the heading of "derived work". Also note that the GPL below is copyrighted by the Free Software Foundation, but the instance of code that it refers to (the Linux kernel) is copyrighted by me and others who actually wrote it. Also note that the only valid version of the GPL as far as the kernel is concerned is _this_ particular version of the license (ie v2, not v2.2 or v3.x or whatever), unless explicitly otherwise stated. Linus Torvaldsraspberrypi-libpisp-9ba67e6/LICENSES/MIT.txt000066400000000000000000000021211507172066400205440ustar00rootroot00000000000000MIT License Copyright (c) 2013-2022 Niels Lohmann Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. raspberrypi-libpisp-9ba67e6/README.md000066400000000000000000000021721507172066400174320ustar00rootroot00000000000000# libpisp A helper library to generate run-time configuration for the Raspberry Pi ISP (PiSP), consisting of the Frontend and Backend hardware components. ## Building and installing To build, setup the meson project as follows: ```sh meson setup ``` To optionally disable the Boost logging library, add ``-Dlogging=disabled`` as an argument to the ``meson setup`` command. To compile and install the ``libpisp.so`` artefact: ```sh meson compile -C sudo meson install -C ``` ## Linking libpisp with an application libpisp can be built and linked as a [meson subproject](https://mesonbuild.com/Subprojects.html) by using an appropriate [libpisp.wrap](utils/libpisp.wrap) file and the following dependency declaration in the target project: ```meson libpisp_dep = dependency('libpisp', fallback : ['libpisp', 'libpisp_dep']) ``` Alternatively [pkg-config](https://www.freedesktop.org/wiki/Software/pkg-config/) can be used to locate ``libpisp.so`` installed in of the system directories for other build environments. ## License Copyright © 2023, Raspberry Pi Ltd. Released under the BSD-2-Clause License. raspberrypi-libpisp-9ba67e6/meson.build000066400000000000000000000005721507172066400203170ustar00rootroot00000000000000# SPDX-License-Identifier: CC0-1.0 # Copyright (C) 2023, Raspberry Pi Ltd project('libpisp', 'c', 'cpp', meson_version : '>= 0.58.0', version : '1.3.0', default_options : [ 'werror=true', 'warning_level=2', 'cpp_std=c++17', 'buildtype=release', ], license : 'BSD-2-Clause') subdir('src') raspberrypi-libpisp-9ba67e6/meson_options.txt000066400000000000000000000002631507172066400216070ustar00rootroot00000000000000# SPDX-License-Identifier: CC0-1.0 # Copyright (C) 2023, Raspberry Pi Ltd option('logging', type : 'feature', value : 'auto') option('examples', type : 'boolean', value : false) raspberrypi-libpisp-9ba67e6/src/000077500000000000000000000000001507172066400167405ustar00rootroot00000000000000raspberrypi-libpisp-9ba67e6/src/examples/000077500000000000000000000000001507172066400205565ustar00rootroot00000000000000raspberrypi-libpisp-9ba67e6/src/examples/convert.cpp000066400000000000000000000312101507172066400227370ustar00rootroot00000000000000 /* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (C) 2025 Raspberry Pi Ltd * * convert.cpp - libpisp simple image converter example */ #include #include #include #include #include #include #include #include #include #include #include #include "helpers/backend_device.hpp" #include "helpers/media_device.hpp" #include "libpisp/backend/backend.hpp" #include "libpisp/common/logging.hpp" #include "libpisp/common/utils.hpp" #include "libpisp/variants/variant.hpp" void read_plane(uint8_t *mem, std::ifstream &in, unsigned int width, unsigned int height, unsigned int file_stride, unsigned int buffer_stride) { width = std::min(width, file_stride); for (unsigned int y = 0; y < height; y++) { in.read((char *)mem + y * buffer_stride, width); in.seekg(file_stride - width, std::ios_base::cur); } } void write_plane(std::ofstream &out, uint8_t *mem, unsigned int width, unsigned int height, unsigned int file_stride, unsigned int buffer_stride) { width = std::min(width, file_stride); for (unsigned int y = 0; y < height; y++) { out.write((char *)mem + y * buffer_stride, width); for (unsigned int i = 0; i < file_stride - width; i++) out.put(0); } } void read_rgb888(std::array &mem, std::ifstream &in, unsigned int width, unsigned int height, unsigned int file_stride, unsigned int buffer_stride) { read_plane((uint8_t *)mem[0], in, width * 3, height, file_stride, buffer_stride); } void write_rgb888(std::ofstream &out, std::array &mem, unsigned int width, unsigned int height, unsigned int file_stride, unsigned int buffer_stride) { write_plane(out, (uint8_t *)mem[0], width * 3, height, file_stride, buffer_stride); } void read_32(std::array &mem, std::ifstream &in, unsigned int width, unsigned int height, unsigned int file_stride, unsigned int buffer_stride) { read_plane((uint8_t *)mem[0], in, width * 4, height, file_stride, buffer_stride); } void write_32(std::ofstream &out, std::array &mem, unsigned int width, unsigned int height, unsigned int file_stride, unsigned int buffer_stride) { write_plane(out, (uint8_t *)mem[0], width * 4, height, file_stride, buffer_stride); } void read_yuv(std::array &mem, std::ifstream &in, unsigned int width, unsigned int height, unsigned int file_stride, unsigned int buffer_stride, unsigned int ss_x, unsigned int ss_y) { uint8_t *dst = mem[0]; // Y read_plane(dst, in, width, height, file_stride, buffer_stride); // U dst = mem[1] ? mem[1] : dst + buffer_stride * height; read_plane(dst, in, width / ss_x, height / ss_y, file_stride / ss_x, buffer_stride / ss_x); // V dst = mem[2] ? mem[2] : dst + buffer_stride / ss_x * height / ss_y; read_plane(dst, in, width / ss_x, height / ss_y, file_stride / ss_x, buffer_stride / ss_x); } void write_yuv(std::ofstream &out, std::array &mem, unsigned int width, unsigned int height, unsigned int file_stride, unsigned int buffer_stride, unsigned int ss_x, unsigned int ss_y) { uint8_t *src = mem[0]; // Y write_plane(out, src, width, height, file_stride, buffer_stride); // U src = mem[1] ? mem[1] : src + buffer_stride * height; write_plane(out, src, width / ss_x, height / ss_y, file_stride / ss_x, buffer_stride / ss_x); // V src = mem[2] ? mem[2] : src + buffer_stride / ss_x * height / ss_y; write_plane(out, src, width / ss_x, height / ss_y, file_stride / ss_x, buffer_stride / ss_x); } void read_yuv420(std::array &mem, std::ifstream &in, unsigned int width, unsigned int height, unsigned int file_stride, unsigned int buffer_stride) { read_yuv(mem, in, width, height, file_stride, buffer_stride, 2, 2); } void read_yuv422p(std::array &mem, std::ifstream &in, unsigned int width, unsigned int height, unsigned int file_stride, unsigned int buffer_stride) { read_yuv(mem, in, width, height, file_stride, buffer_stride, 2, 1); } void read_yuv444p(std::array &mem, std::ifstream &in, unsigned int width, unsigned int height, unsigned int file_stride, unsigned int buffer_stride) { read_yuv(mem, in, width, height, file_stride, buffer_stride, 1, 1); } void read_yuv422i(std::array &mem, std::ifstream &in, unsigned int width, unsigned int height, unsigned int file_stride, unsigned int buffer_stride) { read_plane(mem[0], in, width * 2, height, file_stride, buffer_stride); } void write_yuv420(std::ofstream &out, std::array &mem, unsigned int width, unsigned int height, unsigned int file_stride, unsigned int buffer_stride) { write_yuv(out, mem, width, height, file_stride, buffer_stride, 2, 2); } void write_yuv422p(std::ofstream &out, std::array &mem, unsigned int width, unsigned int height, unsigned int file_stride, unsigned int buffer_stride) { write_yuv(out, mem, width, height, file_stride, buffer_stride, 2, 1); } void write_yuv444p(std::ofstream &out, std::array &mem, unsigned int width, unsigned int height, unsigned int file_stride, unsigned int buffer_stride) { write_yuv(out, mem, width, height, file_stride, buffer_stride, 1, 1); } void write_yuv422i(std::ofstream &out, std::array &mem, unsigned int width, unsigned int height, unsigned int file_stride, unsigned int buffer_stride) { write_plane(out, mem[0], width * 2, height, file_stride, buffer_stride); } struct FormatFuncs { std::function &, std::ifstream &, unsigned int, unsigned int, unsigned int, unsigned int)> read_file; std::function &, unsigned int, unsigned int, unsigned int, unsigned int)> write_file; }; const std::map Formats = { { "RGB888", { read_rgb888, write_rgb888 } }, { "RGBX8888", { read_32, write_32 } }, { "YUV420P", { read_yuv420, write_yuv420 } }, { "YUV422P", { read_yuv422p, write_yuv422p } }, { "YUV444P", { read_yuv444p, write_yuv444p } }, { "YUYV", { read_yuv422i, write_yuv422i } }, { "UYVY", { read_yuv422i, write_yuv422i } }, }; struct Format { unsigned int width; unsigned int height; unsigned int stride; std::string format; }; Format parse_format(const std::string &fmt) { Format format; size_t pos = 0, start = 0; pos = fmt.find(':', start); if (pos == std::string::npos) return {}; format.width = std::stoi(fmt.substr(start, pos - start)); start = pos + 1; pos = fmt.find(':', start); if (pos == std::string::npos) return {}; format.height = std::stoi(fmt.substr(start, pos - start)); start = pos + 1; pos = fmt.find(':', start); if (pos == std::string::npos) return {}; format.stride = std::stoi(fmt.substr(start, pos - start)); start = pos + 1; format.format = fmt.substr(start); return format; } int main(int argc, char *argv[]) { libpisp::helpers::MediaDevice devices; libpisp::logging_init(); cxxopts::Options options(argv[0], "PiSP Image Converter"); options.add_options() ("input", "Input file", cxxopts::value()) ("output", "Output file", cxxopts::value()) ("input-format", "Input format in the form width:height:stride:format\n" "Bit-depth is assumed to be 8-bit.",cxxopts::value()->default_value("")) ("output-format", "Output format in the form width:height:stride:format\n" "Bit-depth is assumed to be 8-bit.", cxxopts::value()->default_value("")) ("f,formats", "List available format strings that can be used") ("l,list", "Enumerate the media device nodes") ("h,help", "Print usage") ; options.parse_positional({ "input", "output" }); options.positional_help(" "); options.set_width(120); auto args = options.parse(argc, argv); if (args.count("help")) { std::cerr << options.help() << std::endl; exit(0); } else if (args.count("list")) { std::cerr << devices.List() << std::endl; exit(0); } else if (args.count("formats")) { for (const auto &f : Formats) std::cerr << f.first << " "; std::cerr << std::endl; exit(0); } std::string media_dev = devices.Acquire(); if (media_dev.empty()) { std::cerr << "Unable to acquire any pisp_be device!" << std::endl; exit(-1); } libpisp::helpers::BackendDevice backend_device { media_dev }; std::cerr << "Acquired device " << media_dev << std::endl; auto in_file = parse_format(args["input-format"].as()); if (!Formats.count(in_file.format)) { std::cerr << "Invalid input-format specified" << std::endl; exit(-1); } auto out_file = parse_format(args["output-format"].as()); if (!Formats.count(out_file.format)) { std::cerr << "Invalid output-format specified" << std::endl; exit(-1); } const std::vector &variants = libpisp::get_variants(); const media_device_info info = devices.DeviceInfo(media_dev); auto variant = std::find_if(variants.begin(), variants.end(), [&info](const auto &v) { return v.BackEndVersion() == info.hw_revision; }); if (variant == variants.end()) { std::cerr << "Backend hardware cound not be identified: " << info.hw_revision << std::endl; exit(-1); } libpisp::BackEnd be(libpisp::BackEnd::Config({}), *variant); pisp_be_global_config global; be.GetGlobal(global); global.bayer_enables = 0; global.rgb_enables = PISP_BE_RGB_ENABLE_INPUT + PISP_BE_RGB_ENABLE_OUTPUT0; if (in_file.format == "RGBX8888" && !variant->BackendRGB32Supported(0)) { std::cerr << "Backend hardware does not support RGBX input" << std::endl; exit(-1); } pisp_image_format_config i = {}; i.width = in_file.width; i.height = in_file.height; i.format = libpisp::get_pisp_image_format(in_file.format); assert(i.format); libpisp::compute_optimal_stride(i); be.SetInputFormat(i); pisp_be_output_format_config o = {}; if (out_file.format == "RGBX8888" && !variant->BackendRGB32Supported(0)) { // Hack to generate RGBX even when BE_MINOR_VERSION < 1 using Resample if (out_file.width < i.width) std::cerr << "Backend hardware has limited RGBX support; resize artifacts may be present" << std::endl; o.image.width = out_file.width * 2 - 1; o.image.height = out_file.height; o.image.format = libpisp::get_pisp_image_format("UYVY"); pisp_be_ccm_config csc = {}; // Define a matrix to swap components [0] and [1] csc.coeffs[1] = 1024; csc.coeffs[3] = 1024; csc.coeffs[8] = 1024; csc.offsets[0] = 131072; // round to nearest after Resample, for 8-bit output csc.offsets[1] = 131072; csc.offsets[2] = 131072; be.SetCsc(0, csc); global.rgb_enables |= PISP_BE_RGB_ENABLE_CSC0; } else { o.image.width = out_file.width; o.image.height = out_file.height; o.image.format = libpisp::get_pisp_image_format(out_file.format); } assert(o.image.format); libpisp::compute_optimal_stride(o.image, true); be.SetOutputFormat(0, o); if (!out_file.stride) out_file.stride = o.image.stride; if (in_file.format >= "U") { pisp_be_ccm_config csc; be.InitialiseYcbcrInverse(csc, "jpeg"); be.SetCcm(csc); global.rgb_enables |= PISP_BE_RGB_ENABLE_CCM; } if (out_file.format >= "U") { pisp_be_ccm_config csc; be.InitialiseYcbcr(csc, "jpeg"); be.SetCsc(0, csc); global.rgb_enables |= PISP_BE_RGB_ENABLE_CSC0; } be.SetGlobal(global); be.SetCrop(0, { 0, 0, i.width, i.height }); be.SetSmartResize(0, { o.image.width, o.image.height }); pisp_be_tiles_config config = {}; be.Prepare(&config); backend_device.Setup(config); auto buffers = backend_device.AcquireBuffers(); std::string input_filename = args["input"].as(); std::ifstream in(input_filename, std::ios::binary); if (!in.is_open()) { std::cerr << "Unable to open " << input_filename << std::endl; exit(-1); } std::cerr << "Reading " << input_filename << " " << in_file.width << ":" << in_file.height << ":" << in_file.stride << ":" << in_file.format << std::endl; Formats.at(in_file.format) .read_file(buffers["pispbe-input"].mem, in, in_file.width, in_file.height, in_file.stride, i.stride); in.close(); int ret = backend_device.Run(buffers); if (ret) { std::cerr << "Job run error!" << std::endl; exit(-1); } std::string output_file = args["output"].as(); std::ofstream out(output_file, std::ios::binary); if (!out.is_open()) { std::cerr << "Unable to open " << output_file << std::endl; exit(-1); } Formats.at(out_file.format) .write_file(out, buffers["pispbe-output0"].mem, out_file.width, out_file.height, out_file.stride, o.image.stride); out.close(); backend_device.ReleaseBuffer(buffers); std::cerr << "Writing " << output_file << " " << out_file.width << ":" << out_file.height << ":" << out_file.stride << ":" << out_file.format << std::endl; return 0; } raspberrypi-libpisp-9ba67e6/src/examples/meson.build000066400000000000000000000005041507172066400227170ustar00rootroot00000000000000# SPDX-License-Identifier: CC0-1.0 # Copyright (C) 2025, Raspberry Pi Ltd opts_dep = dependency('cxxopts', fallback : ['cxxopts', 'cxxopts_dep']) libpisp_convert = executable('convert', files('convert.cpp'), dependencies: [libpisp_dep, opts_dep], install : false) raspberrypi-libpisp-9ba67e6/src/helpers/000077500000000000000000000000001507172066400204025ustar00rootroot00000000000000raspberrypi-libpisp-9ba67e6/src/helpers/backend_device.cpp000066400000000000000000000111541507172066400240160ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (C) 2025 Raspberry Pi Ltd * * backend_device.cpp - PiSP Backend device helper */ #include #include #include "backend_device.hpp" using namespace libpisp::helpers; BackendDevice::BackendDevice(const std::string &device) : valid_(true) { nodes_ = MediaDevice().OpenV4l2Nodes(device); if (nodes_.empty()) valid_ = false; // Allocate a config buffer to persist. nodes_.at("pispbe-config").RequestBuffers(1); nodes_.at("pispbe-config").StreamOn(); config_buffer_ = nodes_.at("pispbe-config").AcquireBuffer().value(); } BackendDevice::~BackendDevice() { nodes_.at("pispbe-config").StreamOff(); } void BackendDevice::Setup(const pisp_be_tiles_config &config, unsigned int buffer_count, bool use_opaque_format) { nodes_enabled_.clear(); if ((config.config.global.rgb_enables & PISP_BE_RGB_ENABLE_INPUT) || (config.config.global.bayer_enables & PISP_BE_BAYER_ENABLE_INPUT)) { nodes_.at("pispbe-input").SetFormat(config.config.input_format, use_opaque_format); // Release old/allocate a single buffer. nodes_.at("pispbe-input").ReturnBuffers(); nodes_.at("pispbe-input").RequestBuffers(buffer_count); nodes_enabled_.emplace("pispbe-input"); } if (config.config.global.rgb_enables & PISP_BE_RGB_ENABLE_OUTPUT0) { nodes_.at("pispbe-output0").SetFormat(config.config.output_format[0].image, use_opaque_format); // Release old/allocate a single buffer. nodes_.at("pispbe-output0").ReturnBuffers(); nodes_.at("pispbe-output0").RequestBuffers(buffer_count); nodes_enabled_.emplace("pispbe-output0"); } if (config.config.global.rgb_enables & PISP_BE_RGB_ENABLE_OUTPUT1) { nodes_.at("pispbe-output1").SetFormat(config.config.output_format[1].image, use_opaque_format); // Release old/allocate a single buffer. nodes_.at("pispbe-output1").ReturnBuffers(); nodes_.at("pispbe-output1").RequestBuffers(buffer_count); nodes_enabled_.emplace("pispbe-output1"); } if (config.config.global.bayer_enables & PISP_BE_BAYER_ENABLE_TDN_INPUT) { nodes_.at("pispbe-tdn_input").SetFormat(config.config.tdn_input_format, use_opaque_format); // Release old/allocate a single buffer. nodes_.at("pispbe-tdn_input").ReturnBuffers(); nodes_.at("pispbe-tdn_input").RequestBuffers(buffer_count); nodes_enabled_.emplace("pispbe-tdn_input"); } if (config.config.global.bayer_enables & PISP_BE_BAYER_ENABLE_TDN_OUTPUT) { nodes_.at("pispbe-tdn_output").SetFormat(config.config.tdn_output_format, use_opaque_format); // Release old/allocate a single buffer. nodes_.at("pispbe-tdn_output").ReturnBuffers(); nodes_.at("pispbe-tdn_output").RequestBuffers(buffer_count); nodes_enabled_.emplace("pispbe-tdn_output"); } if (config.config.global.bayer_enables & PISP_BE_BAYER_ENABLE_STITCH_INPUT) { nodes_.at("pispbe-stitch_input").SetFormat(config.config.stitch_input_format, use_opaque_format); // Release old/allocate a single buffer. nodes_.at("pispbe-stitch_input").ReturnBuffers(); nodes_.at("pispbe-stitch_input").RequestBuffers(buffer_count); nodes_enabled_.emplace("pispbe-stitch_input"); } if (config.config.global.bayer_enables & PISP_BE_BAYER_ENABLE_STITCH_OUTPUT) { nodes_.at("pispbe-stitch_output").SetFormat(config.config.stitch_output_format, use_opaque_format); // Release old/allocate a single buffer. nodes_.at("pispbe-stitch_output").ReturnBuffers(); nodes_.at("pispbe-stitch_output").RequestBuffers(buffer_count); nodes_enabled_.emplace("pispbe-stitch_output"); } std::memcpy(reinterpret_cast(config_buffer_.mem[0]), &config, sizeof(config)); } std::map BackendDevice::AcquireBuffers() { std::map buffers; for (auto const &n : nodes_enabled_) buffers[n] = nodes_.at(n).AcquireBuffer().value(); return buffers; } void BackendDevice::ReleaseBuffer(const std::map &buffers) { for (auto const &[n, b] : buffers) nodes_.at(n).ReleaseBuffer(b); } int BackendDevice::Run(const std::map &buffers) { int ret = 0; for (auto const &n : nodes_enabled_) { nodes_.at(n).StreamOn(); if (nodes_.at(n).QueueBuffer(buffers.at(n).buffer.index)) ret = -1; } // Triggers the HW job. if (nodes_.at("pispbe-config").QueueBuffer(config_buffer_.buffer.index)) ret = -1; for (auto const &n : nodes_enabled_) { if (nodes_.at(n).DequeueBuffer(1000) < 0) ret = -1; } // Must dequeue the config buffer in case it's used again. if (nodes_.at("pispbe-config").DequeueBuffer(1000) < 0) ret = -1; for (auto const &n : nodes_enabled_) nodes_.at(n).StreamOff(); return ret; } raspberrypi-libpisp-9ba67e6/src/helpers/backend_device.hpp000066400000000000000000000021301507172066400240150ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (C) 2025 Raspberry Pi Ltd * * backend_device.hpp - PiSP Backend device helper */ #pragma once #include #include #include "libpisp/backend/pisp_be_config.h" #include "media_device.hpp" #include "v4l2_device.hpp" namespace libpisp::helpers { class BackendDevice { public: BackendDevice(const std::string &device); ~BackendDevice(); void Setup(const pisp_be_tiles_config &config, unsigned int buffer_count = 1, bool use_opaque_format = false); int Run(const std::map &buffers); bool Valid() const { return valid_; } V4l2Device &Node(const std::string &node) { return nodes_.at(node); } std::map AcquireBuffers(); void ReleaseBuffer(const std::map &buffers); V4l2Device::Buffer &ConfigBuffer() { return config_buffer_; } private: bool valid_; V4l2DevMap nodes_; MediaDevice devices_; std::unordered_set nodes_enabled_; V4l2Device::Buffer config_buffer_; }; } // namespace libpisp raspberrypi-libpisp-9ba67e6/src/helpers/device_fd.hpp000066400000000000000000000017731507172066400230330ustar00rootroot00000000000000 /* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (C) 2025 Raspberry Pi Ltd * * device_fd.hpp - PiSP device file descriptor helper */ #pragma once #include #include #include namespace libpisp::helpers { class DeviceFd { public: DeviceFd(DeviceFd const &) = delete; void operator=(DeviceFd const &) = delete; DeviceFd(const std::string &file, mode_t mode) : deviceFd_(-1) { int DeviceFd = ::open(file.c_str(), mode); if (DeviceFd >= 0) deviceFd_ = DeviceFd; } ~DeviceFd() { Close(); } DeviceFd(DeviceFd &&other) { this->deviceFd_ = other.deviceFd_; other.deviceFd_ = -1; } DeviceFd &operator=(DeviceFd &&other) { if (this != &other) { this->deviceFd_ = other.deviceFd_; other.deviceFd_ = -1; } return *this; } int Get() { return deviceFd_; } void Close() { if (Valid()) ::close(deviceFd_); deviceFd_ = -1; } bool Valid() { return deviceFd_ >= 0; } private: int deviceFd_; }; } // namespace libpisp raspberrypi-libpisp-9ba67e6/src/helpers/media_device.cpp000066400000000000000000000137551507172066400235170ustar00rootroot00000000000000 /* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (C) 2025 Raspberry Pi Ltd * * media_device.cpp - PiSP media device helper */ #include "media_device.hpp" #include #include #include #include #include #include #include #include #include #include namespace fs = std::filesystem; using namespace libpisp::helpers; struct DeviceNode { std::string name; std::string dev_node; }; struct MediaDevMap { MediaDevMap(const std::string &_media_node, const std::vector &&_device_nodes) : media_node(_media_node), device_nodes(_device_nodes) { } std::string media_node; std::vector device_nodes; }; class MediaEnumerator { public: using MediaDevList = std::vector; MediaEnumerator(MediaEnumerator &other) = delete; void operator=(const MediaEnumerator &other) = delete; static const MediaEnumerator *Get() { static std::unique_ptr mdev(new MediaEnumerator); return mdev.get(); } const MediaDevList &MediaDeviceList() const { return device_list_; } private: MediaEnumerator() { // Sysfs path for media devices - correct for Raspberry Pi, but not sure if this is globally consistent. const fs::path media_sysfs("/sys/bus/media/devices"); if (!fs::exists(media_sysfs)) return; for (auto const &media_device : fs::directory_iterator { media_sysfs }) { if (!fs::is_symlink(media_device)) continue; // Find a pisp_be device link, const std::string link(fs::read_symlink(media_device).string()); if (link.find("pisp_be") == std::string::npos) continue; // Find the /dev/mediaX device node. const std::string::size_type pos = link.find_last_of("/"); const std::string media_node("/dev/" + link.substr(pos + 1)); DeviceFd fd(media_node.c_str(), O_RDWR | O_CLOEXEC); if (!fd.Valid()) continue; struct media_v2_topology topology; std::memset(&topology, 0, sizeof(topology)); // First ioctl call to get the number of interfaces. int ret = ioctl(fd.Get(), MEDIA_IOC_G_TOPOLOGY, &topology); if (ret < 0 || !topology.num_interfaces) continue; auto interfaces = std::make_unique(topology.num_interfaces); topology.ptr_interfaces = reinterpret_cast(interfaces.get()); // Second ioctl call with allocated space to populate the interface array. ret = ioctl(fd.Get(), MEDIA_IOC_G_TOPOLOGY, &topology); if (ret < 0) continue; std::vector device_node; for (unsigned int i = 0; i < topology.num_interfaces; i++) { const media_v2_interface &intf = interfaces[i]; if (intf.intf_type != MEDIA_INTF_T_V4L_VIDEO) break; // Find the char dev with the major:minor advertised by the interface. const fs::path char_dev("/sys/dev/char/" + std::to_string(intf.devnode.major) + ":" + std::to_string(intf.devnode.minor)); if (!fs::is_symlink(char_dev)) break; const std::string char_dev_link = fs::read_symlink(char_dev).string(); const std::string::size_type npos = char_dev_link.find_last_of("/"); const std::string dev_node("/dev/" + char_dev_link.substr(npos + 1)); // Finally get the /dev/videoX node for the interface. std::ifstream dev_name_file(char_dev / "name"); if (!dev_name_file.is_open()) break; std::string dev_name; std::getline(dev_name_file, dev_name); device_node.push_back({ dev_name, dev_node }); } // If we have some device nodes enumerated, add them to our map. if (!device_node.empty()) device_list_.emplace_back(media_node, std::move(device_node)); } } MediaDevList device_list_; }; MediaDevice::MediaDevice() : media_enumerator_(MediaEnumerator::Get()) { } MediaDevice::~MediaDevice() { for (auto it = lock_map_.begin(); it != lock_map_.end();) it = unlock(it->first); } std::string MediaDevice::Acquire(const std::string &device) { for (const auto &m : media_enumerator_->MediaDeviceList()) { if (!device.empty() && m.media_node != device) continue; // Check if this process has the lock. auto it = lock_map_.find(m.media_node.c_str()); if (it != lock_map_.end()) continue; // Check if another process has the lock. DeviceFd fd(m.media_node.c_str(), O_RDWR | O_CLOEXEC); if (!fd.Valid()) continue; if (lockf(fd.Get(), F_TLOCK, 0)) continue; lock_map_.emplace(std::piecewise_construct, std::forward_as_tuple(m.media_node), std::forward_as_tuple(std::move(fd))); return m.media_node; } return {}; } void MediaDevice::Release(const std::string &device) { unlock(device); } V4l2DevMap MediaDevice::OpenV4l2Nodes(const std::string &device) const { V4l2DevMap dev_map; for (const auto &m : media_enumerator_->MediaDeviceList()) { if (m.media_node != device) continue; for (auto const &n : m.device_nodes) { V4l2Device dev(n.dev_node); if (!dev.Valid()) return {}; dev_map.emplace(std::piecewise_construct, std::forward_as_tuple(n.name), std::forward_as_tuple(std::move(dev))); } return dev_map; } return {}; } void MediaDevice::CloseV4l2Nodes(V4l2DevMap &device_map) { for (auto &d : device_map) d.second.Close(); } std::string MediaDevice::List() const { std::stringstream ss; for (const auto &it : media_enumerator_->MediaDeviceList()) { ss << std::endl << it.media_node << std::endl; for (const auto &it1 : it.device_nodes) ss << " " << it1.dev_node << " " << it1.name << std::endl; } return ss.str(); } media_device_info MediaDevice::DeviceInfo(const std::string &device) const { media_device_info info; DeviceFd fd(device.c_str(), O_RDONLY | O_CLOEXEC); if (!fd.Valid()) return {}; int ret = ioctl(fd.Get(), MEDIA_IOC_DEVICE_INFO, &info); if (ret) return {}; return info; } std::map::iterator MediaDevice::unlock(const std::string &device) { auto it = lock_map_.find(device); if (it == lock_map_.end()) return lock_map_.end(); std::ignore = lockf(it->second.Get(), F_ULOCK, 0); return lock_map_.erase(it); } raspberrypi-libpisp-9ba67e6/src/helpers/media_device.hpp000066400000000000000000000017011507172066400235100ustar00rootroot00000000000000 /* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (C) 2025 Raspberry Pi Ltd * * media_device.hpp - PiSP media device helper */ #pragma once #include #include #include #include #include "device_fd.hpp" #include "v4l2_device.hpp" class MediaEnumerator; namespace libpisp::helpers { using V4l2DevMap = std::map; class MediaDevice { public: MediaDevice(); ~MediaDevice(); std::string Acquire(const std::string &device = {}); void Release(const std::string &device); V4l2DevMap OpenV4l2Nodes(const std::string &device) const; void CloseV4l2Nodes(V4l2DevMap &device_map); std::string List() const; struct media_device_info DeviceInfo(const std::string &device) const; private: std::map::iterator unlock(const std::string &device); std::map lock_map_; const MediaEnumerator *media_enumerator_; }; } // namespace libpisp raspberrypi-libpisp-9ba67e6/src/helpers/meson.build000066400000000000000000000006341507172066400225470ustar00rootroot00000000000000# SPDX-License-Identifier: CC0-1.0 # Copyright (C) 2025, Raspberry Pi Ltd pisp_sources += files([ 'backend_device.cpp', 'media_device.cpp', 'v4l2_device.cpp' ]) helper_headers = files([ 'backend_device.hpp', 'device_fd.hpp', 'media_device.hpp', 'v4l2_device.hpp', ]) helper_include_dir = pisp_include_dir / 'helpers' install_headers(helper_headers, subdir: helper_include_dir) raspberrypi-libpisp-9ba67e6/src/helpers/v4l2_device.cpp000066400000000000000000000175541507172066400232300ustar00rootroot00000000000000 /* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (C) 2424 Raspberry Pi Ltd * * v4l2_device.cpp - PiSP V4L2 device helper */ #include #include #include #include #include #include #include #include #include #include #include "libpisp/common/utils.hpp" #include "v4l2_device.hpp" using namespace libpisp::helpers; namespace { struct FormatInfo { unsigned int v4l2_pixfmt; unsigned int num_memory_planes; }; static FormatInfo get_v4l2_format(const std::string &format) { std::map formats { { "RGB888", { V4L2_PIX_FMT_RGB24, 1 } }, { "RGBX8888", { V4L2_PIX_FMT_RGBX32, 1 } }, { "YUV420P", { V4L2_PIX_FMT_YUV420, 1 } }, { "YUV422P", { V4L2_PIX_FMT_YUV422P, 1 } }, { "YUV444P", { V4L2_PIX_FMT_YUV444M, 3 } }, { "YUYV", { V4L2_PIX_FMT_YUYV, 1 } }, { "UYVY", { V4L2_PIX_FMT_UYVY, 1 } }, }; auto it = formats.find(format); if (it == formats.end()) return { 0, {} }; return it->second; } } // namespace V4l2Device::V4l2Device(const std::string &device) : fd_(device, O_RDWR | O_NONBLOCK | O_CLOEXEC), num_memory_planes_(1) { struct v4l2_capability caps; int ret = ioctl(fd_.Get(), VIDIOC_QUERYCAP, &caps); if (ret < 0) throw std::runtime_error("Cannot query device caps"); if (caps.capabilities & V4L2_CAP_VIDEO_CAPTURE_MPLANE) buf_type_ = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; else if (caps.capabilities & V4L2_CAP_VIDEO_OUTPUT_MPLANE) buf_type_ = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; else if (caps.capabilities & V4L2_CAP_META_OUTPUT) buf_type_ = V4L2_BUF_TYPE_META_OUTPUT; else throw std::runtime_error("Invalid buffer_type - caps: " + std::to_string(caps.capabilities)); } V4l2Device::~V4l2Device() { ReturnBuffers(); Close(); } int V4l2Device::RequestBuffers(unsigned int count) { int ret; ReturnBuffers(); v4l2_requestbuffers req_bufs {}; req_bufs.count = count; req_bufs.type = buf_type_; req_bufs.memory = V4L2_MEMORY_MMAP; ret = ioctl(fd_.Get(), VIDIOC_REQBUFS, &req_bufs); if (ret < 0) throw std::runtime_error("VIDIOC_REQBUFS failed: " + std::to_string(ret)); for (unsigned int i = 0; i < req_bufs.count; i++) { v4l2_plane planes[VIDEO_MAX_PLANES] = {}; v4l2_buffer buffer {}; buffer.index = i; buffer.type = buf_type_; buffer.memory = V4L2_MEMORY_MMAP; if (!isMeta()) { buffer.m.planes = planes; buffer.length = num_memory_planes_; } ret = ioctl(fd_.Get(), VIDIOC_QUERYBUF, &buffer); if (ret < 0) throw std::runtime_error("VIDIOC_QUERYBUF failed: " + std::to_string(ret)); // Don't keep this pointer dangling when putting into v4l2_buffers_. buffer.m.planes = NULL; v4l2_buffers_.emplace_back(buffer); available_buffers_.push(i); for (unsigned int p = 0; p < num_memory_planes_; p++) { size_t size = !isMeta() ? planes[p].length : buffer.length; unsigned int offset = !isMeta() ? planes[p].m.mem_offset : buffer.m.offset; void *mem = mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_.Get(), offset); if (mem == MAP_FAILED) throw std::runtime_error("Unable to mmap buffer"); v4l2_buffers_.back().size[p] = size; v4l2_buffers_.back().mem[p] = (uint8_t *)mem; } } return v4l2_buffers_.size(); } void V4l2Device::ReturnBuffers() { v4l2_requestbuffers req_bufs {}; if (!v4l2_buffers_.size()) return; for (auto const &b : v4l2_buffers_) { for (unsigned int p = 0; p < num_memory_planes_; p++) munmap(b.mem[p], b.size[p]); } req_bufs.type = buf_type_; req_bufs.count = 0; req_bufs.memory = V4L2_MEMORY_MMAP; ioctl(fd_.Get(), VIDIOC_REQBUFS, &req_bufs); v4l2_buffers_.clear(); } std::optional V4l2Device::AcquireBuffer() { if (available_buffers_.empty()) return {}; unsigned int index = available_buffers_.front(); available_buffers_.pop(); return findBuffer(index); } void V4l2Device::ReleaseBuffer(const Buffer &buffer) { available_buffers_.push(buffer.buffer.index); } int V4l2Device::QueueBuffer(unsigned int index) { std::optional buf = findBuffer(index); if (!buf) return -1; v4l2_plane planes[VIDEO_MAX_PLANES] = {}; if (!isMeta()) { buf->buffer.m.planes = planes; buf->buffer.length = num_memory_planes_; for (unsigned int p = 0; p < num_memory_planes_; p++) { buf->buffer.m.planes[p].bytesused = buf->size[p]; buf->buffer.m.planes[p].length = buf->size[p]; } } else buf->buffer.bytesused = buf->size[0]; buf->buffer.timestamp.tv_sec = time(NULL); buf->buffer.field = V4L2_FIELD_NONE; buf->buffer.flags = 0; int ret = ioctl(fd_.Get(), VIDIOC_QBUF, &buf->buffer); if (ret < 0) throw std::runtime_error("Unable to queue buffer: " + std::string(strerror(errno))); return ret; } int V4l2Device::DequeueBuffer(unsigned int timeout_ms) { short int poll_event = isOutput() ? POLLOUT : POLLIN; pollfd p = { fd_.Get(), poll_event, 0 }; int ret = poll(&p, 1, timeout_ms); if (ret <= 0) return -1; if (!(p.revents & poll_event)) return -1; v4l2_buffer buf = {}; v4l2_plane planes[VIDEO_MAX_PLANES] = {}; buf.type = buf_type_; buf.memory = V4L2_MEMORY_MMAP; if (!isMeta()) { buf.m.planes = planes; buf.length = VIDEO_MAX_PLANES; } ret = ioctl(fd_.Get(), VIDIOC_DQBUF, &buf); if (ret) return -1; return buf.index; } void V4l2Device::SetFormat(const pisp_image_format_config &format, bool use_opaque_format) { struct v4l2_format f = {}; FormatInfo info = get_v4l2_format(libpisp::get_pisp_image_format(format.format)); num_memory_planes_ = info.num_memory_planes; f.type = buf_type_; f.fmt.pix_mp.width = format.width; f.fmt.pix_mp.height = format.height; f.fmt.pix_mp.pixelformat = info.v4l2_pixfmt; f.fmt.pix_mp.field = V4L2_FIELD_NONE; f.fmt.pix_mp.num_planes = num_memory_planes_; unsigned int num_image_planes = libpisp::num_planes((pisp_image_format)format.format); if (use_opaque_format || info.v4l2_pixfmt == 0) { // This format is not specified by V4L2, we use an opaque buffer buffer as a workaround. // Size the dimensions down so the kernel drive does not attempt to resize it. f.fmt.pix_mp.width = 16; f.fmt.pix_mp.height = 16; f.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_YUV444M; num_memory_planes_ = 3; f.fmt.pix_mp.plane_fmt[0].bytesperline = format.stride; f.fmt.pix_mp.plane_fmt[0].sizeimage = 0; for (unsigned int i = 0; i < 3; i++) f.fmt.pix_mp.plane_fmt[0].sizeimage += libpisp::get_plane_size(format, i); f.fmt.pix_mp.plane_fmt[1].sizeimage = f.fmt.pix_mp.plane_fmt[2].sizeimage = f.fmt.pix_mp.plane_fmt[0].sizeimage; f.fmt.pix_mp.plane_fmt[1].bytesperline = f.fmt.pix_mp.plane_fmt[2].bytesperline = format.stride2; } else { unsigned int p = 0; for (; p < num_memory_planes_; p++) { const unsigned int stride = p == 0 ? format.stride : format.stride2; // Wallpaper stride is not something the V4L2 kernel knows about! f.fmt.pix_mp.plane_fmt[p].bytesperline = stride; f.fmt.pix_mp.plane_fmt[p].sizeimage = libpisp::get_plane_size(format, p); } for (; p < num_image_planes; p++) f.fmt.pix_mp.plane_fmt[num_memory_planes_ - 1].sizeimage += libpisp::get_plane_size(format, p); } int ret = ioctl(fd_.Get(), VIDIOC_S_FMT, &f); if (ret) throw std::runtime_error("Unable to set format: " + std::string(strerror(errno))); } void V4l2Device::StreamOn() { int ret = ioctl(fd_.Get(), VIDIOC_STREAMON, &buf_type_); if (ret < 0) throw std::runtime_error("Stream on failed: " + std::string(strerror(errno))); } void V4l2Device::StreamOff() { int ret = ioctl(fd_.Get(), VIDIOC_STREAMOFF, &buf_type_); if (ret < 0) throw std::runtime_error("Stream off failed: " + std::string(strerror(errno))); } std::optional V4l2Device::findBuffer(unsigned int index) const { auto it = std::find_if(v4l2_buffers_.begin(), v4l2_buffers_.end(), [index](auto const &b) { return b.buffer.index == index; }); if (it == v4l2_buffers_.end()) { throw std::runtime_error("find buffers failed"); return {}; } return *it; } raspberrypi-libpisp-9ba67e6/src/helpers/v4l2_device.hpp000066400000000000000000000035561507172066400232320ustar00rootroot00000000000000 /* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (C) 2025 Raspberry Pi Ltd * * v4l2_device.hpp - PiSP V4L2 device helper */ #pragma once #include #include #include #include #include #include #include "libpisp/backend/pisp_be_config.h" #include "device_fd.hpp" namespace libpisp::helpers { class V4l2Device { public: V4l2Device(const std::string &device); ~V4l2Device(); V4l2Device(V4l2Device &&) = default; V4l2Device &operator=(V4l2Device &&) = default; V4l2Device(const V4l2Device &) = delete; V4l2Device &operator=(const V4l2Device &) = delete; unsigned int Fd() { return fd_.Get(); } bool Valid() { return fd_.Valid(); } void Close() { if (fd_.Valid()) fd_.Close(); } struct Buffer { Buffer() { } Buffer(const v4l2_buffer& buf) : buffer(buf), size({}), mem({}) { } v4l2_buffer buffer; std::array size; std::array mem; }; int RequestBuffers(unsigned int count = 1); void ReturnBuffers(); std::optional AcquireBuffer(); void ReleaseBuffer(const Buffer &buffer); const std::vector &Buffers() const { return v4l2_buffers_; }; int QueueBuffer(unsigned int index); int DequeueBuffer(unsigned int timeout_ms = 500); void SetFormat(const pisp_image_format_config &format, bool use_opaque_format = false); void StreamOn(); void StreamOff(); private: bool isMeta() { return buf_type_ == V4L2_BUF_TYPE_META_OUTPUT; } bool isCapture() { return buf_type_ == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; } bool isOutput() { return !isCapture(); } std::optional findBuffer(unsigned int index) const; std::queue available_buffers_; std::vector v4l2_buffers_; DeviceFd fd_; enum v4l2_buf_type buf_type_; unsigned int num_memory_planes_; }; } // namespace libpisp raspberrypi-libpisp-9ba67e6/src/libpisp/000077500000000000000000000000001507172066400204025ustar00rootroot00000000000000raspberrypi-libpisp-9ba67e6/src/libpisp/backend/000077500000000000000000000000001507172066400217715ustar00rootroot00000000000000raspberrypi-libpisp-9ba67e6/src/libpisp/backend/backend.cpp000066400000000000000000000336461507172066400241000ustar00rootroot00000000000000 /* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (C) 2021 - 2023, Raspberry Pi Ltd * * backend.cpp - PiSP Back End implementation */ #include "backend.hpp" #include #include #include "common/logging.hpp" #include "common/pisp_common.h" using namespace libpisp; BackEnd::BackEnd(Config const &config, PiSPVariant const &variant) : config_(config), variant_(variant), retile_(true), finalise_tiling_(true) { unsigned int max_tile_width = variant_.BackEndMaxTileWidth(0); if (config_.max_tile_width > max_tile_width) PISP_LOG(fatal, "Configured max tile width " << config_.max_tile_width << " exceeds " << max_tile_width); smart_resize_dirty_ = 0; memset(&be_config_, 0, sizeof(be_config_)); memset(&be_config_extra_, 0, sizeof(be_config_extra_)); const char *env = std::getenv("LIBPISP_BE_CONFIG_FILE"); initialiseDefaultConfig(env ? std::string(env) : config.defaults_file); } BackEnd::~BackEnd() { } void BackEnd::SetGlobal(pisp_be_global_config const &global) { uint32_t changed_rgb_enables = (global.rgb_enables ^ be_config_.global.rgb_enables); if (changed_rgb_enables & (PISP_BE_RGB_ENABLE_DOWNSCALE0 | PISP_BE_RGB_ENABLE_DOWNSCALE1 | PISP_BE_RGB_ENABLE_RESAMPLE0 | PISP_BE_RGB_ENABLE_RESAMPLE1)) retile_ = true; // must retile when rescale change if (global.rgb_enables & PISP_BE_RGB_ENABLE_HOG) throw std::runtime_error("HOG output is not supported."); be_config_extra_.dirty_flags_bayer |= (global.bayer_enables & ~be_config_.global.bayer_enables); // label anything newly enabled as dirty be_config_extra_.dirty_flags_rgb |= (global.rgb_enables & ~be_config_.global.rgb_enables); // label anything newly enabled as dirty be_config_.global = global; be_config_.global.pad[0] = be_config_.global.pad[1] = be_config_.global.pad[2] = 0; be_config_extra_.dirty_flags_extra |= PISP_BE_DIRTY_GLOBAL; } void BackEnd::GetGlobal(pisp_be_global_config &global) const { global = be_config_.global; } void BackEnd::SetInputFormat(pisp_image_format_config const &input_format) { be_config_.input_format = input_format; if (PISP_IMAGE_FORMAT_THREE_CHANNEL(input_format.format)) be_config_extra_.dirty_flags_bayer |= PISP_BE_BAYER_ENABLE_INPUT; else be_config_extra_.dirty_flags_rgb |= PISP_BE_RGB_ENABLE_INPUT; retile_ = true; } void BackEnd::GetInputFormat(pisp_image_format_config &input_format) const { input_format = be_config_.input_format; } void BackEnd::SetDecompress(pisp_decompress_config const &decompress) { be_config_.decompress = decompress; be_config_extra_.dirty_flags_bayer |= PISP_BE_BAYER_ENABLE_DECOMPRESS; } void BackEnd::SetDpc(pisp_be_dpc_config const &dpc) { be_config_.dpc = dpc; be_config_.dpc.pad = 0; be_config_extra_.dirty_flags_bayer |= PISP_BE_BAYER_ENABLE_DPC; } void BackEnd::SetGeq(pisp_be_geq_config const &geq) { be_config_.geq = geq; be_config_.geq.slope_sharper &= (PISP_BE_GEQ_SLOPE | PISP_BE_GEQ_SHARPER); be_config_extra_.dirty_flags_bayer |= PISP_BE_BAYER_ENABLE_GEQ; } void BackEnd::SetTdnInputFormat(pisp_image_format_config const &tdn_input_format) { be_config_.tdn_input_format = tdn_input_format; be_config_extra_.dirty_flags_bayer |= PISP_BE_BAYER_ENABLE_TDN_INPUT; // TDN input address will always be written finalise_tiling_ = true; } void BackEnd::GetTdnInputFormat(pisp_image_format_config &tdn_input_format) const { tdn_input_format = be_config_.tdn_input_format; } void BackEnd::SetTdnDecompress(pisp_decompress_config const &tdn_decompress) { be_config_.tdn_decompress = tdn_decompress; be_config_extra_.dirty_flags_bayer |= PISP_BE_BAYER_ENABLE_TDN_DECOMPRESS; } void BackEnd::SetTdn(pisp_be_tdn_config const &tdn) { be_config_.tdn = tdn; be_config_.tdn.pad = 0; be_config_extra_.dirty_flags_bayer |= PISP_BE_BAYER_ENABLE_TDN; } void BackEnd::GetTdn(pisp_be_tdn_config &tdn) const { tdn = be_config_.tdn; } void BackEnd::SetTdnCompress(pisp_compress_config const &tdn_compress) { be_config_.tdn_compress = tdn_compress; be_config_extra_.dirty_flags_bayer |= PISP_BE_BAYER_ENABLE_TDN_COMPRESS; } void BackEnd::SetTdnOutputFormat(pisp_image_format_config const &tdn_output_format) { be_config_.tdn_output_format = tdn_output_format; be_config_extra_.dirty_flags_bayer |= PISP_BE_BAYER_ENABLE_TDN_OUTPUT; // TDN output address will always be written finalise_tiling_ = true; } void BackEnd::GetTdnOutputFormat(pisp_image_format_config &tdn_output_format) const { tdn_output_format = be_config_.tdn_output_format; } void BackEnd::SetSdn(pisp_be_sdn_config const &sdn) { be_config_.sdn = sdn; be_config_.sdn.pad = 0; be_config_extra_.dirty_flags_bayer |= PISP_BE_BAYER_ENABLE_SDN; } void BackEnd::SetBlc(pisp_bla_config const &blc) { be_config_.blc = blc; be_config_.blc.pad[0] = be_config_.blc.pad[1] = 0; be_config_extra_.dirty_flags_bayer |= PISP_BE_BAYER_ENABLE_BLC; } void BackEnd::GetBlc(pisp_bla_config &blc) const { blc = be_config_.blc; } void BackEnd::SetStitchInputFormat(pisp_image_format_config const &stitch_input_format) { be_config_.stitch_input_format = stitch_input_format; be_config_.stitch.pad = 0; be_config_extra_.dirty_flags_bayer |= PISP_BE_BAYER_ENABLE_STITCH_INPUT; finalise_tiling_ = true; } void BackEnd::GetStitchInputFormat(pisp_image_format_config &stitch_input_format) const { stitch_input_format = be_config_.stitch_input_format; } void BackEnd::SetStitchDecompress(pisp_decompress_config const &stitch_decompress) { be_config_.stitch_decompress = stitch_decompress; be_config_extra_.dirty_flags_bayer |= PISP_BE_BAYER_ENABLE_STITCH_DECOMPRESS; } void BackEnd::SetStitch(pisp_be_stitch_config const &stitch) { be_config_.stitch = stitch; be_config_extra_.dirty_flags_bayer |= PISP_BE_BAYER_ENABLE_STITCH; } void BackEnd::SetStitchCompress(pisp_compress_config const &stitch_compress) { be_config_.stitch_compress = stitch_compress; be_config_extra_.dirty_flags_bayer |= PISP_BE_BAYER_ENABLE_STITCH_COMPRESS; } void BackEnd::SetStitchOutputFormat(pisp_image_format_config const &stitch_output_format) { be_config_.stitch_output_format = stitch_output_format; be_config_extra_.dirty_flags_bayer |= PISP_BE_BAYER_ENABLE_STITCH_OUTPUT; finalise_tiling_ = true; } void BackEnd::GetStitchOutputFormat(pisp_image_format_config &stitch_output_format) const { stitch_output_format = be_config_.stitch_output_format; } void BackEnd::SetCdn(pisp_be_cdn_config const &cdn) { be_config_.cdn = cdn; be_config_extra_.dirty_flags_bayer |= PISP_BE_BAYER_ENABLE_CDN; } void BackEnd::SetWbg(pisp_wbg_config const &wbg) { be_config_.wbg = wbg; be_config_.wbg.pad[0] = be_config_.wbg.pad[1] = 0; be_config_extra_.dirty_flags_bayer |= PISP_BE_BAYER_ENABLE_WBG; } void BackEnd::GetWbg(pisp_wbg_config &wbg) const { wbg = be_config_.wbg; } void BackEnd::SetLsc(pisp_be_lsc_config const &lsc, pisp_be_lsc_extra lsc_extra) { // Should not need a finalise_tile if only the cell coefficients have changed. finalise_tiling_ |= be_config_.lsc.grid_step_x != lsc.grid_step_x || be_config_.lsc.grid_step_y != lsc.grid_step_y; be_config_.lsc = lsc; be_config_extra_.dirty_flags_bayer |= PISP_BE_BAYER_ENABLE_LSC; be_config_extra_.lsc = lsc_extra; } void BackEnd::SetCac(pisp_be_cac_config const &cac, pisp_be_cac_extra cac_extra) { finalise_tiling_ |= be_config_.cac.grid_step_x != cac.grid_step_x || be_config_.cac.grid_step_y != cac.grid_step_y; be_config_.cac = cac; be_config_extra_.cac = cac_extra; be_config_extra_.dirty_flags_bayer |= PISP_BE_BAYER_ENABLE_CAC; } void BackEnd::SetDebin(pisp_be_debin_config const &debin) { be_config_.debin = debin; be_config_.debin.pad[0] = be_config_.debin.pad[1] = 0; be_config_extra_.dirty_flags_bayer |= PISP_BE_BAYER_ENABLE_DEBIN; } void BackEnd::GetDebin(pisp_be_debin_config &debin) const { debin = be_config_.debin; } void BackEnd::SetTonemap(pisp_be_tonemap_config const &tonemap) { be_config_.tonemap = tonemap; be_config_extra_.dirty_flags_bayer |= PISP_BE_BAYER_ENABLE_TONEMAP; } void BackEnd::SetDemosaic(pisp_be_demosaic_config const &demosaic) { be_config_.demosaic = demosaic; be_config_.demosaic.pad[0] = be_config_.demosaic.pad[1] = 0; be_config_extra_.dirty_flags_bayer |= PISP_BE_BAYER_ENABLE_DEMOSAIC; } void BackEnd::GetDemosaic(pisp_be_demosaic_config &demosaic) const { demosaic = be_config_.demosaic; } void BackEnd::SetCcm(pisp_be_ccm_config const &ccm) { be_config_.ccm = ccm; be_config_.ccm.pad[0] = be_config_.ccm.pad[1] = 0; be_config_extra_.dirty_flags_rgb |= PISP_BE_RGB_ENABLE_CCM; } void BackEnd::SetSatControl(pisp_be_sat_control_config const &sat_control) { be_config_.sat_control = sat_control; be_config_.sat_control.pad = 0; be_config_extra_.dirty_flags_rgb |= PISP_BE_RGB_ENABLE_SAT_CONTROL; } void BackEnd::SetYcbcr(pisp_be_ccm_config const &ycbcr) { be_config_.ycbcr = ycbcr; be_config_.ycbcr.pad[0] = be_config_.ycbcr.pad[1] = 0; be_config_extra_.dirty_flags_rgb |= PISP_BE_RGB_ENABLE_YCBCR; } void BackEnd::GetYcbcr(pisp_be_ccm_config &ycbcr) const { ycbcr = be_config_.ycbcr; } void BackEnd::SetFalseColour(pisp_be_false_colour_config const &false_colour) { be_config_.false_colour = false_colour; be_config_.false_colour.pad[0] = be_config_.false_colour.pad[1] = be_config_.false_colour.pad[2] = 0; be_config_extra_.dirty_flags_rgb |= PISP_BE_RGB_ENABLE_FALSE_COLOUR; } void BackEnd::SetSharpen(pisp_be_sharpen_config const &sharpen) { be_config_.sharpen = sharpen; be_config_.sharpen.pad0[0] = be_config_.sharpen.pad0[1] = be_config_.sharpen.pad0[2] = 0; be_config_.sharpen.pad1[0] = be_config_.sharpen.pad1[1] = be_config_.sharpen.pad1[2] = 0; be_config_.sharpen.pad2[0] = be_config_.sharpen.pad2[1] = be_config_.sharpen.pad2[2] = 0; be_config_.sharpen.pad3[0] = be_config_.sharpen.pad3[1] = be_config_.sharpen.pad3[2] = 0; be_config_.sharpen.pad4[0] = be_config_.sharpen.pad4[1] = be_config_.sharpen.pad4[2] = 0; be_config_.sharpen.pad5 = be_config_.sharpen.pad6 = be_config_.sharpen.pad7 = be_config_.sharpen.pad8 = be_config_.sharpen.pad9 = 0; be_config_extra_.dirty_flags_rgb |= PISP_BE_RGB_ENABLE_SHARPEN; } void BackEnd::GetSharpen(pisp_be_sharpen_config &sharpen) const { sharpen = be_config_.sharpen; } void BackEnd::SetShFcCombine(pisp_be_sh_fc_combine_config const &sh_fc_combine) { be_config_.sh_fc_combine = sh_fc_combine; be_config_.sh_fc_combine.pad = 0; be_config_extra_.dirty_flags_extra |= PISP_BE_DIRTY_SH_FC_COMBINE; } void BackEnd::SetYcbcrInverse(pisp_be_ccm_config const &ycbcr_inverse) { be_config_.ycbcr_inverse = ycbcr_inverse; be_config_.ycbcr_inverse.pad[0] = be_config_.ycbcr_inverse.pad[1] = 0; be_config_extra_.dirty_flags_rgb |= PISP_BE_RGB_ENABLE_YCBCR_INVERSE; } void BackEnd::SetGamma(pisp_be_gamma_config const &gamma) { be_config_.gamma = gamma; be_config_extra_.dirty_flags_rgb |= PISP_BE_RGB_ENABLE_GAMMA; } void BackEnd::GetGamma(pisp_be_gamma_config &gamma) const { gamma = be_config_.gamma; } void BackEnd::SetCrop(pisp_be_crop_config const &crop) { for (unsigned int i = 0; i < variant_.BackEndNumBranches(0); i++) be_config_extra_.crop[i] = crop; be_config_extra_.dirty_flags_extra |= PISP_BE_DIRTY_CROP; retile_ = true; } void BackEnd::SetCrop(unsigned int i, pisp_be_crop_config const &crop) { PISP_ASSERT(i < variant_.BackEndNumBranches(0)); be_config_extra_.crop[i] = crop; be_config_extra_.dirty_flags_extra |= PISP_BE_DIRTY_CROP; retile_ = true; } void BackEnd::SetCsc(unsigned int i, pisp_be_ccm_config const &csc) { be_config_.csc[i] = csc; be_config_extra_.dirty_flags_bayer |= PISP_BE_RGB_ENABLE_CSC(i); } void BackEnd::GetCsc(unsigned int i, pisp_be_ccm_config &csc) const { csc = be_config_.csc[i]; } void BackEnd::SetDownscale(unsigned int i, pisp_be_downscale_config const &downscale, pisp_be_downscale_extra const &downscale_extra) { be_config_.downscale[i] = downscale; be_config_extra_.downscale[i] = downscale_extra; be_config_extra_.dirty_flags_rgb |= PISP_BE_RGB_ENABLE_DOWNSCALE(i); retile_ = true; } void BackEnd::SetDownscale(unsigned int i, pisp_be_downscale_extra const &downscale_extra) { be_config_extra_.downscale[i] = downscale_extra; be_config_extra_.dirty_flags_rgb |= PISP_BE_RGB_ENABLE_DOWNSCALE(i); retile_ = true; } void BackEnd::SetResample(unsigned int i, pisp_be_resample_config const &resample, pisp_be_resample_extra const &resample_extra) { be_config_.resample[i] = resample; be_config_extra_.resample[i] = resample_extra; be_config_extra_.dirty_flags_rgb |= PISP_BE_RGB_ENABLE_RESAMPLE(i); retile_ = true; } void BackEnd::SetResample(unsigned int i, pisp_be_resample_extra const &resample_extra) { be_config_extra_.resample[i] = resample_extra; be_config_extra_.dirty_flags_rgb |= PISP_BE_RGB_ENABLE_RESAMPLE(i); retile_ = true; } void BackEnd::SetOutputFormat(unsigned int i, pisp_be_output_format_config const &output_format) { PISP_ASSERT(i < variant_.BackEndNumBranches(0)); be_config_.output_format[i] = output_format; be_config_.output_format[i].pad[0] = be_config_.output_format[i].pad[1] = be_config_.output_format[i].pad[2] = 0; be_config_extra_.dirty_flags_rgb |= PISP_BE_RGB_ENABLE_OUTPUT(i); // Should only need a retile if the transform has changed, othwise a finalise_tile will do. retile_ = true; } void BackEnd::GetOutputFormat(unsigned int i, pisp_be_output_format_config &output_format) const { PISP_ASSERT(i < variant_.BackEndNumBranches(0)); output_format = be_config_.output_format[i]; } void BackEnd::SetSmartResize(unsigned int i, BackEnd::SmartResize const &smart_resize) { PISP_ASSERT(i < variant_.BackEndNumBranches(0)); // Non-zero width and height will be interpreted as "enabled". smart_resize_[i] = smart_resize; smart_resize_dirty_ |= (1 << i); } unsigned int BackEnd::GetMaxDownscale() const { // Return an estimate of the largest horizontal downscale we can do. unsigned int max_tile_width = config_.max_tile_width; if (!max_tile_width) max_tile_width = variant_.BackEndMaxTileWidth(0); // We reckon a 640 tile-width implementation can do 24x safely with formats that // want 1 byte per pixel. This should approximately scale with the max tile width. unsigned int downscale = 24; const unsigned int ref_tile_width = 640; return downscale * max_tile_width / ref_tile_width; } raspberrypi-libpisp-9ba67e6/src/libpisp/backend/backend.hpp000066400000000000000000000232141507172066400240730ustar00rootroot00000000000000 /* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (C) 2021 - 2023, Raspberry Pi Ltd * * backend.hpp - PiSP backend implementation */ #pragma once #include #include #include #include #include #include "common/shm_mutex.hpp" #include "tiling/pisp_tiling.hpp" #include "variants/variant.hpp" #include "pisp_be_config.h" // Definition of the PiSP Back End class. namespace libpisp { using TileArray = std::array; // We use std::array> insead of std::map<.,.> to ensure this object provides a standard layout. using YcbcrMap = std::array, 16>; using ResampleMap = std::array, 16>; using ResampleList = std::array, 16>; class BackEnd final { public: struct Config { enum Flags { NONE = 0, LOW_LATENCY = 1, // Attempt to process image with lowest possible latency (no longer implemented) HIGH_PRIORITY = 2 // Not currently implemented }; Config(unsigned int _max_stripe_height = 0, unsigned int _max_tile_width = 0, unsigned int _flags = 0, std::string _defaults_file = {}) : max_stripe_height(_max_stripe_height), max_tile_width(_max_tile_width), flags(_flags), defaults_file(_defaults_file) { } unsigned int max_stripe_height; // Use zero to get "default behaviour" unsigned int max_tile_width; // Can only go larger than h/w defined limit in simulations unsigned int flags; // An "or" of the Flags above std::string defaults_file; // json file for default IQ settings }; struct SmartResize { uint16_t width = 0; uint16_t height = 0; }; BackEnd(Config const &user_config, PiSPVariant const &variant); ~BackEnd(); void SetGlobal(pisp_be_global_config const &global); void GetGlobal(pisp_be_global_config &global) const; void SetInputFormat(pisp_image_format_config const &input_format); void GetInputFormat(pisp_image_format_config &input_format) const; void SetDecompress(pisp_decompress_config const &decompress); void GetDecompress(pisp_decompress_config &decompress) const; void SetDpc(pisp_be_dpc_config const &dpc); void GetDpc(pisp_be_dpc_config &dpc) const; void SetGeq(pisp_be_geq_config const &geq); void GetGeq(pisp_be_geq_config &geq) const; void SetTdnInputFormat(pisp_image_format_config const &tdn_input_format); void GetTdnInputFormat(pisp_image_format_config &tdn_input_format) const; void SetTdnDecompress(pisp_decompress_config const &tdn_decompress); void GetTdnDecompress(pisp_decompress_config &tdn_decompress) const; void SetTdn(pisp_be_tdn_config const &tdn); void GetTdn(pisp_be_tdn_config &tdn) const; void SetTdnCompress(pisp_compress_config const &tdn_compress); void GetTdnCompress(pisp_compress_config &tdn_compress) const; void SetTdnOutputFormat(pisp_image_format_config const &tdn_output_format); void GetTdnOutputFormat(pisp_image_format_config &tdn_output_format) const; void SetSdn(pisp_be_sdn_config const &sdn); void GetSdn(pisp_be_sdn_config &sdn) const; void SetBlc(pisp_bla_config const &blc); void GetBlc(pisp_bla_config &blc) const; void SetStitchInputFormat(pisp_image_format_config const &stitch_input_format); void GetStitchInputFormat(pisp_image_format_config &stitch_input_format) const; void SetStitchDecompress(pisp_decompress_config const &stitch_decompress); void GetStitchDecompress(pisp_decompress_config &stitch_decompress) const; void SetStitch(pisp_be_stitch_config const &stitch); void GetStitch(pisp_be_stitch_config &stitch) const; void SetStitchCompress(pisp_compress_config const &stitch_compress); void GetStitchCompress(pisp_compress_config &stitch_compress) const; void SetStitchOutputFormat(pisp_image_format_config const &stitch_output_format); void GetStitchOutputFormat(pisp_image_format_config &stitch_output_format) const; void SetWbg(pisp_wbg_config const &wbg); void GetWbg(pisp_wbg_config &wbg) const; void SetCdn(pisp_be_cdn_config const &cdn); void GetCdn(pisp_be_cdn_config &cdn) const; void SetLsc(pisp_be_lsc_config const &lsc, pisp_be_lsc_extra lsc_extra = { 0, 0 }); void GetLsc(pisp_be_lsc_config &lsc, pisp_be_lsc_extra &lsc_extra) const; void SetCac(pisp_be_cac_config const &cac, pisp_be_cac_extra cac_extra = { 0, 0 }); void GetCac(pisp_be_cac_config &cac, pisp_be_cac_extra &cac_extra) const; void SetDebin(pisp_be_debin_config const &debin); void GetDebin(pisp_be_debin_config &debin) const; void SetTonemap(pisp_be_tonemap_config const &tonemap); void GetTonemap(pisp_be_tonemap_config &tonemap) const; void SetDemosaic(pisp_be_demosaic_config const &demosaic); void GetDemosaic(pisp_be_demosaic_config &demosaic) const; void SetCcm(pisp_be_ccm_config const &ccm); void GetCcm(pisp_be_ccm_config &ccm) const; void SetSatControl(pisp_be_sat_control_config const &sat_control); void GetSatControl(pisp_be_sat_control_config &sat_control) const; void SetYcbcr(pisp_be_ccm_config const &ycbcr); void GetYcbcr(pisp_be_ccm_config &ccm) const; void SetFalseColour(pisp_be_false_colour_config const &false_colour); void GetFalseColour(pisp_be_false_colour_config &false_colour) const; void SetSharpen(pisp_be_sharpen_config const &sharpen); void GetSharpen(pisp_be_sharpen_config &sharpen) const; void SetShFcCombine(pisp_be_sh_fc_combine_config const &sh_fc_combine); void GetShFcCombine(pisp_be_sh_fc_combine_config &sh_fc_combine) const; void SetYcbcrInverse(pisp_be_ccm_config const &ycbcr_inverse); void GetYcbcrInverse(pisp_be_ccm_config &ycbcr_inverse) const; void SetGamma(pisp_be_gamma_config const &gamma); void GetGamma(pisp_be_gamma_config &gamma) const; void SetCrop(pisp_be_crop_config const &crop); void GetCrop(pisp_be_crop_config &crop) const; void SetCrop(unsigned int i, pisp_be_crop_config const &crop); void GetCrop(unsigned int i, pisp_be_crop_config &crop) const; void SetCsc(unsigned int i, pisp_be_ccm_config const &csc); void GetCsc(unsigned int i, pisp_be_ccm_config &csc) const; void SetOutputFormat(unsigned int i, pisp_be_output_format_config const &output_format); void GetOutputFormat(unsigned int i, pisp_be_output_format_config &output_format) const; void SetResample(unsigned int i, pisp_be_resample_config const &resample, pisp_be_resample_extra const &resample_extra); void GetResample(unsigned int i, pisp_be_resample_config &resample, pisp_be_resample_extra &resample_extra) const; void SetResample(unsigned int i, pisp_be_resample_extra const &resample_extra); void GetResample(unsigned int i, pisp_be_resample_extra &resample_extra) const; void SetDownscale(unsigned int i, pisp_be_downscale_config const &downscale, pisp_be_downscale_extra const &downscale_extra); void GetDownscale(unsigned int i, pisp_be_downscale_config &downscale, pisp_be_downscale_extra &downscale_extra) const; void SetDownscale(unsigned int i, pisp_be_downscale_extra const &downscale_extra); void GetDownscale(unsigned int i, pisp_be_downscale_extra &downscale_extra) const; void InitialiseYcbcr(pisp_be_ccm_config &ycbcr, const std::string &colour_space); void InitialiseYcbcrInverse(pisp_be_ccm_config &ycbcr_inverse, const std::string &colour_space); void InitialiseResample(pisp_be_resample_config &resample, const std::string &filter); void InitialiseResample(pisp_be_resample_config &resample, double downscale); void InitialiseSharpen(pisp_be_sharpen_config &sharpen, pisp_be_sh_fc_combine_config &shfc); void Prepare(pisp_be_tiles_config *config); bool ComputeOutputImageFormat(unsigned int i, pisp_image_format_config &output_format, pisp_image_format_config const &input_format) const; void SetSmartResize(unsigned int i, SmartResize const &smart_resize); unsigned int GetMaxDownscale() const; std::string GetJsonConfig(pisp_be_tiles_config *config); void SetJsonConfig(const std::string &json_config); void SetMaxTileWidth(unsigned int width) { config_.max_tile_width = width; } void SetMaxStripeHeight(unsigned int height) { config_.max_stripe_height = height; } void lock() { mutex_.lock(); } void unlock() { mutex_.unlock(); } bool try_lock() { return mutex_.try_lock(); } private: struct BeConfigExtra { // Non-register fields: pisp_be_lsc_extra lsc; pisp_be_cac_extra cac; pisp_be_downscale_extra downscale[PISP_BACK_END_NUM_OUTPUTS]; pisp_be_resample_extra resample[PISP_BACK_END_NUM_OUTPUTS]; pisp_be_crop_config crop[PISP_BACK_END_NUM_OUTPUTS]; uint32_t dirty_flags_bayer; //these use pisp_be_bayer_enable uint32_t dirty_flags_rgb; //use pisp_be_rgb_enable uint32_t dirty_flags_extra; //these use pisp_be_dirty_t }; void finaliseConfig(); void updateSmartResize(); void updateTiles(); TileArray retilePipeline(TilingConfig const &tiling_config); void finaliseTiling(); void getOutputSize(int output_num, uint16_t *width, uint16_t *height, pisp_image_format_config const &ifmt) const; void initialiseDefaultConfig(const std::string &filename); Config config_; const PiSPVariant variant_; pisp_be_config be_config_; BeConfigExtra be_config_extra_; pisp_image_format_config max_input_; bool retile_; bool finalise_tiling_; TileArray tiles_; int num_tiles_x_, num_tiles_y_; mutable ShmMutex mutex_; std::array smart_resize_; uint32_t smart_resize_dirty_; // Default config YcbcrMap ycbcr_map_; YcbcrMap inverse_ycbcr_map_; ResampleMap resample_filter_map_; ResampleList resample_select_list_; pisp_be_sharpen_config default_sharpen_; pisp_be_sh_fc_combine_config default_shfc_; }; // This is required to ensure we can safely share a BackEnd object across multiple processes. static_assert(std::is_standard_layout::value, "BackEnd must be a standard layout type"); } // namespace libpisp raspberrypi-libpisp-9ba67e6/src/libpisp/backend/backend_debug.cpp000066400000000000000000000612371507172066400252430ustar00rootroot00000000000000 /* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (C) 2021 - 2023, Raspberry Pi Ltd * * backend_prepare.cpp - PiSP Back End debug features */ #include "backend.hpp" #include #include #include "pisp_be_config.h" using namespace libpisp; using json = nlohmann::ordered_json; namespace { struct config_field { std::string name; std::size_t offset; std::size_t size; std::size_t num; }; struct config_block { std::string name; std::size_t offset; std::vector fields; }; const std::map mask { { 1, 0x000000ff }, { 2, 0x0000ffff }, { 4, 0xffffffff }, }; #define PISP_ARRAY_SIZE(s, f) (sizeof(((s *)0)->f) / sizeof(((s *)0)->f[0])) // clang-format off const std::vector be_config { { "global", offsetof(pisp_be_config, global), { { "bayer_enables", offsetof(pisp_be_global_config, bayer_enables), sizeof(uint32_t), 1 }, { "rgb_enables", offsetof(pisp_be_global_config, rgb_enables), sizeof(uint32_t), 1 }, { "bayer_order", offsetof(pisp_be_global_config, bayer_order), sizeof(uint8_t), 1 }, } }, { "input_format", offsetof(pisp_be_config, input_format), { { "width", offsetof(pisp_image_format_config, width), sizeof(uint16_t), 1 }, { "height", offsetof(pisp_image_format_config, height), sizeof(uint16_t), 1 }, { "format", offsetof(pisp_image_format_config, format), sizeof(uint32_t), 1 }, { "stride", offsetof(pisp_image_format_config, stride), sizeof(int32_t), 1 }, { "stride2", offsetof(pisp_image_format_config, stride2), sizeof(int32_t), 1 }, } }, { "decompress", offsetof(pisp_be_config, decompress), { { "offset", offsetof(pisp_decompress_config, offset), sizeof(uint16_t), 1 }, { "mode", offsetof(pisp_decompress_config, mode), sizeof(uint8_t), 1 }, } }, { "dpc", offsetof(pisp_be_config, dpc), { { "coeff_level", offsetof(pisp_be_dpc_config, coeff_level), sizeof(uint8_t), 1 }, { "coeff_range", offsetof(pisp_be_dpc_config, coeff_range), sizeof(uint8_t), 1 }, { "flags", offsetof(pisp_be_dpc_config, flags), sizeof(uint8_t), 1 }, } }, { "geq", offsetof(pisp_be_config, geq), { { "offset", offsetof(pisp_be_geq_config, offset), sizeof(uint16_t), 1 }, { "slope_sharper", offsetof(pisp_be_geq_config, slope_sharper), sizeof(uint16_t), 1 }, { "min", offsetof(pisp_be_geq_config, min), sizeof(uint16_t), 1 }, { "max", offsetof(pisp_be_geq_config, max), sizeof(uint16_t), 1 }, } }, { "tdn_input_format", offsetof(pisp_be_config, tdn_input_format), { { "width", offsetof(pisp_image_format_config, width), sizeof(uint16_t), 1 }, { "height", offsetof(pisp_image_format_config, height), sizeof(uint16_t), 1 }, { "format", offsetof(pisp_image_format_config, format), sizeof(uint32_t), 1 }, { "stride", offsetof(pisp_image_format_config, stride), sizeof(int32_t), 1 }, { "stride2", offsetof(pisp_image_format_config, stride2), sizeof(int32_t), 1 }, } }, { "tdn_decompress", offsetof(pisp_be_config, tdn_decompress), { { "offset", offsetof(pisp_decompress_config, offset), sizeof(uint16_t), 1 }, { "mode", offsetof(pisp_decompress_config, mode), sizeof(uint8_t), 1 }, } }, { "tdn", offsetof(pisp_be_config, tdn), { { "black_level", offsetof(pisp_be_tdn_config, black_level), sizeof(uint16_t), 1 }, { "ratio", offsetof(pisp_be_tdn_config, ratio), sizeof(uint16_t), 1 }, { "noise_constant", offsetof(pisp_be_tdn_config, noise_constant), sizeof(uint16_t), 1 }, { "noise_slope", offsetof(pisp_be_tdn_config, noise_slope), sizeof(uint16_t), 1 }, { "threshold", offsetof(pisp_be_tdn_config, threshold), sizeof(uint16_t), 1 }, { "reset", offsetof(pisp_be_tdn_config, reset), sizeof(uint8_t), 1 }, } }, { "tdn_compress", offsetof(pisp_be_config, tdn_compress), { { "offset", offsetof(pisp_compress_config, offset), sizeof(uint16_t), 1 }, { "mode", offsetof(pisp_compress_config, mode), sizeof(uint8_t), 1 }, } }, { "tdn_output_format", offsetof(pisp_be_config, tdn_output_format), { { "width", offsetof(pisp_image_format_config, width), sizeof(uint16_t), 1 }, { "height", offsetof(pisp_image_format_config, height), sizeof(uint16_t), 1 }, { "format", offsetof(pisp_image_format_config, format), sizeof(uint32_t), 1 }, { "stride", offsetof(pisp_image_format_config, stride), sizeof(int32_t), 1 }, { "stride2", offsetof(pisp_image_format_config, stride2), sizeof(int32_t), 1 }, } }, { "sdn", offsetof(pisp_be_config, sdn), { { "black_level", offsetof(pisp_be_sdn_config, black_level), sizeof(uint16_t), 1 }, { "leakage", offsetof(pisp_be_sdn_config, leakage), sizeof(uint8_t), 1 }, { "noise_constant", offsetof(pisp_be_sdn_config, noise_constant), sizeof(uint16_t), 1 }, { "noise_slope", offsetof(pisp_be_sdn_config, noise_slope), sizeof(uint16_t), 1 }, { "noise_constant2", offsetof(pisp_be_sdn_config, noise_constant2), sizeof(uint16_t), 1 }, { "noise_slope2", offsetof(pisp_be_sdn_config, noise_slope2), sizeof(uint16_t), 1 }, } }, { "blc", offsetof(pisp_be_config, blc), { { "black_level_r", offsetof(pisp_bla_config, black_level_r), sizeof(uint16_t), 1 }, { "black_level_gr", offsetof(pisp_bla_config, black_level_gr), sizeof(uint16_t), 1 }, { "black_level_gb", offsetof(pisp_bla_config, black_level_gb), sizeof(uint16_t), 1 }, { "black_level_b", offsetof(pisp_bla_config, black_level_b), sizeof(uint16_t), 1 }, { "output_black_level", offsetof(pisp_bla_config, output_black_level), sizeof(uint16_t), 1 }, } }, { "stitch_compress", offsetof(pisp_be_config, stitch_compress), { { "offset", offsetof(pisp_compress_config, offset), sizeof(uint16_t), 1 }, { "mode", offsetof(pisp_compress_config, mode), sizeof(uint8_t), 1 }, } }, { "stitch_output_format", offsetof(pisp_be_config, stitch_output_format), { { "width", offsetof(pisp_image_format_config, width), sizeof(uint16_t), 1 }, { "height", offsetof(pisp_image_format_config, height), sizeof(uint16_t), 1 }, { "format", offsetof(pisp_image_format_config, format), sizeof(uint32_t), 1 }, { "stride", offsetof(pisp_image_format_config, stride), sizeof(int32_t), 1 }, { "stride2", offsetof(pisp_image_format_config, stride2), sizeof(int32_t), 1 }, } }, { "stitch_input_format", offsetof(pisp_be_config, stitch_input_format), { { "width", offsetof(pisp_image_format_config, width), sizeof(uint16_t), 1 }, { "height", offsetof(pisp_image_format_config, height), sizeof(uint16_t), 1 }, { "format", offsetof(pisp_image_format_config, format), sizeof(uint32_t), 1 }, { "stride", offsetof(pisp_image_format_config, stride), sizeof(int32_t), 1 }, { "stride2", offsetof(pisp_image_format_config, stride2), sizeof(int32_t), 1 }, } }, { "stitch_decompress", offsetof(pisp_be_config, stitch_decompress), { { "offset", offsetof(pisp_decompress_config, offset), sizeof(uint16_t), 1 }, { "mode", offsetof(pisp_decompress_config, mode), sizeof(uint8_t), 1 }, } }, { "stitch", offsetof(pisp_be_config, stitch), { { "threshold_lo", offsetof(pisp_be_stitch_config, threshold_lo), sizeof(uint16_t), 1 }, { "threshold_diff_power", offsetof(pisp_be_stitch_config, threshold_diff_power), sizeof(uint8_t), 1 }, { "exposure_ratio", offsetof(pisp_be_stitch_config, exposure_ratio), sizeof(uint16_t), 1 }, { "motion_threshold_256", offsetof(pisp_be_stitch_config, motion_threshold_256), sizeof(uint8_t), 1 }, { "motion_threshold_recip", offsetof(pisp_be_stitch_config, motion_threshold_recip), sizeof(uint8_t), 1 }, } }, { "lsc", offsetof(pisp_be_config, lsc), { { "grid_step_x", offsetof(pisp_be_lsc_config, grid_step_x), sizeof(uint16_t), 1 }, { "grid_step_y", offsetof(pisp_be_lsc_config, grid_step_y), sizeof(uint16_t), 1 }, { "lut_packed", offsetof(pisp_be_lsc_config, lut_packed), sizeof(uint32_t), PISP_ARRAY_SIZE(pisp_be_lsc_config, lut_packed) }, } }, { "wbg", offsetof(pisp_be_config, wbg), { { "gain_r", offsetof(pisp_wbg_config, gain_r), sizeof(uint16_t), 1 }, { "gain_g", offsetof(pisp_wbg_config, gain_g), sizeof(uint16_t), 1 }, { "gain_b", offsetof(pisp_wbg_config, gain_b), sizeof(uint16_t), 1 }, } }, { "cdn", offsetof(pisp_be_config, cdn), { { "thresh", offsetof(pisp_be_cdn_config, thresh), sizeof(uint16_t), 1 }, { "iir_strength", offsetof(pisp_be_cdn_config, iir_strength), sizeof(uint8_t), 1 }, { "g_adjust", offsetof(pisp_be_cdn_config, g_adjust), sizeof(uint8_t), 1 }, } }, { "cac", offsetof(pisp_be_config, cac), { { "grid_step_x", offsetof(pisp_be_cac_config, grid_step_x), sizeof(uint16_t), 1 }, { "grid_step_y", offsetof(pisp_be_cac_config, grid_step_y), sizeof(uint8_t), 1 }, { "lut", offsetof(pisp_be_cac_config, lut), sizeof(int8_t), PISP_ARRAY_SIZE(pisp_be_cac_config, lut) }, } }, { "debin", offsetof(pisp_be_config, debin), { { "coeffs", offsetof(pisp_be_debin_config, coeffs), sizeof(int8_t), PISP_ARRAY_SIZE(pisp_be_debin_config, coeffs) }, { "h_enable", offsetof(pisp_be_debin_config, h_enable), sizeof(int8_t), 1 }, { "v_enable", offsetof(pisp_be_debin_config, v_enable), sizeof(int8_t), 1 }, } }, { "tonemap", offsetof(pisp_be_config, tonemap), { { "detail_constant", offsetof(pisp_be_tonemap_config, detail_constant), sizeof(uint16_t), 1 }, { "detail_slope", offsetof(pisp_be_tonemap_config, detail_slope), sizeof(uint16_t), 1 }, { "iir_strength", offsetof(pisp_be_tonemap_config, iir_strength), sizeof(uint16_t), 1 }, { "strength", offsetof(pisp_be_tonemap_config, strength), sizeof(uint16_t), 1 }, { "lut", offsetof(pisp_be_tonemap_config, lut), sizeof(uint32_t), PISP_ARRAY_SIZE(pisp_be_tonemap_config, lut) }, } }, { "demosaic", offsetof(pisp_be_config, demosaic), { { "sharper", offsetof(pisp_be_demosaic_config, sharper), sizeof(uint8_t), 1 }, { "fc_mode", offsetof(pisp_be_demosaic_config, fc_mode), sizeof(uint8_t), 1 }, } }, { "ccm", offsetof(pisp_be_config, ccm), { { "coeffs", offsetof(pisp_be_ccm_config, coeffs), sizeof(int16_t), PISP_ARRAY_SIZE(pisp_be_ccm_config, coeffs) }, { "offsets", offsetof(pisp_be_ccm_config, offsets), sizeof(int32_t), PISP_ARRAY_SIZE(pisp_be_ccm_config, offsets) }, } }, { "sat_control", offsetof(pisp_be_config, sat_control), { { "shift_r", offsetof(pisp_be_sat_control_config, shift_r), sizeof(uint8_t), 1 }, { "shift_g", offsetof(pisp_be_sat_control_config, shift_g), sizeof(uint8_t), 1 }, { "shift_b", offsetof(pisp_be_sat_control_config, shift_b), sizeof(uint8_t), 1 }, } }, { "ycbcr", offsetof(pisp_be_config, ycbcr), { { "coeffs", offsetof(pisp_be_ccm_config, coeffs), sizeof(int16_t), PISP_ARRAY_SIZE(pisp_be_ccm_config, coeffs) }, { "offsets", offsetof(pisp_be_ccm_config, offsets), sizeof(int32_t), PISP_ARRAY_SIZE(pisp_be_ccm_config, offsets) }, } }, { "sharpen", offsetof(pisp_be_config, sharpen), { { "kernel0", offsetof(pisp_be_sharpen_config, kernel0), sizeof(int8_t), PISP_ARRAY_SIZE(pisp_be_sharpen_config, kernel0) }, { "kernel1", offsetof(pisp_be_sharpen_config, kernel1), sizeof(int8_t), PISP_ARRAY_SIZE(pisp_be_sharpen_config, kernel1) }, { "kernel2", offsetof(pisp_be_sharpen_config, kernel2), sizeof(int8_t), PISP_ARRAY_SIZE(pisp_be_sharpen_config, kernel2) }, { "kernel3", offsetof(pisp_be_sharpen_config, kernel3), sizeof(int8_t), PISP_ARRAY_SIZE(pisp_be_sharpen_config, kernel3) }, { "kernel4", offsetof(pisp_be_sharpen_config, kernel4), sizeof(int8_t), PISP_ARRAY_SIZE(pisp_be_sharpen_config, kernel4) }, { "threshold_offset0", offsetof(pisp_be_sharpen_config, threshold_offset0), sizeof(uint16_t), 1 }, { "threshold_slope0", offsetof(pisp_be_sharpen_config, threshold_slope0), sizeof(uint16_t), 1 }, { "threshold_offset1", offsetof(pisp_be_sharpen_config, threshold_offset1), sizeof(uint16_t), 1 }, { "threshold_slope1", offsetof(pisp_be_sharpen_config, threshold_slope1), sizeof(uint16_t), 1 }, { "threshold_offset2", offsetof(pisp_be_sharpen_config, threshold_offset2), sizeof(uint16_t), 1 }, { "threshold_slope2", offsetof(pisp_be_sharpen_config, threshold_slope2), sizeof(uint16_t), 1 }, { "threshold_offset3", offsetof(pisp_be_sharpen_config, threshold_offset3), sizeof(uint16_t), 1 }, { "threshold_slope3", offsetof(pisp_be_sharpen_config, threshold_slope3), sizeof(uint16_t), 1 }, { "threshold_offset4", offsetof(pisp_be_sharpen_config, threshold_offset4), sizeof(uint16_t), 1 }, { "threshold_slope4", offsetof(pisp_be_sharpen_config, threshold_slope4), sizeof(uint16_t), 1 }, { "positive_strength", offsetof(pisp_be_sharpen_config, positive_strength), sizeof(uint16_t), 1 }, { "positive_pre_limit", offsetof(pisp_be_sharpen_config, positive_pre_limit), sizeof(uint16_t), 1 }, { "positive_func", offsetof(pisp_be_sharpen_config, positive_func), sizeof(uint16_t), PISP_ARRAY_SIZE(pisp_be_sharpen_config, positive_func) }, { "positive_limit", offsetof(pisp_be_sharpen_config, positive_limit), sizeof(uint16_t), 1 }, { "negative_strength", offsetof(pisp_be_sharpen_config, negative_strength), sizeof(uint16_t), 1 }, { "negative_pre_limit", offsetof(pisp_be_sharpen_config, negative_pre_limit), sizeof(uint16_t), 1 }, { "negative_func", offsetof(pisp_be_sharpen_config, negative_func), sizeof(uint16_t), PISP_ARRAY_SIZE(pisp_be_sharpen_config, negative_func) }, { "negative_limit", offsetof(pisp_be_sharpen_config, negative_limit), sizeof(uint16_t), 1 }, { "enables", offsetof(pisp_be_sharpen_config, enables), sizeof(uint8_t), 1 }, { "white", offsetof(pisp_be_sharpen_config, white), sizeof(uint8_t), 1 }, { "black", offsetof(pisp_be_sharpen_config, black), sizeof(uint8_t), 1 }, { "grey", offsetof(pisp_be_sharpen_config, grey), sizeof(uint8_t), 1 }, } }, { "false_colour", offsetof(pisp_be_config, false_colour), { { "distance", offsetof(pisp_be_false_colour_config, distance), sizeof(uint8_t), 1 }, } }, { "sh_fc_combine", offsetof(pisp_be_config, sh_fc_combine), { { "y_factor", offsetof(pisp_be_sh_fc_combine_config, y_factor), sizeof(uint8_t), 1 }, { "c1_factor", offsetof(pisp_be_sh_fc_combine_config, c1_factor), sizeof(uint8_t), 1 }, { "c2_factor", offsetof(pisp_be_sh_fc_combine_config, c2_factor), sizeof(uint8_t), 1 }, } }, { "ycbcr_inverse", offsetof(pisp_be_config, ycbcr_inverse), { { "coeffs", offsetof(pisp_be_ccm_config, coeffs), sizeof(int16_t), PISP_ARRAY_SIZE(pisp_be_ccm_config, coeffs) }, { "offsets", offsetof(pisp_be_ccm_config, offsets), sizeof(int32_t), PISP_ARRAY_SIZE(pisp_be_ccm_config, offsets) }, } }, { "gamma", offsetof(pisp_be_config, gamma), { { "lut", offsetof(pisp_be_gamma_config, lut), sizeof(uint32_t), PISP_ARRAY_SIZE(pisp_be_gamma_config, lut) }, } }, { "csc0", offsetof(pisp_be_config, csc[0]), { { "coeffs", offsetof(pisp_be_ccm_config, coeffs), sizeof(int16_t), PISP_ARRAY_SIZE(pisp_be_ccm_config, coeffs) }, { "offsets", offsetof(pisp_be_ccm_config, offsets), sizeof(int32_t), PISP_ARRAY_SIZE(pisp_be_ccm_config, offsets) }, } }, { "csc1", offsetof(pisp_be_config, csc[1]), { { "coeffs", offsetof(pisp_be_ccm_config, coeffs), sizeof(int16_t), PISP_ARRAY_SIZE(pisp_be_ccm_config, coeffs) }, { "offsets", offsetof(pisp_be_ccm_config, offsets), sizeof(int32_t), PISP_ARRAY_SIZE(pisp_be_ccm_config, offsets) }, } }, { "downscale0", offsetof(pisp_be_config, downscale[0]), { { "scale_factor_h", offsetof(pisp_be_downscale_config, scale_factor_h), sizeof(uint16_t), 1 }, { "scale_factor_v", offsetof(pisp_be_downscale_config, scale_factor_v), sizeof(uint16_t), 1 }, { "scale_recip_h", offsetof(pisp_be_downscale_config, scale_recip_h), sizeof(uint16_t), 1 }, { "scale_recip_v", offsetof(pisp_be_downscale_config, scale_recip_v), sizeof(uint16_t), 1 }, } }, { "downscale1", offsetof(pisp_be_config, downscale[1]), { { "scale_factor_h", offsetof(pisp_be_downscale_config, scale_factor_h), sizeof(uint16_t), 1 }, { "scale_factor_v", offsetof(pisp_be_downscale_config, scale_factor_v), sizeof(uint16_t), 1 }, { "scale_recip_h", offsetof(pisp_be_downscale_config, scale_recip_h), sizeof(uint16_t), 1 }, { "scale_recip_v", offsetof(pisp_be_downscale_config, scale_recip_v), sizeof(uint16_t), 1 }, } }, { "resample0", offsetof(pisp_be_config, resample[0]), { { "scale_factor_h", offsetof(pisp_be_resample_config, scale_factor_h), sizeof(uint16_t), 1 }, { "scale_factor_v", offsetof(pisp_be_resample_config, scale_factor_v), sizeof(uint16_t), 1 }, { "coef", offsetof(pisp_be_resample_config, coef), sizeof(int16_t), PISP_ARRAY_SIZE(pisp_be_resample_config, coef) }, } }, { "resample1", offsetof(pisp_be_config, resample[1]), { { "scale_factor_h", offsetof(pisp_be_resample_config, scale_factor_h), sizeof(uint16_t), 1 }, { "scale_factor_v", offsetof(pisp_be_resample_config, scale_factor_v), sizeof(uint16_t), 1 }, { "coef", offsetof(pisp_be_resample_config, coef), sizeof(int16_t), PISP_ARRAY_SIZE(pisp_be_resample_config, coef) }, } }, { "output_format0", offsetof(pisp_be_config, output_format[0]), { { "width", offsetof(pisp_image_format_config, width), sizeof(uint16_t), 1 }, { "height", offsetof(pisp_image_format_config, height), sizeof(uint16_t), 1 }, { "format", offsetof(pisp_image_format_config, format), sizeof(uint32_t), 1 }, { "stride", offsetof(pisp_image_format_config, stride), sizeof(int32_t), 1 }, { "stride2", offsetof(pisp_image_format_config, stride2), sizeof(int32_t), 1 }, { "transform", offsetof(pisp_be_output_format_config, transform), sizeof(uint8_t), 1 }, { "lo", offsetof(pisp_be_output_format_config, lo), sizeof(uint8_t), 1 }, { "hi", offsetof(pisp_be_output_format_config, hi), sizeof(uint8_t), 1 }, { "lo2", offsetof(pisp_be_output_format_config, lo2), sizeof(uint8_t), 1 }, { "hi2", offsetof(pisp_be_output_format_config, hi2), sizeof(uint8_t), 1 }, } }, { "output_format1", offsetof(pisp_be_config, output_format[1]), { { "width", offsetof(pisp_image_format_config, width), sizeof(uint16_t), 1 }, { "height", offsetof(pisp_image_format_config, height), sizeof(uint16_t), 1 }, { "format", offsetof(pisp_image_format_config, format), sizeof(uint32_t), 1 }, { "stride", offsetof(pisp_image_format_config, stride), sizeof(int32_t), 1 }, { "stride2", offsetof(pisp_image_format_config, stride2), sizeof(int32_t), 1 }, { "transform", offsetof(pisp_be_output_format_config, transform), sizeof(uint8_t), 1 }, { "lo", offsetof(pisp_be_output_format_config, lo), sizeof(uint8_t), 1 }, { "hi", offsetof(pisp_be_output_format_config, hi), sizeof(uint8_t), 1 }, { "lo2", offsetof(pisp_be_output_format_config, lo2), sizeof(uint8_t), 1 }, { "hi2", offsetof(pisp_be_output_format_config, hi2), sizeof(uint8_t), 1 }, } }, { "hog", offsetof(pisp_be_config, hog), { { "compute_signed", offsetof(pisp_be_hog_config, compute_signed), sizeof(uint8_t), 1 }, { "channel_mix", offsetof(pisp_be_hog_config, channel_mix), sizeof(uint8_t), PISP_ARRAY_SIZE(pisp_be_hog_config, channel_mix) }, { "stride", offsetof(pisp_be_hog_config, stride), sizeof(uint32_t), 1 }, } } /* The "axi" field here is never configured or used; the real BE_AXI register is not part of the config */ }; const config_block tiles_config { "tiles", 0, { { "edge", offsetof(pisp_tile, edge), sizeof(uint8_t), 1 }, { "input_addr_offset", offsetof(pisp_tile, input_addr_offset), sizeof(uint32_t), 1 }, { "input_addr_offset2", offsetof(pisp_tile, input_addr_offset2), sizeof(uint32_t), 1 }, { "input_offset_x", offsetof(pisp_tile, input_offset_x), sizeof(uint16_t), 1 }, { "input_offset_y", offsetof(pisp_tile, input_offset_y), sizeof(uint16_t), 1 }, { "input_width", offsetof(pisp_tile, input_width), sizeof(uint16_t), 1 }, { "input_height", offsetof(pisp_tile, input_width), sizeof(uint16_t), 1 }, { "tdn_input_addr_offset", offsetof(pisp_tile, tdn_input_addr_offset), sizeof(uint32_t), 1 }, { "tdn_output_addr_offset", offsetof(pisp_tile, tdn_output_addr_offset), sizeof(uint32_t), 1 }, { "stitch_input_addr_offset", offsetof(pisp_tile, stitch_input_addr_offset), sizeof(uint32_t), 1 }, { "stitch_output_addr_offset", offsetof(pisp_tile, stitch_output_addr_offset), sizeof(uint32_t), 1 }, { "lsc_grid_offset_x", offsetof(pisp_tile, lsc_grid_offset_x), sizeof(uint32_t), 1 }, { "lsc_grid_offset_y", offsetof(pisp_tile, lsc_grid_offset_y), sizeof(uint32_t), 1 }, { "cac_grid_offset_x", offsetof(pisp_tile, cac_grid_offset_x), sizeof(uint32_t), 1 }, { "cac_grid_offset_y", offsetof(pisp_tile, cac_grid_offset_y), sizeof(uint32_t), 1 }, { "crop_x_start", offsetof(pisp_tile, crop_x_start), sizeof(uint16_t), PISP_ARRAY_SIZE(pisp_tile, crop_x_start) }, { "crop_x_end", offsetof(pisp_tile, crop_x_end), sizeof(uint16_t), PISP_ARRAY_SIZE(pisp_tile, crop_x_end) }, { "crop_y_start", offsetof(pisp_tile, crop_y_start), sizeof(uint16_t), PISP_ARRAY_SIZE(pisp_tile, crop_y_start) }, { "crop_y_end", offsetof(pisp_tile, crop_y_end), sizeof(uint16_t), PISP_ARRAY_SIZE(pisp_tile, crop_y_end) }, { "downscale_phase_x", offsetof(pisp_tile, downscale_phase_x), sizeof(uint16_t), PISP_ARRAY_SIZE(pisp_tile, downscale_phase_x) }, { "downscale_phase_y", offsetof(pisp_tile, downscale_phase_y), sizeof(uint16_t), PISP_ARRAY_SIZE(pisp_tile, downscale_phase_y) }, { "resample_in_width", offsetof(pisp_tile, resample_in_width), sizeof(uint16_t), PISP_ARRAY_SIZE(pisp_tile, resample_in_width) }, { "resample_in_height", offsetof(pisp_tile, resample_in_height), sizeof(uint16_t), PISP_ARRAY_SIZE(pisp_tile, resample_in_height) }, { "resample_phase_x", offsetof(pisp_tile, resample_phase_x), sizeof(uint16_t), PISP_ARRAY_SIZE(pisp_tile, resample_phase_x) }, { "resample_phase_y", offsetof(pisp_tile, resample_phase_y), sizeof(uint16_t), PISP_ARRAY_SIZE(pisp_tile, resample_phase_y) }, { "output_offset_x", offsetof(pisp_tile, output_offset_x), sizeof(uint16_t), PISP_ARRAY_SIZE(pisp_tile, output_offset_x) }, { "output_offset_y", offsetof(pisp_tile, output_offset_y), sizeof(uint16_t), PISP_ARRAY_SIZE(pisp_tile, output_offset_y) }, { "output_width", offsetof(pisp_tile, output_width), sizeof(uint16_t), PISP_ARRAY_SIZE(pisp_tile, output_width) }, { "output_height", offsetof(pisp_tile, output_height), sizeof(uint16_t), PISP_ARRAY_SIZE(pisp_tile, output_height) }, { "output_addr_offset", offsetof(pisp_tile, output_addr_offset), sizeof(uint32_t), PISP_ARRAY_SIZE(pisp_tile, output_addr_offset) }, { "output_addr_offset2", offsetof(pisp_tile, output_addr_offset2), sizeof(uint32_t), PISP_ARRAY_SIZE(pisp_tile, output_addr_offset2) }, { "output_hog_addr_offset", offsetof(pisp_tile, output_hog_addr_offset), sizeof(uint32_t), 1 }, } }; uint32_t read_val(uint8_t *ptr, uint32_t offset, uint32_t size) { uint32_t val = 0; for (unsigned int i = 0; i < size; i++) val |= (*(ptr + offset + i)) << (i * 8); return val & mask.at(size); } void write_val(uint8_t *ptr, uint32_t offset, uint32_t size, uint32_t val) { for (unsigned int i = 0; i < size; i++) *(ptr + offset + i) = (val >> (i * 8)) & 0xff; } } // namespace std::string BackEnd::GetJsonConfig(pisp_be_tiles_config *config) { json j = { {"version", 1.0}, {"be_revision", variant_.BackEndVersion()} }; for (auto const ¶m : be_config) { json b; for (auto const &field : param.fields) { if (field.num == 1) { b[param.name][field.name] = read_val((uint8_t *)config, param.offset + field.offset, field.size); } else { for (unsigned int i = 0; i < field.num; i++) b[param.name][field.name].push_back(read_val((uint8_t *)config, param.offset + field.offset + i * field.size, field.size)); } } j["config"].push_back(b); } for (unsigned int t = 0; t < config->num_tiles; t++) { json b; for (auto const &field : tiles_config.fields) { if (field.num == 1) { b[field.name] = read_val((uint8_t *)&config->tiles[t], field.offset, field.size); } else { for (unsigned int i = 0; i < field.num; i++) b[field.name].push_back(read_val((uint8_t *)&config->tiles[t], field.offset + i * field.size, field.size)); } } j["tiles"].push_back(b); } return j.dump(4); } void BackEnd::SetJsonConfig(const std::string &json_str) { json j = json::parse(json_str); pisp_be_config *config = &be_config_; for (auto const ¶m : be_config) { for (auto const &field : param.fields) { if (field.num == 1) { uint32_t value = j["config"][param.name][field.name].get(); write_val((uint8_t *)config, param.offset + field.offset, field.size, value); } else { std::vector values = j["config"][param.name][field.name].get>(); for (unsigned int i = 0; i < values.size(); i++) write_val((uint8_t *)config, param.offset + field.offset + i * field.size, field.size, values[i]); } } } // Clear any dirty flags so no reconfiguration happens on the next Prepare() call. be_config_extra_.dirty_flags_bayer = be_config_extra_.dirty_flags_rgb = be_config_extra_.dirty_flags_extra = 0; // But do retile the pipeline to get the tile structures setup correctly. retile_ = true; } // clang-format on raspberrypi-libpisp-9ba67e6/src/libpisp/backend/backend_default_config.cpp000066400000000000000000000316251507172066400271240ustar00rootroot00000000000000 /* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (C) 2021 - 2023, Raspberry Pi Ltd * * backend_default_config.cpp - Default configuration setup for the PiSP Back End */ #include "backend.hpp" #include #include #include #include #include #include #include #include #include "backend/pisp_build_config.h" #include "common/pisp_pwl.hpp" #include "pisp_be_config.h" // musl doesn't declare _DYNAMIC in link.h, declare it manually, see: // https://github.com/raspberrypi/libpisp/issues/25 extern ElfW(Dyn) _DYNAMIC[]; using json = nlohmann::json; // Where it might be helpful we initialise some blocks with the "obvious" default parameters. This saves users the trouble, // and they can just "enable" the blocks. namespace { // Check if the RPATH or RUNPATH definitions exist in the ELF file. If they don't exist, it means that meson has // stripped them out when doing the install step. // // Source: https://stackoverflow.com/questions/2836330/is-there-a-programmatic-way-to-inspect-the-current-rpath-on-linux bool is_installed() { const ElfW(Dyn) *dyn = _DYNAMIC; for (; dyn->d_tag != DT_NULL; dyn++) { if (dyn->d_tag == DT_RPATH || dyn->d_tag == DT_RUNPATH) return false; } return true; } std::string source_path() { Dl_info dl_info; std::string path; if (dladdr(reinterpret_cast(source_path), &dl_info)) path = dl_info.dli_fname; if (path.empty()) return {}; auto const pos = path.find_last_of('/'); if (pos == std::string::npos) return {}; path.erase(pos, path.length() - pos); return path; } void initialise_debin(pisp_be_debin_config &debin, const json &root) { constexpr unsigned int num_coefs = sizeof(debin.coeffs) / sizeof(debin.coeffs[0]); auto coefs = root["debin"]["coefs"].get>(); if (coefs.size() != num_coefs) throw std::runtime_error("Debin filter size mismatch"); memcpy(debin.coeffs, coefs.data(), sizeof(debin.coeffs)); debin.h_enable = debin.v_enable = 1; } void initialise_demosaic(pisp_be_demosaic_config &demosaic, const json &root) { auto ¶ms = root["demosaic"]; demosaic.sharper = params["sharper"].get(); demosaic.fc_mode = params["fc_mode"].get(); } void initialise_false_colour(pisp_be_false_colour_config &fc, const json &root) { auto ¶ms = root["false_colour"]; fc.distance = params["distance"].get(); } void initialise_gamma(pisp_be_gamma_config &gamma, const json &root) { constexpr unsigned int num_points = sizeof(gamma.lut) / sizeof(gamma.lut[0]); libpisp::Pwl pwl; pwl.Read(root["gamma"]["lut"]); static constexpr unsigned int SlopeBits = 14; static constexpr unsigned int PosBits = 16; int lastY = 0; for (unsigned int i = 0; i < num_points; i++) { int x, y; if (i < 32) x = i * 512; else if (i < 48) x = (i - 32) * 1024 + 16384; else x = std::min(65535u, (i - 48) * 2048 + 32768); y = pwl.Eval(x); if (y < 0 || (i && y < lastY)) throw std::runtime_error("initialise_gamma: Malformed LUT"); if (i) { unsigned int slope = y - lastY; if (slope >= (1u << SlopeBits)) { slope = (1u << SlopeBits) - 1; y = lastY + slope; } gamma.lut[i - 1] |= slope << PosBits; } gamma.lut[i] = y; lastY = y; } } void read_resample(libpisp::ResampleMap &resample_filter_map, libpisp::ResampleList &resample_select_list, const json &root) { auto &filters = root["resample"]["filters"]; unsigned int i = 0, j = 0; for (auto const &[name, filter] : filters.items()) { pisp_be_resample_config r; constexpr unsigned int num_coefs = sizeof(r.coef) / sizeof(r.coef[0]); auto coefs = filter.get>(); if (coefs.size() != num_coefs) throw std::runtime_error("read_resample: Incorrect number of filter coefficients"); memcpy(r.coef, coefs.data(), sizeof(r.coef)); resample_filter_map[i++] = { name, r }; if (i == resample_filter_map.size()) break; } i = 0; auto &smart = root["resample"]["smart_selection"]; for (auto &scale : smart["downscale"]) { resample_select_list[i++] = { scale.get(), std::string {} }; if (i == resample_select_list.size()) break; } for (auto &filter : smart["filter"]) { resample_select_list[j++].second = filter.get(); if (j == resample_select_list.size()) break; } if (j != i) throw std::runtime_error("read_resample: Incorrect number of smart filters/downscale factors"); } // Macros for the sharpening filters, to avoid repeating the same code 5 times #define FILTER(i) \ { \ auto filter = params["filter" #i]; \ std::vector kernel_v; \ for (auto &x : filter["kernel"]) \ kernel_v.push_back(x.get()); \ int8_t *kernel = &kernel_v[0]; \ uint16_t offset = filter["offset"].get(); \ uint16_t threshold_slope = filter["threshold_slope"].get(); \ uint16_t scale = filter["scale"].get(); \ memcpy(sharpen.kernel##i, kernel, sizeof(sharpen.kernel##i)); \ memcpy(&sharpen.threshold_offset##i, &offset, sizeof(sharpen.threshold_offset##i)); \ memcpy(&sharpen.threshold_slope##i, &threshold_slope, sizeof(sharpen.threshold_slope##i)); \ memcpy(&sharpen.scale##i, &scale, sizeof(sharpen.scale##i)); \ } #define POS_NEG(i) \ { \ auto tive = params[#i "tive"]; \ uint16_t strength = tive["strength"].get(); \ uint16_t pre_limit = tive["pre_limit"].get(); \ std::vector function_v; \ for (auto &x : tive["function"]) \ function_v.push_back(x.get()); \ uint16_t *function = &function_v[0]; \ uint16_t limit = tive["limit"].get(); \ memcpy(&sharpen.i##tive_strength, &strength, sizeof(sharpen.i##tive_strength)); \ memcpy(&sharpen.i##tive_pre_limit, &pre_limit, sizeof(sharpen.i##tive_pre_limit)); \ memcpy(sharpen.i##tive_func, function, sizeof(sharpen.i##tive_func)); \ memcpy(&sharpen.i##tive_limit, &limit, sizeof(sharpen.i##tive_limit)); \ } void read_sharpen(pisp_be_sharpen_config &sharpen, pisp_be_sh_fc_combine_config &shfc, const json &root) { auto params = root["sharpen"]; FILTER(0); FILTER(1); FILTER(2); FILTER(3); FILTER(4); POS_NEG(posi); POS_NEG(nega); std::string enables_s = params["enables"].get(); sharpen.enables = std::stoul(enables_s, nullptr, 16); sharpen.white = params["white"].get(); sharpen.black = params["black"].get(); sharpen.grey = params["grey"].get(); memset(&shfc, 0, sizeof(shfc)); shfc.y_factor = params["shfc_y_factor"].get() * (1 << 8); } void read_ycbcr(libpisp::YcbcrMap &ycbcr_map, libpisp::YcbcrMap &inverse_ycbcr_map, const json &root) { auto encoding = root["colour_encoding"]; unsigned int i = 0; for (auto const &[format, enc] : encoding.items()) { static const std::string keys[2] { "ycbcr", "ycbcr_inverse" }; for (auto &key : keys) { auto &matrix = enc[key]; pisp_be_ccm_config ccm; auto coeffs = matrix["coeffs"].get>(); if (coeffs.size() != 9) throw std::runtime_error("read_ycbcr: Incorrect number of matrix coefficients"); memcpy(ccm.coeffs, coeffs.data(), sizeof(ccm.coeffs)); auto offsets = matrix["offsets"].get>(); if (offsets.size() != 3) throw std::runtime_error("read_ycbcr: Incorrect number of matrix offsets"); memcpy(ccm.offsets, offsets.data(), sizeof(ccm.offsets)); if (key == "ycbcr") ycbcr_map[i] = { format, ccm }; else inverse_ycbcr_map[i] = { format, ccm }; } if (++i == ycbcr_map.size()) break; } } void get_matrix(pisp_be_ccm_config &matrix, const libpisp::YcbcrMap &map, const std::string &colour_space) { memset(matrix.coeffs, 0, sizeof(matrix.coeffs)); memset(matrix.offsets, 0, sizeof(matrix.offsets)); auto it = std::find_if(map.begin(), map.end(), [&colour_space](const auto &m) { return m.first == colour_space; }); if (it != map.end()) { memcpy(matrix.coeffs, it->second.coeffs, sizeof(matrix.coeffs)); memcpy(matrix.offsets, it->second.offsets, sizeof(matrix.offsets)); } } } // namespace namespace libpisp { void BackEnd::InitialiseYcbcr(pisp_be_ccm_config &ycbcr, const std::string &colour_space) { get_matrix(ycbcr, ycbcr_map_, colour_space); } void BackEnd::InitialiseYcbcrInverse(pisp_be_ccm_config &ycbcr_inverse, const std::string &colour_space) { get_matrix(ycbcr_inverse, inverse_ycbcr_map_, colour_space); } void BackEnd::InitialiseResample(pisp_be_resample_config &resample, const std::string &filter) { memset(resample.coef, 0, sizeof(resample.coef)); auto it = std::find_if(resample_filter_map_.begin(), resample_filter_map_.end(), [&filter](const auto &m) { return m.first == filter; }); if (it != resample_filter_map_.end()) memcpy(resample.coef, it->second.coef, sizeof(resample.coef)); } void BackEnd::InitialiseResample(pisp_be_resample_config &resample, double downscale) { auto it = std::find_if(resample_select_list_.begin(), resample_select_list_.end(), [downscale](const auto &item) { return item.first >= downscale; }); if (it != resample_select_list_.end()) InitialiseResample(resample, it->second); else InitialiseResample(resample, resample_select_list_.back().second); } void BackEnd::InitialiseSharpen(pisp_be_sharpen_config &sharpen, pisp_be_sh_fc_combine_config &shfc) { sharpen = default_sharpen_; shfc = default_shfc_; } void BackEnd::initialiseDefaultConfig(const std::string &filename) { std::string file(filename); if (file.empty()) { if (!is_installed()) { std::string path = source_path(); if (path.empty()) throw std::runtime_error("BE: Could not determine the local source path"); file = path + "/libpisp/backend/backend_default_config.json"; } else file = std::string(PISP_BE_CONFIG_DIR) + "/" + "backend_default_config.json"; } std::ifstream ifs(file); if (!ifs.good()) throw std::runtime_error("BE: Could not find config json file: " + file); json root = json::parse(ifs); ifs.close(); memset(&be_config_, 0, sizeof(be_config_)); initialise_debin(be_config_.debin, root); be_config_extra_.dirty_flags_bayer |= PISP_BE_BAYER_ENABLE_DEBIN; initialise_demosaic(be_config_.demosaic, root); be_config_extra_.dirty_flags_bayer |= PISP_BE_BAYER_ENABLE_DEMOSAIC; initialise_false_colour(be_config_.false_colour, root); be_config_extra_.dirty_flags_bayer |= PISP_BE_RGB_ENABLE_FALSE_COLOUR; initialise_gamma(be_config_.gamma, root); be_config_extra_.dirty_flags_rgb |= PISP_BE_RGB_ENABLE_GAMMA; read_ycbcr(ycbcr_map_, inverse_ycbcr_map_, root); read_resample(resample_filter_map_, resample_select_list_, root); read_sharpen(default_sharpen_, default_shfc_, root); InitialiseSharpen(be_config_.sharpen, be_config_.sh_fc_combine); be_config_extra_.dirty_flags_rgb |= PISP_BE_RGB_ENABLE_SHARPEN; // Start with a sensible default YCbCr -- must be full-range on 2712C1 InitialiseYcbcr(be_config_.ycbcr, "jpeg"); InitialiseYcbcrInverse(be_config_.ycbcr, "jpeg"); be_config_extra_.dirty_flags_rgb |= PISP_BE_RGB_ENABLE_YCBCR + PISP_BE_RGB_ENABLE_YCBCR_INVERSE; for (unsigned int i = 0; i < variant_.BackEndNumBranches(0); i++) { // Start with a sensible default InitialiseResample(be_config_.resample[i], "lanczos3"); be_config_extra_.dirty_flags_rgb |= PISP_BE_RGB_ENABLE_RESAMPLE(i); } } } // namespace libpisp raspberrypi-libpisp-9ba67e6/src/libpisp/backend/backend_default_config.json000066400000000000000000000247441507172066400273170ustar00rootroot00000000000000{ "debin": { "coefs": [-7, 105, 35, -5] }, "demosaic": { "sharper": 8, "fc_mode": 1 }, "false_colour": { "distance": 2 }, "colour_encoding": { "jpeg": { "ycbcr": { "coeffs": [ 306, 601, 117, -173, -339, 512, 512, -429, -83 ], "offsets": [0, 33554432, 33554432] }, "ycbcr_inverse": { "coeffs": [ 1024, 0, 1436, 1024, -352, -731, 1024, 1815, 0 ], "offsets": [-47043259, 35509710, -59458469] } }, "smpte170m": { "ycbcr": { "coeffs": [ 263, 516, 100, -152, -298, 450, 450, -377, -73 ], "offsets": [4194304, 33554432, 33554432] }, "ycbcr_inverse": { "coeffs": [ 1192, 0, 1634, 1192, -401, -832, 1192, 2066, 0 ], "offsets": [-58437489, 35540222, -72570875] } }, "rec709": { "ycbcr": { "coeffs": [ 187, 629, 63, -103, -347, 450, 450, -409, -41 ], "offsets": [4194304, 33554432, 33554432] }, "ycbcr_inverse": { "coeffs": [ 1192, 0, 1836, 1192, -218, -546, 1192, 2163, 0 ], "offsets": [-65031074, 20151458, -75768630] } }, "rec709_full": { "ycbcr": { "coeffs": [ 218, 732, 74, -117, -395, 512, 512, -465, -47 ], "offsets": [0, 33554432, 33554432] }, "ycbcr_inverse": { "coeffs": [ 1024, 0, 1613, 1024, -192, -479, 1024, 1900, 0 ], "offsets": [-52835271, 21991737, -62267477] } }, "bt2020": { "ycbcr": { "coeffs": [ 231, 596, 52, -126, -324, 450, 450, -414, -36 ], "offsets": [4194304, 33554432, 33554432] }, "ycbcr_inverse": { "coeffs": [ 1192, 0, 1719, 1192, -192, -666, 1192, 2193, 0 ], "offsets": [-61210735, 23226461, -76749732] } }, "bt2020_full": { "ycbcr": { "coeffs": [ 269, 694, 61, -143, -369, 512, 512, -471, -41 ], "offsets": [0, 33554432, 33554432] }, "ycbcr_inverse": { "coeffs": [ 1024, 0, 1510, 1024, -168, -585, 1024, 1927, 0 ], "offsets": [-49479366, 24692917, -63129308] } } }, "gamma": { "lut": [ 0, 0, 1024, 5040, 2048, 9338, 3072, 12356, 4096, 15312, 5120, 18051, 6144, 20790, 7168, 23193, 8192, 25744, 9216, 27942, 10240, 30035, 11264, 32005, 12288, 33975, 13312, 35815, 14336, 37600, 15360, 39168, 16384, 40642, 18432, 43379, 20480, 45749, 22528, 47753, 24576, 49621, 26624, 51253, 28672, 52698, 30720, 53796, 32768, 54876, 36864, 57012, 40960, 58656, 45056, 59954, 49152, 61183, 53248, 62355, 57344, 63419, 61440, 64476, 65535, 65535 ] }, "resample": { "smart_selection": { "filter": [ "lanczos3", "lanczos2", "michel-netravali"], "downscale": [ 0.5, 2.0, 64.0] }, "filters": { "lanczos3": [ -15, 16, 979, 71, -31, 4, -2, -30, 965, 132, -48, 7, 9, -69, 939, 200, -65, 10, 18, -99, 901, 273, -83, 14, 24, -121, 854, 350, -101, 18, 28, -135, 796, 429, -116, 22, 30, -143, 731, 509, -129, 26, 30, -143, 660, 587, -139, 29, 29, -139, 587, 660, -143, 30, 26, -129, 509, 731, -143, 30, 22, -116, 429, 796, -135, 28, 18, -101, 350, 854, -121, 24, 14, -83, 273, 901, -99, 18, 10, -65, 200, 939, -69, 9, 7, -48, 132, 965, -30, -2, 4, -31, 71, 979, 16, -15 ], "lanczos2": [ -55, 231, 638, 266, -54, -2, -55, 197, 637, 301, -51, -5, -53, 165, 628, 337, -46, -7, -51, 134, 617, 373, -39, -10, -47, 105, 602, 408, -30, -14, -43, 79, 582, 442, -18, -18, -38, 54, 561, 474, -4, -23, -33, 32, 535, 505, 13, -28, -28, 13, 505, 535, 32, -33, -23, -4, 474, 561, 54, -38, -18, -18, 442, 582, 79, -43, -14, -30, 408, 602, 105, -47, -10, -39, 373, 617, 134, -51, -7, -46, 337, 628, 165, -53, -5, -51, 301, 637, 197, -55, -2, -54, 266, 638, 231, -55 ], "bicubic-spline": [ 51, 103, 678, 141, 51, 0, 49, 69, 673, 182, 50, 1, 47, 40, 661, 226, 48, 2, 44, 17, 642, 272, 44, 5, 40, 0, 617, 320, 39, 8, 35, -9, 588, 367, 31, 12, 31, -11, 552, 414, 22, 16, 26, -3, 510, 460, 10, 21, 21, 10, 460, 510, -3, 26, 16, 22, 414, 552, -11, 31, 12, 31, 367, 588, -9, 35, 8, 39, 320, 617, 0, 40, 5, 44, 272, 642, 17, 44, 2, 48, 226, 661, 40, 47, 1, 50, 182, 673, 69, 49, 0, 51, 141, 678, 103, 51 ], "michel-netravali": [ -24, 217, 604, 249, -22, 0, -25, 186, 600, 282, -19, 0, -25, 156, 594, 315, -15, -1, -24, 129, 584, 348, -10, -3, -23, 103, 571, 381, -3, -5, -21, 80, 554, 413, 6, -8, -18, 59, 533, 444, 16, -10, -16, 42, 510, 473, 28, -13, -13, 28, 473, 510, 42, -16, -10, 16, 444, 533, 59, -18, -8, 6, 413, 554, 80, -21, -5, -3, 381, 571, 103, -23, -3, -10, 348, 584, 129, -24, -1, -15, 315, 594, 156, -25, 0, -19, 282, 600, 186, -25, 0, -22, 249, 604, 217, -24 ] } }, "sharpen": { "filter0": { "kernel": [ -2, -2, -2, -2, -2, -1, -1, -1, -1, -1, 6, 6, 6, 6, 6, -1, -1, -1, -1, -1, -2, -2, -2, -2, -2 ], "offset": 100, "threshold_slope": 614, "scale": 512 }, "filter1": { "kernel": [ -2, -1, 6, -1, -2, -2, -1, 6, -1, -2, -2, -1, 6, -1, -2, -2, -1, 6, -1, -2, -2, -1, 6, -1, -2 ], "offset": 100, "threshold_slope": 614, "scale": 512 }, "filter2": { "kernel": [ 2, -1, -2, 0, 0, -1, 5, -1, -2, 0, -2, -1, 6, -1, -2, 0, -2, -1, 5, -1, 0, 0, -2, -1, 2 ], "offset": 100, "threshold_slope": 614, "scale": 921 }, "filter3": { "kernel": [ 0, 0, -2, -1, 2, 0, -2, -1, 5, -1, -2, -1, 6, -1, -2, -1, 5, -1, -2, 0, 2, -1, -2, 0, 0 ], "offset": 100, "threshold_slope": 614, "scale": 921 }, "filter4": { "kernel": [ -1, -2, -2, -2, -1, -2, 2, 2, 2, -2, -2, 2, 12, 2, -2, -2, 2, 2, 2, -2, -1, -2, -2, -2, -1 ], "offset": 200, "threshold_slope": 552, "scale": 512 }, "positive": { "strength": 76, "pre_limit": 2000, "function": [512, 1024, 1536, 2048, 3072, 4096, 5120, 6656, 8192], "limit": 4000 }, "negative": { "strength": 102, "pre_limit": 2000, "function": [512, 716, 1536, 2048, 3072, 4096, 5120, 6656, 8192], "limit": 4000 }, "enables": "0x1f", "white": 0, "black": 0, "grey": 50, "shfc_y_factor": 0.75 } } raspberrypi-libpisp-9ba67e6/src/libpisp/backend/backend_prepare.cpp000066400000000000000000001322771507172066400256160ustar00rootroot00000000000000 /* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (C) 2021 - 2023, Raspberry Pi Ltd * * backend_prepare.cpp - PiSP Back End configuration generation */ #include "backend.hpp" #include #include #include #include #include "common/logging.hpp" #include "common/utils.hpp" #include "tiling/types.hpp" using namespace libpisp; namespace { // Limit this to a sensible size constexpr unsigned int MaxStripeHeight = 3072; // Precision for the scaler blocks constexpr unsigned int ScalePrecision = 12; constexpr unsigned int PhasePrecision = 12; constexpr unsigned int UnityScale = 1 << ScalePrecision; constexpr unsigned int UnityPhase = 1 << PhasePrecision; // PPF properties constexpr unsigned int ResamplePrecision = 10; constexpr unsigned int NumPhases = 16; constexpr unsigned int NumTaps = 6; void check_stride(pisp_image_format_config const &config) { if (config.stride % PISP_BACK_END_OUTPUT_MIN_ALIGN || config.stride2 % PISP_BACK_END_OUTPUT_MIN_ALIGN) throw std::runtime_error("Output stride values not sufficiently aligned"); if (PISP_IMAGE_FORMAT_WALLPAPER(config.format) && (config.stride % 128 || config.stride2 % 128)) throw std::runtime_error("Wallpaper format should have 128-byte aligned rolls"); pisp_image_format_config check = config; compute_stride_align(check, PISP_BACK_END_OUTPUT_MIN_ALIGN); if (check.stride > config.stride || check.stride2 > config.stride2) { PISP_LOG(fatal, "Strides should be at least " << check.stride << " and " << check.stride2 << " but are " << config.stride << " and " << config.stride2); } } void finalise_bayer_rgb_inputs(pisp_image_format_config const &config) { if (config.width < PISP_BACK_END_MIN_TILE_WIDTH || config.height < PISP_BACK_END_MIN_TILE_HEIGHT) throw std::runtime_error("finalise_bayer_rgb_inputs: input image too small"); } void finalise_inputs(pisp_be_config &config) { // Not so much finalising, just checking that input dimensions and strides are OK. if (config.global.bayer_enables & PISP_BE_BAYER_ENABLE_INPUT) { if ((config.input_format.width & 1) || (config.input_format.height & 1)) throw std::runtime_error("finalise_inputs: Bayer pipe image dimensions must be even"); if (config.input_format.stride & 15) throw std::runtime_error("finalise_inputs: input stride should be at least 16-byte aligned"); } else if (config.global.rgb_enables & PISP_BE_RGB_ENABLE_INPUT) { if (PISP_IMAGE_FORMAT_SAMPLING_420(config.input_format.format) && (config.input_format.width & 1)) throw std::runtime_error("finalise_inputs: 420 input height must be even"); else if ((PISP_IMAGE_FORMAT_SAMPLING_420(config.input_format.format) || PISP_IMAGE_FORMAT_SAMPLING_422(config.input_format.format)) && (config.input_format.width & 1)) throw std::runtime_error("finalise_inputs: 420/422 input width must be even"); if (PISP_IMAGE_FORMAT_WALLPAPER(config.input_format.format)) { if ((config.input_format.stride & 127) || (config.input_format.stride2 & 127)) throw std::runtime_error("finalise_inputs: wallpaper format strides must be at least 128-byte aligned"); } else if ((config.input_format.stride & 15) || (config.input_format.stride2 & 15)) throw std::runtime_error("finalise_inputs: input strides must be at least 16-byte aligned"); } } void finalise_lsc(pisp_be_lsc_config &lsc, [[maybe_unused]] pisp_be_lsc_extra &lsc_extra, uint16_t width, uint16_t height) { // Just a warning that ACLS algorithms might want the grid calculations here to match the AWB/ACLS stats. static const int P = PISP_BE_LSC_STEP_PRECISION; if (lsc.grid_step_x == 0) lsc.grid_step_x = (PISP_BE_LSC_GRID_SIZE << P) / width; if (lsc.grid_step_y == 0) lsc.grid_step_y = (PISP_BE_LSC_GRID_SIZE << P) / height; PISP_ASSERT(lsc.grid_step_x * (width + lsc_extra.offset_x - 1) < (PISP_BE_LSC_GRID_SIZE << P)); PISP_ASSERT(lsc.grid_step_y * (height + lsc_extra.offset_y - 1) < (PISP_BE_LSC_GRID_SIZE << P)); } void finalise_cac(pisp_be_cac_config &cac, [[maybe_unused]] pisp_be_cac_extra &cac_extra, uint16_t width, uint16_t height) { static const int P = PISP_BE_CAC_STEP_PRECISION; if (cac.grid_step_x == 0) cac.grid_step_x = (PISP_BE_CAC_GRID_SIZE << P) / width; if (cac.grid_step_y == 0) cac.grid_step_y = (PISP_BE_CAC_GRID_SIZE << P) / height; PISP_ASSERT(cac.grid_step_x * (width + cac_extra.offset_x - 1) < (PISP_BE_CAC_GRID_SIZE << P)); PISP_ASSERT(cac.grid_step_y * (height + cac_extra.offset_y - 1) < (PISP_BE_CAC_GRID_SIZE << P)); } void finalise_resample(pisp_be_resample_config &resample, pisp_be_resample_extra &resample_extra, uint16_t width, uint16_t height) { uint32_t scale_factor_h = ((width - 1) << ScalePrecision) / (resample_extra.scaled_width - 1); uint32_t scale_factor_v = ((height - 1) << ScalePrecision) / (resample_extra.scaled_height - 1); if ((scale_factor_h < UnityScale / 16 || scale_factor_h >= 16 * UnityScale) || (scale_factor_v < UnityScale / 16 || scale_factor_v >= 16 * UnityScale)) throw std::runtime_error("finalise_resample: Invalid scaling factors (must be < 16x down/upscale)."); resample.scale_factor_h = scale_factor_h; resample.scale_factor_v = scale_factor_v; // If the filter coefficients are unset we should probably copy in our "default ones". } void finalise_downscale(pisp_be_downscale_config &downscale, pisp_be_downscale_extra &downscale_extra, uint16_t width, uint16_t height) { PISP_LOG(debug, "width " << width << " scaled_width " << downscale_extra.scaled_width); PISP_LOG(debug, "height " << height << " scaled_height " << downscale_extra.scaled_height); uint32_t scale_factor_h = (width << ScalePrecision) / (downscale_extra.scaled_width); uint32_t scale_factor_v = (height << ScalePrecision) / (downscale_extra.scaled_height); if ((scale_factor_h != UnityScale && (scale_factor_h < 2 * UnityScale || scale_factor_h > 8 * UnityScale)) || (scale_factor_v != UnityScale && (scale_factor_v < 2 * UnityScale || scale_factor_v > 8 * UnityScale))) throw std::runtime_error("finalise_downscale: Invalid scaling factors (must be 1x or >= 2x && <= 8x)."); downscale.scale_factor_h = scale_factor_h; downscale.scale_factor_v = scale_factor_v; downscale.scale_recip_h = (downscale_extra.scaled_width << ScalePrecision) / (width); downscale.scale_recip_v = (downscale_extra.scaled_height << ScalePrecision) / (height); PISP_LOG(debug, "scale_factor_h " << downscale.scale_factor_h << " scale_factor_v " << downscale.scale_factor_v); PISP_LOG(debug, "scale_recip_h " << downscale.scale_recip_h << " scale_recip_v " << downscale.scale_recip_v); } void finalise_decompression(pisp_be_config const &be_config) { uint32_t fmt = be_config.input_format.format, bayer_enables = be_config.global.bayer_enables; if (PISP_IMAGE_FORMAT_COMPRESSED(fmt) && !(bayer_enables & PISP_BE_BAYER_ENABLE_DECOMPRESS)) throw std::runtime_error("BackEnd::finalise: input compressed but decompression not enabled"); if (!PISP_IMAGE_FORMAT_COMPRESSED(fmt) && (bayer_enables & PISP_BE_BAYER_ENABLE_DECOMPRESS)) throw std::runtime_error("BackEnd::finalise: input uncompressed but decompression enabled"); if ((bayer_enables & PISP_BE_BAYER_ENABLE_DECOMPRESS) && !PISP_IMAGE_FORMAT_BPS_8(fmt)) throw std::runtime_error("BackEnd::finalise: compressed input is not 8bpp"); } // TDN and Stitch I/O dimensions must match the input, though the format may differ. static void check_rawio_format(pisp_image_format_config &fmt, uint16_t w, uint16_t h) { if (fmt.width == 0 || fmt.height == 0) { fmt.width = w; fmt.height = h; } else if (fmt.width != w || fmt.height != h) throw std::runtime_error("BackEnd::finalise: Image dimensions do not match input"); if (fmt.stride == 0) compute_stride(fmt); else check_stride(fmt); } void finalise_tdn(pisp_be_config &config) { int tdn_enabled = config.global.bayer_enables & PISP_BE_BAYER_ENABLE_TDN; int tdn_input_enabled = config.global.bayer_enables & PISP_BE_BAYER_ENABLE_TDN_INPUT; int tdn_decompress_enabled = config.global.bayer_enables & PISP_BE_BAYER_ENABLE_TDN_DECOMPRESS; int tdn_compress_enabled = config.global.bayer_enables & PISP_BE_BAYER_ENABLE_TDN_COMPRESS; int tdn_output_enabled = config.global.bayer_enables & PISP_BE_BAYER_ENABLE_TDN_OUTPUT; uint32_t fmt = config.tdn_output_format.format; if (tdn_enabled && !tdn_output_enabled) throw std::runtime_error("BackEnd::finalise: TDN output not enabled when TDN enabled"); if (PISP_IMAGE_FORMAT_COMPRESSED(fmt) && !tdn_compress_enabled) throw std::runtime_error("BackEnd::finalise: TDN output compressed but compression not enabled"); if (!PISP_IMAGE_FORMAT_COMPRESSED(fmt) && tdn_compress_enabled) throw std::runtime_error("BackEnd::finalise: TDN output uncompressed but compression enabled"); if (tdn_compress_enabled && !PISP_IMAGE_FORMAT_BPS_8(fmt)) throw std::runtime_error("BackEnd::finalise: TDN output does not match compression mode"); if (tdn_output_enabled) check_rawio_format(config.tdn_output_format, config.input_format.width, config.input_format.height); if (tdn_input_enabled) check_rawio_format(config.tdn_input_format, config.input_format.width, config.input_format.height); if (!tdn_enabled) { if (tdn_input_enabled) throw std::runtime_error("BackEnd::finalise: TDN input enabled but TDN not enabled"); // I suppose there is a weird (and entirely pointless) case where TDN is not enabled but TDN output is, which we allow. } else if (config.tdn.reset) { if (tdn_input_enabled) throw std::runtime_error("BackEnd::finalise: TDN input enabled but TDN being reset"); } else { if (!tdn_input_enabled) throw std::runtime_error("BackEnd::finalise: TDN input not enabled but TDN not being reset"); // Make the TDN input match the output if it's unset. Usually this will be the sensible thing to do. if (config.tdn_input_format.width == 0 && config.tdn_input_format.height == 0) config.tdn_input_format = config.tdn_output_format; if (PISP_IMAGE_FORMAT_COMPRESSED(fmt) && !tdn_decompress_enabled) throw std::runtime_error("BackEnd::finalise: TDN input compressed but decompression not enabled"); if (!PISP_IMAGE_FORMAT_COMPRESSED(fmt) && tdn_decompress_enabled) throw std::runtime_error("BackEnd::finalise: TDN input uncompressed but decompression enabled"); if (tdn_compress_enabled && !PISP_IMAGE_FORMAT_BPS_8(fmt)) throw std::runtime_error("BackEnd::finalise: TDN output does not match compression mode"); } } void finalise_stitch(pisp_be_config &config) { bool stitch_enabled = config.global.bayer_enables & PISP_BE_BAYER_ENABLE_STITCH; bool stitch_input_enabled = config.global.bayer_enables & PISP_BE_BAYER_ENABLE_STITCH_INPUT; bool stitch_decompress_enabled = config.global.bayer_enables & PISP_BE_BAYER_ENABLE_STITCH_DECOMPRESS; bool stitch_compress_enabled = config.global.bayer_enables & PISP_BE_BAYER_ENABLE_STITCH_COMPRESS; bool stitch_output_enabled = config.global.bayer_enables & PISP_BE_BAYER_ENABLE_STITCH_OUTPUT; uint32_t input_fmt = config.stitch_input_format.format; uint32_t output_fmt = config.stitch_output_format.format; if (stitch_enabled != stitch_input_enabled) throw std::runtime_error("BackEnd::finalise: stitch and stitch_input should be enabled/disabled together"); if (stitch_input_enabled && PISP_IMAGE_FORMAT_COMPRESSED(input_fmt) && !stitch_decompress_enabled) throw std::runtime_error("BackEnd::finalise: stitch output compressed but decompression not enabled"); if (stitch_input_enabled && !PISP_IMAGE_FORMAT_COMPRESSED(input_fmt) && stitch_decompress_enabled) throw std::runtime_error("BackEnd::finalise: stitch output uncompressed but decompression enabled"); if (stitch_output_enabled && PISP_IMAGE_FORMAT_COMPRESSED(output_fmt) && !stitch_compress_enabled) throw std::runtime_error("BackEnd::finalise: stitch output compressed but compression not enabled"); if (stitch_output_enabled && !PISP_IMAGE_FORMAT_COMPRESSED(output_fmt) && stitch_compress_enabled) throw std::runtime_error("BackEnd::finalise: stitch output uncompressed but compression enabled"); if (stitch_decompress_enabled && !PISP_IMAGE_FORMAT_BPS_8(input_fmt)) throw std::runtime_error("BackEnd::finalise: stitch input does not match compression mode"); if (stitch_compress_enabled && !PISP_IMAGE_FORMAT_BPS_8(output_fmt)) throw std::runtime_error("BackEnd::finalise: stitch output does not match compression mode"); if (stitch_output_enabled) check_rawio_format(config.stitch_output_format, config.input_format.width, config.input_format.height); if (stitch_input_enabled) check_rawio_format(config.stitch_input_format, config.input_format.width, config.input_format.height); // Compute the motion_threshold reciprocal if it hasn't been done. if (config.stitch.motion_threshold_recip == 0) { if (config.stitch.motion_threshold_256 == 0) config.stitch.motion_threshold_recip = 255; else // We round the result up where possible as the block may work (ever so slightly) better like this. config.stitch.motion_threshold_recip = std::min(255, (256 + (int)config.stitch.motion_threshold_256 - 1) / config.stitch.motion_threshold_256); } } void finalise_output(pisp_be_output_format_config &config) { // If the high clipping bound is zero assume it wasn't set and the intention is that no clipping occurs. if (config.hi == 0) config.hi = 65535; if (config.hi2 == 0) config.hi2 = 65535; // Do some checking on output image dimensions and strides. if (config.image.width < PISP_BACK_END_MIN_TILE_WIDTH || config.image.height < PISP_BACK_END_MIN_TILE_HEIGHT) throw std::runtime_error("finalise_output: output image too small"); if (PISP_IMAGE_FORMAT_SAMPLING_420(config.image.format) && (config.image.height & 1)) throw std::runtime_error("finalise_output: 420 image height should be even"); if ((PISP_IMAGE_FORMAT_SAMPLING_420(config.image.format) || PISP_IMAGE_FORMAT_SAMPLING_422(config.image.format)) && !PISP_IMAGE_FORMAT_INTERLEAVED(config.image.format) && (config.image.width & 1)) throw std::runtime_error("finalise_output: 420/422 image width should be even"); if (PISP_IMAGE_FORMAT_WALLPAPER(config.image.format)) { if ((config.image.stride & 127) || (config.image.stride2 & 127)) throw std::runtime_error("finalise_output: wallpaper image stride should be at least 128-byte aligned"); } else if ((config.image.stride & 15) || (config.image.stride2 & 15)) throw std::runtime_error("finalise_output: image stride should be at least 16-byte aligned"); } void check_tiles(TileArray const &tiles, uint32_t rgb_enables, unsigned int numBranches, unsigned int num_tiles, TilingConfig const &tiling_config) { for (unsigned int tile_num = 0; tile_num < num_tiles; tile_num++) { const pisp_tile &tile = tiles[tile_num]; PISP_ASSERT(tile.input_width && tile.input_height); // zero inputs shouldn't be possible if (tile.input_width < PISP_BACK_END_MIN_TILE_WIDTH || tile.input_height < PISP_BACK_END_MIN_TILE_HEIGHT) throw std::runtime_error("Tile too small at input"); for (unsigned int i = 0; i < numBranches; i++) { if ((rgb_enables & PISP_BE_RGB_ENABLE_OUTPUT(i)) == 0) continue; unsigned int width_after_crop = tile.input_width - tile.crop_x_start[i] - tile.crop_x_end[i]; unsigned int height_after_crop = tile.input_height - tile.crop_y_start[i] - tile.crop_y_end[i]; // A tile that gets cropped away completely can't produce output, and vice versa. PISP_ASSERT((width_after_crop * height_after_crop == 0) == (tile.output_width[i] * tile.output_height[i] == 0)); // A zero-sized tile is legitimate meaning "no output", but otherwise minimum tile sizes must be respected. if (width_after_crop && height_after_crop) { bool rh_edge = tile.output_offset_x[i] + tile.output_width[i] == tiling_config.output_image_size[i].dx; if (width_after_crop < PISP_BACK_END_MIN_TILE_WIDTH) { PISP_LOG(warning, "Tile narrow after crop: tile " << tile_num << " output " << i << " input_width " << tile.input_width << " after_crop " << width_after_crop << " crop start " << tile.crop_x_start[i] << " end " << tile.crop_x_end[i]); if (!rh_edge) throw std::runtime_error("Tile width too small after crop"); } if (height_after_crop < PISP_BACK_END_MIN_TILE_HEIGHT) throw std::runtime_error("Tile height too small after crop"); if (tile.resample_in_width[i] < PISP_BACK_END_MIN_TILE_WIDTH) { PISP_LOG(warning, "Tile narrow after downscale: tile " << tile_num << " output " << i << " input_width " << tile.input_width << " after_crop " << width_after_crop << " after downscale " << tile.resample_in_width[i]); if (!rh_edge) throw std::runtime_error("Tile width too small after downscale"); } if (tile.resample_in_height[i] < PISP_BACK_END_MIN_TILE_HEIGHT) throw std::runtime_error("Tile height too small after downscale"); if (!rh_edge && tile.output_width[i] < PISP_BACK_END_MIN_TILE_WIDTH) throw std::runtime_error("Tile width too small at output"); if (tile.output_height[i] < PISP_BACK_END_MIN_TILE_HEIGHT) throw std::runtime_error("Tile height too small at output"); } } } } unsigned int get_pixel_alignment(uint32_t format, int byte_alignment) { int alignment_pixels = byte_alignment; // for 8bpp formats if (PISP_IMAGE_FORMAT_BPS_16(format)) alignment_pixels = byte_alignment / 2; else if (PISP_IMAGE_FORMAT_BPS_10(format)) alignment_pixels = byte_alignment * 3 / 4; else if (PISP_IMAGE_FORMAT_BPP_32(format)) alignment_pixels = byte_alignment / 4; if (PISP_IMAGE_FORMAT_PLANAR(format) && !PISP_IMAGE_FORMAT_SAMPLING_444(format)) alignment_pixels *= 2; // the UV planes in fully planar 420/422 output will have half the width else if (PISP_IMAGE_FORMAT_INTERLEAVED(format) && (PISP_IMAGE_FORMAT_SAMPLING_422(format) || PISP_IMAGE_FORMAT_SAMPLING_420(format))) alignment_pixels /= 2; // YUYV type outputs need only 8 pixels to make 16 bytes return alignment_pixels; } unsigned int lcm(int a, int b) { int orig_a = a, orig_b = b, tmp; while (b) tmp = a % b, a = b, b = tmp; return orig_a / a * orig_b; } static tiling::Length2 calculate_input_alignment(pisp_be_config const &config) { if (config.global.rgb_enables & PISP_BE_RGB_ENABLE_INPUT) { PISP_LOG(debug, "RGB input enabled"); // Need 4 byte alignment AND even number of pixels. Height must be 2 row aligned only for 420 input. return tiling::Length2(lcm(get_pixel_alignment(config.input_format.format, PISP_BACK_END_INPUT_ALIGN), 2), PISP_IMAGE_FORMAT_SAMPLING_420(config.input_format.format) ? 2 : 1); } uint32_t bayer_enables = config.global.bayer_enables; // For starters, we need 4 *byte* alignment (this automatically cover 2 *pixel* alignment for all the raw formats). int pixel_alignment = get_pixel_alignment(config.input_format.format, PISP_BACK_END_INPUT_ALIGN); // If any input is compressed, we need 8 *pixel* alignment. if (PISP_IMAGE_FORMAT_COMPRESSED(config.input_format.format) || ((bayer_enables & PISP_BE_BAYER_ENABLE_TDN_INPUT) && PISP_IMAGE_FORMAT_COMPRESSED(config.tdn_input_format.format)) || ((bayer_enables & PISP_BE_BAYER_ENABLE_STITCH_INPUT) && PISP_IMAGE_FORMAT_COMPRESSED(config.stitch_input_format.format))) pixel_alignment = lcm(pixel_alignment, PISP_BACK_END_COMPRESSED_ALIGN); // If any of the Bayer outputs are enabled, those need 16 *byte* alignment. (This already covers the outputs being compressed.) if (bayer_enables & PISP_BE_BAYER_ENABLE_TDN_OUTPUT) pixel_alignment = lcm(pixel_alignment, get_pixel_alignment(config.tdn_output_format.format, PISP_BACK_END_OUTPUT_MIN_ALIGN)); if (bayer_enables & PISP_BE_BAYER_ENABLE_STITCH_OUTPUT) pixel_alignment = lcm(pixel_alignment, get_pixel_alignment(config.stitch_output_format.format, PISP_BACK_END_OUTPUT_MIN_ALIGN)); return tiling::Length2(pixel_alignment, 2); // Bayer input rows always in pairs } static tiling::Length2 calculate_output_alignment(uint32_t format, int align = PISP_BACK_END_OUTPUT_MAX_ALIGN) { int y_alignment = PISP_IMAGE_FORMAT_SAMPLING_420(format) ? 2 : 1; return tiling::Length2(get_pixel_alignment(format, align), y_alignment); } void calculate_input_addr_offset(int x, int y, pisp_image_format_config const &input_format, uint32_t *addr_offset, uint32_t *addr_offset2 = nullptr) { uint32_t offset2 = 0; compute_addr_offset(input_format, x, y, addr_offset, &offset2); if (addr_offset2) *addr_offset2 = offset2; } } // namespace void BackEnd::finaliseConfig() { uint32_t dirty_flags_bayer = be_config_extra_.dirty_flags_bayer & be_config_.global.bayer_enables; // only finalise blocks that are dirty *and* enabled uint32_t dirty_flags_rgb = be_config_extra_.dirty_flags_rgb & be_config_.global.rgb_enables; // only finalise blocks that are dirty *and* enabled if ((dirty_flags_bayer & PISP_BE_BAYER_ENABLE_INPUT) || (dirty_flags_rgb & PISP_BE_RGB_ENABLE_INPUT)) finalise_bayer_rgb_inputs(be_config_.input_format); if (dirty_flags_bayer & PISP_BE_BAYER_ENABLE_INPUT) finalise_inputs(be_config_); if (dirty_flags_bayer & (PISP_BE_BAYER_ENABLE_INPUT | PISP_BE_BAYER_ENABLE_DECOMPRESS)) finalise_decompression(be_config_); if ((be_config_extra_.dirty_flags_bayer & (PISP_BE_BAYER_ENABLE_TDN | PISP_BE_BAYER_ENABLE_TDN_INPUT | PISP_BE_BAYER_ENABLE_TDN_DECOMPRESS | PISP_BE_BAYER_ENABLE_TDN_COMPRESS | PISP_BE_BAYER_ENABLE_TDN_OUTPUT))) { finalise_tdn(be_config_); } if (be_config_extra_.dirty_flags_bayer & (PISP_BE_BAYER_ENABLE_STITCH | PISP_BE_BAYER_ENABLE_STITCH_INPUT | PISP_BE_BAYER_ENABLE_STITCH_DECOMPRESS | PISP_BE_BAYER_ENABLE_STITCH_COMPRESS | PISP_BE_BAYER_ENABLE_STITCH_OUTPUT)) { finalise_stitch(be_config_); } if (dirty_flags_bayer & PISP_BE_BAYER_ENABLE_LSC) finalise_lsc(be_config_.lsc, be_config_extra_.lsc, be_config_.input_format.width, be_config_.input_format.height); if (dirty_flags_bayer & PISP_BE_BAYER_ENABLE_CAC) finalise_cac(be_config_.cac, be_config_extra_.cac, be_config_.input_format.width, be_config_.input_format.height); for (unsigned int j = 0; j < variant_.BackEndNumBranches(0); j++) { bool enabled = be_config_.global.rgb_enables & PISP_BE_RGB_ENABLE_OUTPUT(j); if (enabled) { // crop is enabled when it contains non-zero width/height uint16_t w = be_config_extra_.crop[j].width ? be_config_extra_.crop[j].width : be_config_.input_format.width; uint16_t h = be_config_extra_.crop[j].width ? be_config_extra_.crop[j].height : be_config_.input_format.height; if (dirty_flags_rgb & PISP_BE_RGB_ENABLE_DOWNSCALE(j)) { if (variant_.BackEndDownscalerAvailable(0, j)) finalise_downscale(be_config_.downscale[j], be_config_extra_.downscale[j], w, h); else throw std::runtime_error("Downscale is not available in output branch " + std::to_string(j)); } if (be_config_.global.rgb_enables & PISP_BE_RGB_ENABLE_DOWNSCALE(j)) { // If the downscale is enabled, we update the input width/height for the resample stage. w = be_config_extra_.downscale[j].scaled_width; h = be_config_extra_.downscale[j].scaled_height; } if (dirty_flags_rgb & PISP_BE_RGB_ENABLE_RESAMPLE(j)) finalise_resample(be_config_.resample[j], be_config_extra_.resample[j], w, h); if (dirty_flags_rgb & PISP_BE_RGB_ENABLE_OUTPUT(j)) finalise_output(be_config_.output_format[j]); } } // Finally check for a sane collection of enable bits. if (!((be_config_.global.bayer_enables & PISP_BE_BAYER_ENABLE_INPUT) || (be_config_.global.bayer_enables == 0))) throw std::runtime_error("BackEnd::finalise: Bayer input disabled but Bayer pipe active"); if (!!(be_config_.global.bayer_enables & PISP_BE_BAYER_ENABLE_INPUT) + !!(be_config_.global.rgb_enables & PISP_BE_RGB_ENABLE_INPUT) != 1) throw std::runtime_error("BackEnd::finalise: exactly one of Bayer and RGB inputs should be enabled"); uint32_t output_enables = be_config_.global.bayer_enables & (PISP_BE_BAYER_ENABLE_TDN_OUTPUT | PISP_BE_BAYER_ENABLE_STITCH_OUTPUT); for (unsigned int i = 0; i < variant_.BackEndNumBranches(0); i++) output_enables |= be_config_.global.rgb_enables & PISP_BE_RGB_ENABLE_OUTPUT(i); if (output_enables == 0) throw std::runtime_error("BackEnd::finalise: PiSP not configured to do anything"); } void BackEnd::updateSmartResize() { std::string filter; // Look through the output branches adjusting the scaling blocks where "smart resizing" // has been requested. for (unsigned int i = 0; i < variant_.BackEndNumBranches(0); i++) { // First get the size of the input to the rescalers. The crops are zero when not in use. uint16_t input_width = be_config_extra_.crop[i].width; if (!input_width) input_width = be_config_.input_format.width; uint16_t input_height = be_config_extra_.crop[i].height; if (!input_height) input_height = be_config_.input_format.height; if ((smart_resize_dirty_ & (1 << i)) || (be_config_extra_.dirty_flags_extra & PISP_BE_DIRTY_CROP)) { if (smart_resize_[i].width && smart_resize_[i].height) { uint16_t resampler_input_width = input_width; uint16_t resampler_input_height = input_height; uint16_t resampler_output_width = smart_resize_[i].width; uint16_t resampler_output_height = smart_resize_[i].height; PISP_LOG(debug, "Smart resize branch " << i << " input size " << input_width << " x " << input_height << " output size " << smart_resize_[i].width << " x " << smart_resize_[i].height); // We're doing to use the downscaler if it's available and we're downscaling // by more than 2x. // \todo - increase this "2x" threshold by using different resampler kernels. if (variant_.BackEndDownscalerAvailable(0, i) && (resampler_output_width * 2 < input_width || resampler_output_height * 2 < input_height)) { uint16_t downscaler_output_width = input_width; uint16_t downscaler_output_height = input_height; // We treat the width and height the same. Look at the width first. if (resampler_output_width * 2 < input_width) { // Try to put 2x downscale into the resampler, everything else into // the downscaler. But remember that it must do *at least* 2x, and no // more than 8x (being careful to round that limit up).. downscaler_output_width = std::clamp(resampler_output_width * 2, (input_width + 7) / 8, input_width / 2); } // Now the same for the height. if (resampler_output_height * 2 < input_height) { // Try to put 2x downscale into the resampler, everything else into // the downscaler. But remember that it must do *at least* 2x and no // more than 8x (being careful to round that limit up).. downscaler_output_height = std::clamp(resampler_output_height * 2, (input_height + 7) / 8, input_height / 2); } PISP_LOG(debug, "Using downscaler, output size " << downscaler_output_width << " x " << downscaler_output_height); // Now program up the downscaler. pisp_be_downscale_extra downscale = {}; downscale.scaled_width = downscaler_output_width; downscale.scaled_height = downscaler_output_height; SetDownscale(i, downscale); be_config_.global.rgb_enables |= PISP_BE_RGB_ENABLE_DOWNSCALE(i); // Now adjust the input dimensions that the code below (which // programs the resampler) will see, so that it can, if necessary, // do its PPF coefficient adjustment. resampler_input_width = downscaler_output_width; resampler_input_height = downscaler_output_height; } else { be_config_.global.rgb_enables &= ~PISP_BE_RGB_ENABLE_DOWNSCALE(i); } // Don't resample by unity: it needlessly reduces bit-depth, and can over-sharpen if (resampler_input_width == resampler_output_width && resampler_input_height == resampler_output_height) { be_config_.global.rgb_enables &= ~PISP_BE_RGB_ENABLE_RESAMPLE(i); continue; } // Finally program up the resampler block. // If the following conditions are met: // // - The x and y scale factors are the same, // - We are downscaling in both directions by a factor of > 2, // - The downscale factor is *smaller* than the number of filter taps - 1, // // then we can use the PPF as a trapezoidal downscaler by setting up // the filter coefficients (per used phase) correctly. This improves // on image quality for larger downscale factors. double scale_factor_x = (double)(resampler_input_width - 1) / (resampler_output_width - 1); double scale_factor_y = (double)(resampler_input_height - 1) / (resampler_output_height - 1); pisp_be_resample_config resample = {}; pisp_be_resample_extra resample_extra = {}; if (scale_factor_x > 2.1 && scale_factor_x < scale_factor_y * 1.1 && scale_factor_y < scale_factor_x * 1.1) { PISP_LOG(debug, "Setting the PPF as a trapezoidal filter"); scale_factor_x = std::min(scale_factor_x, NumTaps - 1); for (unsigned int p = 0; p < NumPhases; p++) { // Initial phase for the current pixel (offset 2 in the filter) // is calculated as 1 - p / NumPhases resample.coef[p * NumTaps + 0] = (1 << ResamplePrecision) - (p << ResamplePrecision) / NumPhases; resample.coef[p * NumTaps + 0] /= scale_factor_x; double scale = scale_factor_x - (1.0 - (double)p / NumPhases); for (unsigned int t = 1; t < 1 + std::ceil(scale_factor_x); t++) { double s = std::min(1.0, scale); resample.coef[p * NumTaps + t] = s * (1 << ResamplePrecision) / scale_factor_x; scale -= s; } } // resample_extra will be filled in below. SetResample(i, resample, resample_extra); } else { // Let's choose a resampling filter based on the scaling factor. // The selection mapping is defined in the config json file. InitialiseResample(resample, scale_factor_x); } // Last thing is to set the output dimensions. resample_extra.scaled_width = resampler_output_width; resample_extra.scaled_height = resampler_output_height; SetResample(i, resample_extra); be_config_.global.rgb_enables |= PISP_BE_RGB_ENABLE_RESAMPLE(i); } } } smart_resize_dirty_ = 0; } void BackEnd::updateTiles() { if (retile_) { TilingConfig tiling_config; pisp_be_config const &c = be_config_; BeConfigExtra const &ce = be_config_extra_; retile_ = false; tiling_config.input_alignment = calculate_input_alignment(c); PISP_LOG(debug, "Input alignments are " << tiling_config.input_alignment << " pixels"); tiling_config.input_image_size = tiling::Length2(c.input_format.width, c.input_format.height); for (unsigned int i = 0; i < variant_.BackEndNumBranches(0); i++) { tiling_config.crop[i] = tiling::Interval2(tiling::Interval(ce.crop[i].offset_x, ce.crop[i].width), tiling::Interval(ce.crop[i].offset_y, ce.crop[i].height)); if (tiling_config.crop[i].x.length == 0 || tiling_config.crop[i].y.length == 0) tiling_config.crop[i] = tiling::Interval2(tiling::Interval(0, c.input_format.width), tiling::Interval(0, c.input_format.height)); tiling_config.output_h_mirror[i] = be_config_.output_format[i].transform & PISP_BE_TRANSFORM_HFLIP; tiling_config.downscale_factor[i] = tiling::Length2(c.downscale[i].scale_factor_h, c.downscale[i].scale_factor_v); tiling_config.resample_factor[i] = tiling::Length2(c.resample[i].scale_factor_h, c.resample[i].scale_factor_v); tiling_config.downscale_image_size[i] = tiling::Length2(ce.downscale[i].scaled_width, ce.downscale[i].scaled_height); tiling_config.output_image_size[i] = tiling::Length2(c.output_format[i].image.width, c.output_format[i].image.height); tiling_config.output_max_alignment[i] = calculate_output_alignment(c.output_format[i].image.format, PISP_BACK_END_OUTPUT_MAX_ALIGN); tiling_config.output_min_alignment[i] = calculate_output_alignment(c.output_format[i].image.format, PISP_BACK_END_OUTPUT_MIN_ALIGN); } tiling_config.max_tile_size.dx = config_.max_tile_width ? config_.max_tile_width : variant_.BackEndMaxTileWidth(0); tiling_config.max_tile_size.dy = config_.max_stripe_height ? config_.max_stripe_height : MaxStripeHeight; tiling_config.min_tile_size = tiling::Length2(PISP_BACK_END_MIN_TILE_WIDTH, PISP_BACK_END_MIN_TILE_HEIGHT); tiling_config.resample_enables = be_config_.global.rgb_enables / (int)PISP_BE_RGB_ENABLE_RESAMPLE0; tiling_config.downscale_enables = be_config_.global.rgb_enables / (int)PISP_BE_RGB_ENABLE_DOWNSCALE0; // Set compressed_input to false as otherwise the tiling would pad tiles up to multiples of 8 pixels even when these lie // outside the actual image width (and we've chosen not to handle compression like that). tiling_config.compressed_input = false; tiles_ = retilePipeline(tiling_config); check_tiles(tiles_, c.global.rgb_enables, variant_.BackEndNumBranches(0), num_tiles_x_ * num_tiles_y_, tiling_config); finalise_tiling_ = true; } if (finalise_tiling_) { finaliseTiling(); finalise_tiling_ = false; } } TileArray BackEnd::retilePipeline(TilingConfig const &tiling_config) { // The tiling library provides tiles in a SW Tile structure. Tile tiles[PISP_BACK_END_NUM_TILES]; tiling::Length2 grid; tile_pipeline(tiling_config, tiles, PISP_BACK_END_NUM_TILES, &grid); num_tiles_x_ = grid.dx; num_tiles_y_ = grid.dy; TileArray tile_array; // Finally convert the Tiles into pisp_tiles. for (int i = 0; i < num_tiles_x_ * num_tiles_y_; i++) { pisp_tile &t = tile_array[i]; memset(&t, 0, sizeof(pisp_tile)); t.edge = 0; if (i < num_tiles_x_) t.edge |= PISP_TOP_EDGE; if (i >= num_tiles_x_ * (num_tiles_y_ - 1)) t.edge |= PISP_BOTTOM_EDGE; if (i % num_tiles_x_ == 0) t.edge |= PISP_LEFT_EDGE; if ((i + 1) % num_tiles_x_ == 0) t.edge |= PISP_RIGHT_EDGE; t.input_offset_x = tiles[i].input.input.x.offset; t.input_offset_y = tiles[i].input.input.y.offset; t.input_width = tiles[i].input.input.x.length; t.input_height = tiles[i].input.input.y.length; if (tiles[i].input.output != tiles[i].input.input) throw std::runtime_error("BackEnd::retilePipeline: tiling error in Bayer pipe"); for (unsigned int j = 0; j < variant_.BackEndNumBranches(0); j++) { bool enabled = (be_config_.global.rgb_enables & PISP_BE_RGB_ENABLE_OUTPUT(j)); if (enabled && (tiles[i].output[j].output.x.length == 0 || tiles[i].output[j].output.y.length == 0)) { // If a tile produces no output there's no point sending anything down this branch, so ensure the crop // "eats" everything and set everything else to zero. t.crop_x_start[j] = t.input_width; t.crop_x_end[j] = 0; t.crop_y_start[j] = t.input_height; t.crop_y_end[j] = 0; t.resample_in_width[j] = 0; t.resample_in_height[j] = 0; t.output_offset_x[j] = 0; t.output_offset_y[j] = 0; t.output_width[j] = 0; t.output_height[j] = 0; continue; } tiling::Crop2 downscale_crop; tiling::Interval2 resample_size = tiles[i].crop[j].output; resample_size.x = resample_size.x - tiles[i].resample[j].crop.x; resample_size.y = resample_size.y - tiles[i].resample[j].crop.y; // When a resize stage is disabled, the tile size after the stage is found from the input of the // next block. Also there will be no extra crop necessary for the resize operation. if (be_config_.global.rgb_enables & PISP_BE_RGB_ENABLE_DOWNSCALE(j)) { downscale_crop = tiles[i].downscale[j].crop + tiles[i].crop[j].crop; // Size of the tile going into the resample block needs to be set here. resample_size = tiles[i].downscale[j].output; } else if (be_config_.global.rgb_enables & PISP_BE_RGB_ENABLE_RESAMPLE(j)) { downscale_crop = tiles[i].resample[j].crop + tiles[i].crop[j].crop; } else { downscale_crop = tiles[i].output[j].crop + tiles[i].crop[j].crop; } t.crop_x_start[j] = downscale_crop.x.start; t.crop_x_end[j] = downscale_crop.x.end; t.crop_y_start[j] = downscale_crop.y.start; t.crop_y_end[j] = downscale_crop.y.end; t.resample_in_width[j] = resample_size.x.length; t.resample_in_height[j] = resample_size.y.length; t.output_offset_x[j] = tiles[i].output[j].output.x.offset; t.output_offset_y[j] = tiles[i].output[j].output.y.offset; t.output_width[j] = tiles[i].output[j].output.x.length; t.output_height[j] = tiles[i].output[j].output.y.length; for (int p = 0; p < 3; p++) { // Calculate x/y initial downsampler/resampler phases per-plane. if (be_config_.global.rgb_enables & PISP_BE_RGB_ENABLE_DOWNSCALE(j)) { unsigned int frac_x = (resample_size.x.offset * be_config_.downscale[j].scale_factor_h) & ((1 << ScalePrecision) - 1); unsigned int frac_y = (resample_size.y.offset * be_config_.downscale[j].scale_factor_v) & ((1 << ScalePrecision) - 1); // Fractional component of the input required to generate the output pixel. t.downscale_phase_x[p * variant_.BackEndNumBranches(0) + j] = (UnityPhase - frac_x); t.downscale_phase_y[p * variant_.BackEndNumBranches(0) + j] = (UnityPhase - frac_y); } if (be_config_.global.rgb_enables & PISP_BE_RGB_ENABLE_RESAMPLE(j)) { // Location of the output pixel in the interpolated (input) image. unsigned int interpolated_pix_x = (t.output_offset_x[j] * NumPhases * be_config_.resample[j].scale_factor_h) >> ScalePrecision; unsigned int interpolated_pix_y = (t.output_offset_y[j] * NumPhases * be_config_.resample[j].scale_factor_v) >> ScalePrecision; // Phase of the interpolated input pixel. t.resample_phase_x[p * variant_.BackEndNumBranches(0) + j] = ((interpolated_pix_x % NumPhases) << ScalePrecision) / NumPhases; t.resample_phase_y[p * variant_.BackEndNumBranches(0) + j] = ((interpolated_pix_y % NumPhases) << ScalePrecision) / NumPhases; // Account for any user defined initial phase - this could be negative! t.resample_phase_x[p * variant_.BackEndNumBranches(0) + j] += be_config_extra_.resample[j].initial_phase_h[p]; t.resample_phase_y[p * variant_.BackEndNumBranches(0) + j] += be_config_extra_.resample[j].initial_phase_v[p]; // Have to be within this range, else some calculation went wrong. PISP_ASSERT(t.resample_phase_x[p * variant_.BackEndNumBranches(0) + j] <= (2 * UnityPhase - 1)); PISP_ASSERT(t.resample_phase_y[p * variant_.BackEndNumBranches(0) + j] <= (2 * UnityPhase - 1)); } } // Phase difference between planes cannot be > 0.5 pixels on the output dimenstions. if (be_config_.global.rgb_enables & PISP_BE_RGB_ENABLE_RESAMPLE(j)) { int phase_max = (be_config_.resample[j].scale_factor_h * UnityPhase / 2) >> ScalePrecision; if (std::abs(t.resample_phase_x[0 * variant_.BackEndNumBranches(0) + j] - t.resample_phase_x[1 * variant_.BackEndNumBranches(0) + j]) > phase_max || std::abs(t.resample_phase_x[1 * variant_.BackEndNumBranches(0) + j] - t.resample_phase_x[2 * variant_.BackEndNumBranches(0) + j]) > phase_max || std::abs(t.resample_phase_x[0 * variant_.BackEndNumBranches(0) + j] - t.resample_phase_x[2 * variant_.BackEndNumBranches(0) + j]) > phase_max) { throw std::runtime_error("Resample phase x for tile is > 0.5 pixels on the output dimensions."); } phase_max = (be_config_.resample[j].scale_factor_v * UnityPhase / 2) >> ScalePrecision; if (std::abs(t.resample_phase_y[0 * variant_.BackEndNumBranches(0) + j] - t.resample_phase_y[1 * variant_.BackEndNumBranches(0) + j]) > phase_max || std::abs(t.resample_phase_y[1 * variant_.BackEndNumBranches(0) + j] - t.resample_phase_y[2 * variant_.BackEndNumBranches(0) + j]) > phase_max || std::abs(t.resample_phase_y[0 * variant_.BackEndNumBranches(0) + j] - t.resample_phase_y[2 * variant_.BackEndNumBranches(0) + j]) > phase_max) { throw std::runtime_error("Resample phase y for tile is > 0.5 pixels on the output dimensions."); } } } } return tile_array; } void BackEnd::finaliseTiling() { // Update tile parameters (offsets/strides) from on the BE pipeline configuration. for (int i = 0; i < num_tiles_x_ * num_tiles_y_; i++) { pisp_tile &t = tiles_[i]; calculate_input_addr_offset(t.input_offset_x, t.input_offset_y, be_config_.input_format, &t.input_addr_offset, &t.input_addr_offset2); calculate_input_addr_offset(t.input_offset_x, t.input_offset_y, be_config_.tdn_input_format, &t.tdn_input_addr_offset); calculate_input_addr_offset(t.input_offset_x, t.input_offset_y, be_config_.tdn_output_format, &t.tdn_output_addr_offset); calculate_input_addr_offset(t.input_offset_x, t.input_offset_y, be_config_.stitch_input_format, &t.stitch_input_addr_offset); calculate_input_addr_offset(t.input_offset_x, t.input_offset_y, be_config_.stitch_output_format, &t.stitch_output_addr_offset); PISP_LOG(debug, "Input offsets " << t.input_offset_x << "," << t.input_offset_y << " address offsets " << t.input_addr_offset << " and " << t.input_addr_offset2); if (be_config_.global.bayer_enables & PISP_BE_BAYER_ENABLE_LSC) { t.lsc_grid_offset_x = (t.input_offset_x + be_config_extra_.lsc.offset_x) * be_config_.lsc.grid_step_x; t.lsc_grid_offset_y = (t.input_offset_y + be_config_extra_.lsc.offset_y) * be_config_.lsc.grid_step_y; } if (be_config_.global.bayer_enables & PISP_BE_BAYER_ENABLE_CAC) { t.cac_grid_offset_x = (t.input_offset_x + be_config_extra_.cac.offset_x) * be_config_.cac.grid_step_x; t.cac_grid_offset_y = (t.input_offset_y + be_config_extra_.cac.offset_y) * be_config_.cac.grid_step_y; } for (unsigned int j = 0; j < variant_.BackEndNumBranches(0); j++) { int output_offset_x_unflipped = t.output_offset_x[j], output_offset_y_unflipped = t.output_offset_y[j]; if (be_config_.output_format[j].transform & PISP_BE_TRANSFORM_HFLIP) t.output_offset_x[j] = be_config_.output_format[j].image.width - output_offset_x_unflipped - t.output_width[j]; if (be_config_.output_format[j].transform & PISP_BE_TRANSFORM_VFLIP) t.output_offset_y[j] = be_config_.output_format[j].image.height - output_offset_y_unflipped - 1; compute_addr_offset(be_config_.output_format[j].image, t.output_offset_x[j], t.output_offset_y[j], &t.output_addr_offset[j], &t.output_addr_offset2[j]); PISP_LOG(debug, "Branch " << j << " output offsets " << t.output_offset_x[j] << "," << t.output_offset_y[j] << " address offsets " << t.output_addr_offset[j] << " and " << t.output_addr_offset2[j]); } } } void BackEnd::getOutputSize(int i, uint16_t *width, uint16_t *height, pisp_image_format_config const &ifmt) const { // This internal version doesn't check if the output is enabled if (smart_resize_[i].width && smart_resize_[i].height) *width = smart_resize_[i].width, *height = smart_resize_[i].height; else if (be_config_.global.rgb_enables & PISP_BE_RGB_ENABLE_RESAMPLE(i)) *width = be_config_extra_.resample[i].scaled_width, *height = be_config_extra_.resample[i].scaled_height; else if (be_config_.global.rgb_enables & PISP_BE_RGB_ENABLE_DOWNSCALE(i)) *width = be_config_extra_.downscale[i].scaled_width, *height = be_config_extra_.downscale[i].scaled_height; else if (be_config_extra_.crop[i].width) // crop width and height will be zero when crop disabled *width = be_config_extra_.crop[i].width, *height = be_config_extra_.crop[i].height; else *width = ifmt.width, *height = ifmt.height; } bool BackEnd::ComputeOutputImageFormat(unsigned int i, pisp_image_format_config &fmt, pisp_image_format_config const &ifmt) const { PISP_ASSERT(i < PISP_BACK_END_NUM_OUTPUTS); if (&fmt != &be_config_.output_format[i].image) fmt = be_config_.output_format[i].image; if (be_config_.global.rgb_enables & PISP_BE_RGB_ENABLE_OUTPUT(i)) { getOutputSize(i, &fmt.width, &fmt.height, ifmt); if (!fmt.stride) compute_stride(fmt); else check_stride(fmt); return true; } else { fmt.width = 0; fmt.height = 0; fmt.stride = 0; fmt.stride2 = 0; return false; } } void BackEnd::Prepare(pisp_be_tiles_config *config) { PISP_LOG(debug, "New frame!"); // On every start-of-frame we: // 1. Check the input configuration appears sensible. if ((be_config_.global.bayer_enables & PISP_BE_BAYER_ENABLE_INPUT) == 0 && (be_config_.global.rgb_enables & PISP_BE_RGB_ENABLE_INPUT) == 0) throw std::runtime_error("BackEnd::preFrameUpdate: neither Bayer nor RGB inputs are enabled"); else if ((be_config_.global.bayer_enables & PISP_BE_BAYER_ENABLE_INPUT) && (be_config_.global.rgb_enables & PISP_BE_RGB_ENABLE_INPUT)) throw std::runtime_error("BackEnd::preFrameUpdate: both Bayer and RGB inputs are enabled"); // 2. Also check the output configuration is all filled in and looks sensible. Again, addresses must be // left to the HAL. for (unsigned int i = 0; i < variant_.BackEndNumBranches(0); i++) { pisp_image_format_config &image_config = be_config_.output_format[i].image; ComputeOutputImageFormat(i, image_config, be_config_.input_format); if (image_config.format & PISP_IMAGE_FORMAT_INTEGRAL_IMAGE) { throw std::runtime_error("Integral images are not supported."); } } // 3. Fill in any other missing bits of config, and update the tiling if necessary. updateSmartResize(); finaliseConfig(); updateTiles(); if (config) { // Allow passing of empty pointer, if only be_config_ should be filled // 4. Write the config and tiles to the provided buffer to send to the hardware. config->num_tiles = num_tiles_x_ * num_tiles_y_; memcpy(config->tiles, tiles_.data(), config->num_tiles * sizeof(pisp_tile)); config->config = be_config_; // 5. Clear any dirty flags for the next configuration update. be_config_extra_.dirty_flags_bayer = be_config_extra_.dirty_flags_rgb = be_config_extra_.dirty_flags_extra = 0; } } raspberrypi-libpisp-9ba67e6/src/libpisp/backend/meson.build000066400000000000000000000021531507172066400241340ustar00rootroot00000000000000# SPDX-License-Identifier: CC0-1.0 # Copyright (C) 2023, Raspberry Pi Ltd config_install_dir = get_option('prefix') / get_option('datadir') / 'libpisp' conf_data = configuration_data() conf_data.set('PISP_BE_CONFIG_DIR', '"' + config_install_dir + '"') pisp_build_config = configure_file(output : 'pisp_build_config.h', configuration : conf_data) backend_sources = files([ 'backend.cpp', 'backend_debug.cpp', 'backend_default_config.cpp', 'backend_prepare.cpp', ]) backend_headers = files([ 'backend.hpp', 'pisp_be_config.h', ]) backend_include_dir = pisp_include_dir / 'backend' install_headers(backend_headers, subdir: backend_include_dir) install_data('backend_default_config.json', install_dir : config_install_dir) # Copy the json config file to the build directory for running locally. custom_target('Default config to build dir', input : 'backend_default_config.json', output : 'backend_default_config.json', command : ['cp', '@INPUT@', '@OUTPUT@'], install : false, build_by_default : true) subdir('tiling') raspberrypi-libpisp-9ba67e6/src/libpisp/backend/pisp_be_config.h000066400000000000000000000714011507172066400251130ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */ /* * PiSP Back End configuration definitions. * * Copyright (C) 2021 - Raspberry Pi Ltd * */ #ifndef _PISP_BE_CONFIG_H_ #define _PISP_BE_CONFIG_H_ #include #include "common/pisp_common.h" /* byte alignment for inputs */ #define PISP_BACK_END_INPUT_ALIGN 4u /* alignment for compressed inputs */ #define PISP_BACK_END_COMPRESSED_ALIGN 8u /* minimum required byte alignment for outputs */ #define PISP_BACK_END_OUTPUT_MIN_ALIGN 16u /* preferred byte alignment for outputs */ #define PISP_BACK_END_OUTPUT_MAX_ALIGN 64u /* minimum allowed tile width anywhere in the pipeline */ #define PISP_BACK_END_MIN_TILE_WIDTH 16u /* minimum allowed tile width anywhere in the pipeline */ #define PISP_BACK_END_MIN_TILE_HEIGHT 16u #define PISP_BACK_END_NUM_OUTPUTS 2 #define PISP_BACK_END_HOG_OUTPUT 1 #define PISP_BACK_END_NUM_TILES 64 enum pisp_be_bayer_enable { PISP_BE_BAYER_ENABLE_INPUT = 0x000001, PISP_BE_BAYER_ENABLE_DECOMPRESS = 0x000002, PISP_BE_BAYER_ENABLE_DPC = 0x000004, PISP_BE_BAYER_ENABLE_GEQ = 0x000008, PISP_BE_BAYER_ENABLE_TDN_INPUT = 0x000010, PISP_BE_BAYER_ENABLE_TDN_DECOMPRESS = 0x000020, PISP_BE_BAYER_ENABLE_TDN = 0x000040, PISP_BE_BAYER_ENABLE_TDN_COMPRESS = 0x000080, PISP_BE_BAYER_ENABLE_TDN_OUTPUT = 0x000100, PISP_BE_BAYER_ENABLE_SDN = 0x000200, PISP_BE_BAYER_ENABLE_BLC = 0x000400, PISP_BE_BAYER_ENABLE_STITCH_INPUT = 0x000800, PISP_BE_BAYER_ENABLE_STITCH_DECOMPRESS = 0x001000, PISP_BE_BAYER_ENABLE_STITCH = 0x002000, PISP_BE_BAYER_ENABLE_STITCH_COMPRESS = 0x004000, PISP_BE_BAYER_ENABLE_STITCH_OUTPUT = 0x008000, PISP_BE_BAYER_ENABLE_WBG = 0x010000, PISP_BE_BAYER_ENABLE_CDN = 0x020000, PISP_BE_BAYER_ENABLE_LSC = 0x040000, PISP_BE_BAYER_ENABLE_TONEMAP = 0x080000, PISP_BE_BAYER_ENABLE_CAC = 0x100000, PISP_BE_BAYER_ENABLE_DEBIN = 0x200000, PISP_BE_BAYER_ENABLE_DEMOSAIC = 0x400000, }; enum pisp_be_rgb_enable { PISP_BE_RGB_ENABLE_INPUT = 0x000001, PISP_BE_RGB_ENABLE_CCM = 0x000002, PISP_BE_RGB_ENABLE_SAT_CONTROL = 0x000004, PISP_BE_RGB_ENABLE_YCBCR = 0x000008, PISP_BE_RGB_ENABLE_FALSE_COLOUR = 0x000010, PISP_BE_RGB_ENABLE_SHARPEN = 0x000020, /* Preferred colours would occupy 0x000040 */ PISP_BE_RGB_ENABLE_YCBCR_INVERSE = 0x000080, PISP_BE_RGB_ENABLE_GAMMA = 0x000100, PISP_BE_RGB_ENABLE_CSC0 = 0x000200, PISP_BE_RGB_ENABLE_CSC1 = 0x000400, PISP_BE_RGB_ENABLE_DOWNSCALE0 = 0x001000, PISP_BE_RGB_ENABLE_DOWNSCALE1 = 0x002000, PISP_BE_RGB_ENABLE_RESAMPLE0 = 0x008000, PISP_BE_RGB_ENABLE_RESAMPLE1 = 0x010000, PISP_BE_RGB_ENABLE_OUTPUT0 = 0x040000, PISP_BE_RGB_ENABLE_OUTPUT1 = 0x080000, PISP_BE_RGB_ENABLE_HOG = 0x200000 }; #define PISP_BE_RGB_ENABLE_CSC(i) (PISP_BE_RGB_ENABLE_CSC0 << (i)) #define PISP_BE_RGB_ENABLE_DOWNSCALE(i) (PISP_BE_RGB_ENABLE_DOWNSCALE0 << (i)) #define PISP_BE_RGB_ENABLE_RESAMPLE(i) (PISP_BE_RGB_ENABLE_RESAMPLE0 << (i)) #define PISP_BE_RGB_ENABLE_OUTPUT(i) (PISP_BE_RGB_ENABLE_OUTPUT0 << (i)) /* * We use the enable flags to show when blocks are "dirty", but we need some * extra ones too. */ enum pisp_be_dirty { PISP_BE_DIRTY_GLOBAL = 0x0001, PISP_BE_DIRTY_SH_FC_COMBINE = 0x0002, PISP_BE_DIRTY_CROP = 0x0004 }; /** * struct pisp_be_global_config - PiSP global enable bitmaps * @bayer_enables: Bayer input enable flags * @rgb_enables: RGB output enable flags * @bayer_order: Bayer input format ordering * @pad: Padding bytes */ struct pisp_be_global_config { __u32 bayer_enables; __u32 rgb_enables; __u8 bayer_order; __u8 pad[3]; } __attribute__((packed)); /** * struct pisp_be_input_buffer_config - PiSP Back End input buffer * @addr: Input buffer address */ struct pisp_be_input_buffer_config { /* low 32 bits followed by high 32 bits (for each of up to 3 planes) */ __u32 addr[3][2]; } __attribute__((packed)); /** * struct pisp_be_dpc_config - PiSP Back End DPC config * * Defective Pixel Correction configuration * * @coeff_level: Coefficient for the darkest neighbouring pixel value * @coeff_range: Coefficient for the range of pixels for this Bayer channel * @pad: Padding byte * @flags: DPC configuration flags */ struct pisp_be_dpc_config { __u8 coeff_level; __u8 coeff_range; __u8 pad; #define PISP_BE_DPC_FLAG_FOLDBACK 1 __u8 flags; } __attribute__((packed)); /** * struct pisp_be_geq_config - PiSP Back End GEQ config * * Green Equalisation configuration * * @offset: Offset value for threshold calculation * @slope_sharper: Slope/Sharper configuration * @min: Minimum value the threshold may have * @max: Maximum value the threshold may have */ struct pisp_be_geq_config { __u16 offset; #define PISP_BE_GEQ_SHARPER (1U << 15) #define PISP_BE_GEQ_SLOPE ((1 << 10) - 1) /* top bit is the "sharper" flag, slope value is bottom 10 bits */ __u16 slope_sharper; __u16 min; __u16 max; } __attribute__((packed)); /** * struct pisp_be_tdn_input_buffer_config - PiSP Back End TDN input buffer * @addr: TDN input buffer address */ struct pisp_be_tdn_input_buffer_config { /* low 32 bits followed by high 32 bits */ __u32 addr[2]; } __attribute__((packed)); /** * struct pisp_be_tdn_config - PiSP Back End TDN config * * Temporal Denoise configuration * * @black_level: Black level value subtracted from pixels * @ratio: Multiplier for the LTA input frame * @noise_constant: Constant offset value used in noise estimation * @noise_slope: Noise estimation multiplier * @threshold: Threshold for TDN operations * @reset: Disable TDN operations * @pad: Padding byte */ struct pisp_be_tdn_config { __u16 black_level; __u16 ratio; __u16 noise_constant; __u16 noise_slope; __u16 threshold; __u8 reset; __u8 pad; } __attribute__((packed)); /** * struct pisp_be_tdn_output_buffer_config - PiSP Back End TDN output buffer * @addr: TDN output buffer address */ struct pisp_be_tdn_output_buffer_config { /* low 32 bits followed by high 32 bits */ __u32 addr[2]; } __attribute__((packed)); /** * struct pisp_be_sdn_config - PiSP Back End SDN config * * Spatial Denoise configuration * * @black_level: Black level subtracted from pixel for noise estimation * @leakage: Proportion of the original undenoised value to mix in * denoised output * @pad: Padding byte * @noise_constant: Noise constant used for noise estimation * @noise_slope: Noise slope value used for noise estimation * @noise_constant2: Second noise constant used for noise estimation * @noise_slope2: Second slope value used for noise estimation */ struct pisp_be_sdn_config { __u16 black_level; __u8 leakage; __u8 pad; __u16 noise_constant; __u16 noise_slope; __u16 noise_constant2; __u16 noise_slope2; } __attribute__((packed)); /** * struct pisp_be_stitch_input_buffer_config - PiSP Back End Stitch input * @addr: Stitch input buffer address */ struct pisp_be_stitch_input_buffer_config { /* low 32 bits followed by high 32 bits */ __u32 addr[2]; } __attribute__((packed)); #define PISP_BE_STITCH_STREAMING_LONG 0x8000 #define PISP_BE_STITCH_EXPOSURE_RATIO_MASK 0x7fff /** * struct pisp_be_stitch_config - PiSP Back End Stitch config * * Stitch block configuration * * @threshold_lo: Low threshold value * @threshold_diff_power: Low and high threshold difference * @pad: Padding bytes * @exposure_ratio: Multiplier to convert long exposure pixels into * short exposure pixels * @motion_threshold_256: Motion threshold above which short exposure * pixels are used * @motion_threshold_recip: Reciprocal of motion_threshold_256 value */ struct pisp_be_stitch_config { __u16 threshold_lo; __u8 threshold_diff_power; __u8 pad; /* top bit indicates whether streaming input is the long exposure */ __u16 exposure_ratio; __u8 motion_threshold_256; __u8 motion_threshold_recip; } __attribute__((packed)); /** * struct pisp_be_stitch_output_buffer_config - PiSP Back End Stitch output * @addr: Stitch input buffer address */ struct pisp_be_stitch_output_buffer_config { /* low 32 bits followed by high 32 bits */ __u32 addr[2]; } __attribute__((packed)); /** * struct pisp_be_cdn_config - PiSP Back End CDN config * * Colour Denoise configuration * * @thresh: Constant for noise estimation * @iir_strength: Relative strength of the IIR part of the filter * @g_adjust: Proportion of the change assigned to the G channel */ struct pisp_be_cdn_config { __u16 thresh; __u8 iir_strength; __u8 g_adjust; } __attribute__((packed)); #define PISP_BE_LSC_LOG_GRID_SIZE 5 #define PISP_BE_LSC_GRID_SIZE (1 << PISP_BE_LSC_LOG_GRID_SIZE) #define PISP_BE_LSC_STEP_PRECISION 18 /** * struct pisp_be_lsc_config - PiSP Back End LSC config * * Lens Shading Correction configuration * * @grid_step_x: Reciprocal of cell size width * @grid_step_y: Reciprocal of cell size height * @lut_packed: Jointly-coded RGB gains for each LSC grid */ struct pisp_be_lsc_config { /* (1<<18) / grid_cell_width */ __u16 grid_step_x; /* (1<<18) / grid_cell_height */ __u16 grid_step_y; /* RGB gains jointly encoded in 32 bits */ #define PISP_BE_LSC_LUT_SIZE (PISP_BE_LSC_GRID_SIZE + 1) __u32 lut_packed[PISP_BE_LSC_LUT_SIZE][PISP_BE_LSC_LUT_SIZE]; } __attribute__((packed)); /** * struct pisp_be_lsc_extra - PiSP Back End LSC Extra config * @offset_x: Horizontal offset into the LSC table of this tile * @offset_y: Vertical offset into the LSC table of this tile */ struct pisp_be_lsc_extra { __u16 offset_x; __u16 offset_y; } __attribute__((packed)); #define PISP_BE_CAC_LOG_GRID_SIZE 3 #define PISP_BE_CAC_GRID_SIZE (1 << PISP_BE_CAC_LOG_GRID_SIZE) #define PISP_BE_CAC_STEP_PRECISION 20 /** * struct pisp_be_cac_config - PiSP Back End CAC config * * Chromatic Aberration Correction config * * @grid_step_x: Reciprocal of cell size width * @grid_step_y: Reciprocal of cell size height * @lut: Pixel shift for the CAC grid */ struct pisp_be_cac_config { /* (1<<20) / grid_cell_width */ __u16 grid_step_x; /* (1<<20) / grid_cell_height */ __u16 grid_step_y; /* [gridy][gridx][rb][xy] */ #define PISP_BE_CAC_LUT_SIZE (PISP_BE_CAC_GRID_SIZE + 1) __s8 lut[PISP_BE_CAC_LUT_SIZE][PISP_BE_CAC_LUT_SIZE][2][2]; } __attribute__((packed)); /** * struct pisp_be_cac_extra - PiSP Back End CAC extra config * @offset_x: Horizontal offset into the CAC table of this tile * @offset_y: Horizontal offset into the CAC table of this tile */ struct pisp_be_cac_extra { __u16 offset_x; __u16 offset_y; } __attribute__((packed)); #define PISP_BE_DEBIN_NUM_COEFFS 4 /** * struct pisp_be_debin_config - PiSP Back End Debin config * * Debinning configuration * * @coeffs: Filter coefficients for debinning * @h_enable: Horizontal debinning enable * @v_enable: Vertical debinning enable * @pad: Padding bytes */ struct pisp_be_debin_config { __s8 coeffs[PISP_BE_DEBIN_NUM_COEFFS]; __s8 h_enable; __s8 v_enable; __s8 pad[2]; } __attribute__((packed)); #define PISP_BE_TONEMAP_LUT_SIZE 64 /** * struct pisp_be_tonemap_config - PiSP Back End Tonemap config * * Tonemapping configuration * * @detail_constant: Constant value for threshold calculation * @detail_slope: Slope value for threshold calculation * @iir_strength: Relative strength of the IIR fiter * @strength: Strength factor * @lut: Look-up table for tonemap curve */ struct pisp_be_tonemap_config { __u16 detail_constant; __u16 detail_slope; __u16 iir_strength; __u16 strength; __u32 lut[PISP_BE_TONEMAP_LUT_SIZE]; } __attribute__((packed)); /** * struct pisp_be_demosaic_config - PiSP Back End Demosaic config * * Demosaic configuration * * @sharper: Use other Bayer channels to increase sharpness * @fc_mode: Built-in false colour suppression mode * @pad: Padding bytes */ struct pisp_be_demosaic_config { __u8 sharper; __u8 fc_mode; __u8 pad[2]; } __attribute__((packed)); /** * struct pisp_be_ccm_config - PiSP Back End CCM config * * Colour Correction Matrix configuration * * @coeffs: Matrix coefficients * @pad: Padding bytes * @offsets: Offsets triplet */ struct pisp_be_ccm_config { __s16 coeffs[9]; __u8 pad[2]; __s32 offsets[3]; } __attribute__((packed)); /** * struct pisp_be_sat_control_config - PiSP Back End SAT config * * Saturation Control configuration * * @shift_r: Left shift for Red colour channel * @shift_g: Left shift for Green colour channel * @shift_b: Left shift for Blue colour channel * @pad: Padding byte */ struct pisp_be_sat_control_config { __u8 shift_r; __u8 shift_g; __u8 shift_b; __u8 pad; } __attribute__((packed)); /** * struct pisp_be_false_colour_config - PiSP Back End False Colour config * * False Colour configuration * * @distance: Distance of neighbouring pixels, either 1 or 2 * @pad: Padding bytes */ struct pisp_be_false_colour_config { __u8 distance; __u8 pad[3]; } __attribute__((packed)); #define PISP_BE_SHARPEN_SIZE 5 #define PISP_BE_SHARPEN_FUNC_NUM_POINTS 9 /** * struct pisp_be_sharpen_config - PiSP Back End Sharpening config * * Sharpening configuration * * @kernel0: Coefficient for filter 0 * @pad0: Padding byte * @kernel1: Coefficient for filter 1 * @pad1: Padding byte * @kernel2: Coefficient for filter 2 * @pad2: Padding byte * @kernel3: Coefficient for filter 3 * @pad3: Padding byte * @kernel4: Coefficient for filter 4 * @pad4: Padding byte * @threshold_offset0: Offset for filter 0 response calculation * @threshold_slope0: Slope multiplier for the filter 0 response calculation * @scale0: Scale factor for filter 0 response calculation * @pad5: Padding byte * @threshold_offset1: Offset for filter 0 response calculation * @threshold_slope1: Slope multiplier for the filter 0 response calculation * @scale1: Scale factor for filter 0 response calculation * @pad6: Padding byte * @threshold_offset2: Offset for filter 0 response calculation * @threshold_slope2: Slope multiplier for the filter 0 response calculation * @scale2: Scale factor for filter 0 response calculation * @pad7: Padding byte * @threshold_offset3: Offset for filter 0 response calculation * @threshold_slope3: Slope multiplier for the filter 0 response calculation * @scale3: Scale factor for filter 0 response calculation * @pad8: Padding byte * @threshold_offset4: Offset for filter 0 response calculation * @threshold_slope4: Slope multiplier for the filter 0 response calculation * @scale4: Scale factor for filter 0 response calculation * @pad9: Padding byte * @positive_strength: Factor to scale the positive sharpening strength * @positive_pre_limit: Maximum allowed possible positive sharpening value * @positive_func: Gain factor applied to positive sharpening response * @positive_limit: Final gain factor applied to positive sharpening * @negative_strength: Factor to scale the negative sharpening strength * @negative_pre_limit: Maximum allowed possible negative sharpening value * @negative_func: Gain factor applied to negative sharpening response * @negative_limit: Final gain factor applied to negative sharpening * @enables: Filter enable mask * @white: White output pixel filter mask * @black: Black output pixel filter mask * @grey: Grey output pixel filter mask */ struct pisp_be_sharpen_config { __s8 kernel0[PISP_BE_SHARPEN_SIZE * PISP_BE_SHARPEN_SIZE]; __s8 pad0[3]; __s8 kernel1[PISP_BE_SHARPEN_SIZE * PISP_BE_SHARPEN_SIZE]; __s8 pad1[3]; __s8 kernel2[PISP_BE_SHARPEN_SIZE * PISP_BE_SHARPEN_SIZE]; __s8 pad2[3]; __s8 kernel3[PISP_BE_SHARPEN_SIZE * PISP_BE_SHARPEN_SIZE]; __s8 pad3[3]; __s8 kernel4[PISP_BE_SHARPEN_SIZE * PISP_BE_SHARPEN_SIZE]; __s8 pad4[3]; __u16 threshold_offset0; __u16 threshold_slope0; __u16 scale0; __u16 pad5; __u16 threshold_offset1; __u16 threshold_slope1; __u16 scale1; __u16 pad6; __u16 threshold_offset2; __u16 threshold_slope2; __u16 scale2; __u16 pad7; __u16 threshold_offset3; __u16 threshold_slope3; __u16 scale3; __u16 pad8; __u16 threshold_offset4; __u16 threshold_slope4; __u16 scale4; __u16 pad9; __u16 positive_strength; __u16 positive_pre_limit; __u16 positive_func[PISP_BE_SHARPEN_FUNC_NUM_POINTS]; __u16 positive_limit; __u16 negative_strength; __u16 negative_pre_limit; __u16 negative_func[PISP_BE_SHARPEN_FUNC_NUM_POINTS]; __u16 negative_limit; __u8 enables; __u8 white; __u8 black; __u8 grey; } __attribute__((packed)); /** * struct pisp_be_sh_fc_combine_config - PiSP Back End Sharpening and * False Colour config * * Sharpening and False Colour configuration * * @y_factor: Control amount of desaturation of pixels being darkened * @c1_factor: Control amount of brightening of a pixel for the Cb * channel * @c2_factor: Control amount of brightening of a pixel for the Cr * channel * @pad: Padding byte */ struct pisp_be_sh_fc_combine_config { __u8 y_factor; __u8 c1_factor; __u8 c2_factor; __u8 pad; } __attribute__((packed)); #define PISP_BE_GAMMA_LUT_SIZE 64 /** * struct pisp_be_gamma_config - PiSP Back End Gamma configuration * @lut: Gamma curve look-up table */ struct pisp_be_gamma_config { __u32 lut[PISP_BE_GAMMA_LUT_SIZE]; } __attribute__((packed)); /** * struct pisp_be_crop_config - PiSP Back End Crop config * * Crop configuration * * @offset_x: Number of pixels cropped from the left of the tile * @offset_y: Number of pixels cropped from the top of the tile * @width: Width of the cropped tile output * @height: Height of the cropped tile output */ struct pisp_be_crop_config { __u16 offset_x, offset_y; __u16 width, height; } __attribute__((packed)); #define PISP_BE_RESAMPLE_FILTER_SIZE 96 /** * struct pisp_be_resample_config - PiSP Back End Resampling config * * Resample configuration * * @scale_factor_h: Horizontal scale factor * @scale_factor_v: Vertical scale factor * @coef: Resample coefficients */ struct pisp_be_resample_config { __u16 scale_factor_h, scale_factor_v; __s16 coef[PISP_BE_RESAMPLE_FILTER_SIZE]; } __attribute__((packed)); /** * struct pisp_be_resample_extra - PiSP Back End Resample config * * Resample configuration * * @scaled_width: Width in pixels of the scaled output * @scaled_height: Height in pixels of the scaled output * @initial_phase_h: Initial horizontal phase * @initial_phase_v: Initial vertical phase */ struct pisp_be_resample_extra { __u16 scaled_width; __u16 scaled_height; __s16 initial_phase_h[3]; __s16 initial_phase_v[3]; } __attribute__((packed)); /** * struct pisp_be_downscale_config - PiSP Back End Downscale config * * Downscale configuration * * @scale_factor_h: Horizontal scale factor * @scale_factor_v: Vertical scale factor * @scale_recip_h: Horizontal reciprocal factor * @scale_recip_v: Vertical reciprocal factor */ struct pisp_be_downscale_config { __u16 scale_factor_h; __u16 scale_factor_v; __u16 scale_recip_h; __u16 scale_recip_v; } __attribute__((packed)); /** * struct pisp_be_downscale_extra - PiSP Back End Downscale Extra config * @scaled_width: Scaled image width * @scaled_height: Scaled image height */ struct pisp_be_downscale_extra { __u16 scaled_width; __u16 scaled_height; } __attribute__((packed)); /** * struct pisp_be_hog_config - PiSP Back End HOG config * * Histogram of Oriented Gradients configuration * * @compute_signed: Set 0 for unsigned gradients, 1 for signed * @channel_mix: Channels proportions to use * @stride: Stride in bytes between blocks directly below */ struct pisp_be_hog_config { __u8 compute_signed; __u8 channel_mix[3]; __u32 stride; } __attribute__((packed)); struct pisp_be_axi_config { __u8 r_qos; /* Read QoS */ __u8 r_cache_prot; /* Read { prot[2:0], cache[3:0] } */ __u8 w_qos; /* Write QoS */ __u8 w_cache_prot; /* Write { prot[2:0], cache[3:0] } */ } __attribute__((packed)); /** * enum pisp_be_transform - PiSP Back End Transform flags * @PISP_BE_TRANSFORM_NONE: No transform * @PISP_BE_TRANSFORM_HFLIP: Horizontal flip * @PISP_BE_TRANSFORM_VFLIP: Vertical flip * @PISP_BE_TRANSFORM_ROT180: 180 degress rotation */ enum pisp_be_transform { PISP_BE_TRANSFORM_NONE = 0x0, PISP_BE_TRANSFORM_HFLIP = 0x1, PISP_BE_TRANSFORM_VFLIP = 0x2, PISP_BE_TRANSFORM_ROT180 = (PISP_BE_TRANSFORM_HFLIP | PISP_BE_TRANSFORM_VFLIP) }; struct pisp_be_output_format_config { struct pisp_image_format_config image; __u8 transform; __u8 pad[3]; __u16 lo; __u16 hi; __u16 lo2; __u16 hi2; } __attribute__((packed)); /** * struct pisp_be_output_buffer_config - PiSP Back End Output buffer * @addr: Output buffer address */ struct pisp_be_output_buffer_config { /* low 32 bits followed by high 32 bits (for each of 3 planes) */ __u32 addr[3][2]; } __attribute__((packed)); /** * struct pisp_be_hog_buffer_config - PiSP Back End HOG buffer * @addr: HOG buffer address */ struct pisp_be_hog_buffer_config { /* low 32 bits followed by high 32 bits */ __u32 addr[2]; } __attribute__((packed)); /** * struct pisp_be_config - RaspberryPi PiSP Back End Processing configuration * * @global: Global PiSP configuration * @input_format: Input image format * @decompress: Decompress configuration * @dpc: Defective Pixel Correction configuration * @geq: Green Equalisation configuration * @tdn_input_format: Temporal Denoise input format * @tdn_decompress: Temporal Denoise decompress configuration * @tdn: Temporal Denoise configuration * @tdn_compress: Temporal Denoise compress configuration * @tdn_output_format: Temporal Denoise output format * @sdn: Spatial Denoise configuration * @blc: Black Level Correction configuration * @stitch_compress: Stitch compress configuration * @stitch_output_format: Stitch output format * @stitch_input_format: Stitch input format * @stitch_decompress: Stitch decompress configuration * @stitch: Stitch configuration * @lsc: Lens Shading Correction configuration * @wbg: White Balance Gain configuration * @cdn: Colour Denoise configuration * @cac: Colour Aberration Correction configuration * @debin: Debinning configuration * @tonemap: Tonemapping configuration * @demosaic: Demosaicing configuration * @ccm: Colour Correction Matrix configuration * @sat_control: Saturation Control configuration * @ycbcr: YCbCr colour correction configuration * @sharpen: Sharpening configuration * @false_colour: False colour correction * @sh_fc_combine: Sharpening and False Colour correction * @ycbcr_inverse: Inverse YCbCr colour correction * @gamma: Gamma curve configuration * @csc: Color Space Conversion configuration * @downscale: Downscale configuration * @resample: Resampling configuration * @output_format: Output format configuration * @hog: HOG configuration * @axi: AXI bus configuration */ struct pisp_be_config { /* For backward compatibility */ uint8_t pad0[112]; /* Processing configuration: */ struct pisp_be_global_config global; struct pisp_image_format_config input_format; struct pisp_decompress_config decompress; struct pisp_be_dpc_config dpc; struct pisp_be_geq_config geq; struct pisp_image_format_config tdn_input_format; struct pisp_decompress_config tdn_decompress; struct pisp_be_tdn_config tdn; struct pisp_compress_config tdn_compress; struct pisp_image_format_config tdn_output_format; struct pisp_be_sdn_config sdn; struct pisp_bla_config blc; struct pisp_compress_config stitch_compress; struct pisp_image_format_config stitch_output_format; struct pisp_image_format_config stitch_input_format; struct pisp_decompress_config stitch_decompress; struct pisp_be_stitch_config stitch; struct pisp_be_lsc_config lsc; struct pisp_wbg_config wbg; struct pisp_be_cdn_config cdn; struct pisp_be_cac_config cac; struct pisp_be_debin_config debin; struct pisp_be_tonemap_config tonemap; struct pisp_be_demosaic_config demosaic; struct pisp_be_ccm_config ccm; struct pisp_be_sat_control_config sat_control; struct pisp_be_ccm_config ycbcr; struct pisp_be_sharpen_config sharpen; struct pisp_be_false_colour_config false_colour; struct pisp_be_sh_fc_combine_config sh_fc_combine; struct pisp_be_ccm_config ycbcr_inverse; struct pisp_be_gamma_config gamma; struct pisp_be_ccm_config csc[PISP_BACK_END_NUM_OUTPUTS]; struct pisp_be_downscale_config downscale[PISP_BACK_END_NUM_OUTPUTS]; struct pisp_be_resample_config resample[PISP_BACK_END_NUM_OUTPUTS]; struct pisp_be_output_format_config output_format[PISP_BACK_END_NUM_OUTPUTS]; struct pisp_be_hog_config hog; struct pisp_be_axi_config axi; /* For backward compatibility */ uint8_t pad1[84]; } __attribute__((packed)); /** * enum pisp_tile_edge - PiSP Back End Tile position * @PISP_LEFT_EDGE: Left edge tile * @PISP_RIGHT_EDGE: Right edge tile * @PISP_TOP_EDGE: Top edge tile * @PISP_BOTTOM_EDGE: Bottom edge tile */ enum pisp_tile_edge { PISP_LEFT_EDGE = (1 << 0), PISP_RIGHT_EDGE = (1 << 1), PISP_TOP_EDGE = (1 << 2), PISP_BOTTOM_EDGE = (1 << 3) }; /** * struct pisp_tile - Raspberry Pi PiSP Back End tile configuration * * Tile parameters: each set of tile parameters is a 160-bytes block of data * which contains the tile processing parameters. * * @edge: Edge tile flag * @pad0: Padding bytes * @input_addr_offset: Top-left pixel offset, in bytes * @input_addr_offset2: Top-left pixel offset, in bytes for the second/ * third image planes * @input_offset_x: Horizontal offset in pixels of this tile in the * input image * @input_offset_y: Vertical offset in pixels of this tile in the * input image * @input_width: Width in pixels of this tile * @input_height: Height in pixels of the this tile * @tdn_input_addr_offset: TDN input image offset, in bytes * @tdn_output_addr_offset: TDN output image offset, in bytes * @stitch_input_addr_offset: Stitch input image offset, in bytes * @stitch_output_addr_offset: Stitch output image offset, in bytes * @lsc_grid_offset_x: Horizontal offset in the LSC table for this tile * @lsc_grid_offset_y: Vertical offset in the LSC table for this tile * @cac_grid_offset_x: Horizontal offset in the CAC table for this tile * @cac_grid_offset_y: Horizontal offset in the CAC table for this tile * @crop_x_start: Number of pixels cropped from the left of the * tile * @crop_x_end: Number of pixels cropped from the right of the * tile * @crop_y_start: Number of pixels cropped from the top of the * tile * @crop_y_end: Number of pixels cropped from the bottom of the * tile * @downscale_phase_x: Initial horizontal phase in pixels * @downscale_phase_y: Initial vertical phase in pixels * @resample_in_width: Width in pixels of the tile entering the * Resample block * @resample_in_height: Height in pixels of the tile entering the * Resample block * @resample_phase_x: Initial horizontal phase for the Resample block * @resample_phase_y: Initial vertical phase for the Resample block * @output_offset_x: Horizontal offset in pixels where the tile will * be written into the output image * @output_offset_y: Vertical offset in pixels where the tile will be * written into the output image * @output_width: Width in pixels in the output image of this tile * @output_height: Height in pixels in the output image of this tile * @output_addr_offset: Offset in bytes into the output buffer * @output_addr_offset2: Offset in bytes into the output buffer for the * second and third plane * @output_hog_addr_offset: Offset in bytes into the HOG buffer where * results of this tile are to be written */ struct pisp_tile { __u8 edge; /* enum pisp_tile_edge */ __u8 pad0[3]; /* 4 bytes */ __u32 input_addr_offset; __u32 input_addr_offset2; __u16 input_offset_x; __u16 input_offset_y; __u16 input_width; __u16 input_height; /* 20 bytes */ __u32 tdn_input_addr_offset; __u32 tdn_output_addr_offset; __u32 stitch_input_addr_offset; __u32 stitch_output_addr_offset; /* 36 bytes */ __u32 lsc_grid_offset_x; __u32 lsc_grid_offset_y; /* 44 bytes */ __u32 cac_grid_offset_x; __u32 cac_grid_offset_y; /* 52 bytes */ __u16 crop_x_start[PISP_BACK_END_NUM_OUTPUTS]; __u16 crop_x_end[PISP_BACK_END_NUM_OUTPUTS]; __u16 crop_y_start[PISP_BACK_END_NUM_OUTPUTS]; __u16 crop_y_end[PISP_BACK_END_NUM_OUTPUTS]; /* 68 bytes */ /* Ordering is planes then branches */ __u16 downscale_phase_x[3 * PISP_BACK_END_NUM_OUTPUTS]; __u16 downscale_phase_y[3 * PISP_BACK_END_NUM_OUTPUTS]; /* 92 bytes */ __u16 resample_in_width[PISP_BACK_END_NUM_OUTPUTS]; __u16 resample_in_height[PISP_BACK_END_NUM_OUTPUTS]; /* 100 bytes */ /* Ordering is planes then branches */ __u16 resample_phase_x[3 * PISP_BACK_END_NUM_OUTPUTS]; __u16 resample_phase_y[3 * PISP_BACK_END_NUM_OUTPUTS]; /* 124 bytes */ __u16 output_offset_x[PISP_BACK_END_NUM_OUTPUTS]; __u16 output_offset_y[PISP_BACK_END_NUM_OUTPUTS]; __u16 output_width[PISP_BACK_END_NUM_OUTPUTS]; __u16 output_height[PISP_BACK_END_NUM_OUTPUTS]; /* 140 bytes */ __u32 output_addr_offset[PISP_BACK_END_NUM_OUTPUTS]; __u32 output_addr_offset2[PISP_BACK_END_NUM_OUTPUTS]; /* 156 bytes */ __u32 output_hog_addr_offset; /* 160 bytes */ } __attribute__((packed)); /** * struct pisp_be_tiles_config - Raspberry Pi PiSP Back End configuration * @tiles: Tile descriptors * @num_tiles: Number of tiles * @config: PiSP Back End configuration */ struct pisp_be_tiles_config { struct pisp_be_config config; struct pisp_tile tiles[PISP_BACK_END_NUM_TILES]; __u32 num_tiles; } __attribute__((packed)); #endif /* _PISP_BE_CONFIG_H_ */ raspberrypi-libpisp-9ba67e6/src/libpisp/backend/tiling/000077500000000000000000000000001507172066400232575ustar00rootroot00000000000000raspberrypi-libpisp-9ba67e6/src/libpisp/backend/tiling/README.txt000066400000000000000000000060541507172066400247620ustar00rootroot00000000000000Code to tile up an image for processing by the PiSP. Essentially we have a wrapper class, Pipeline (pipeline.h), which contains some configuration and points to all the processing stages. Then we have the abstract Stage class (stages.h) which is specialised to describe how a tile going through it will change. Most stages have a single upstream and a single downstream stage, and these can be conveniently derived from BasicStage (also stages.h), though there are other kinds of stages too (e.g. SplitStage in split_stage.hpp). The algorithm requires 3 principle methods to be defined on each derived version of the stage class. Note that the tiling algorithm is agnostic about the direction (X or Y) in which it is tiling. It merely uses the direction to read the appropriate configuration values for that direction, but is otherwise the same. The three methods are: 1. void PushStartUp(int output_start, Dir dir) This method is given the coordinate (in the output image produced as a whole by this stage) of the place where the output tile must begin. The code must calculate what input coordinate (in the input image as a whole which the Stage will see) will be required. This is then passed to the the upstream stage's PushStartUp method. (Normally a stage's input and output images are the same size, though some stages, such as crops and resizes, will change it.) 2. int PushEndDown(int input_end, Dir dir) This time we are given the final input pixel coordinate of the tile. We must calculate the final output pixel coordinate that we can produce given this end point. This value is passed to the downstream stage's PushEndDown method. (The return value will be described after the following and final of these three methods.) 3. void PushEndUp(int output_end, Dir dir) This method takes the final pixel that the downstream stage could actually make (which may be less that we told it when we invoked PushEndDown on it), and we must calculate the last input pixel that we require to make it. PushEndDown and PushEndUp work together. Before PushEndDown returns it invokes PushEndUp and it is that last input pixel coordinate calculated by PushEndUp that PushEndDown returns. In this way, calling PushEndDown with a value can can be thought of as asking "here is the maximum number of pixels that you can have, tell me how many you can actually use". The progress of the tiling algorithm should be plainly visible in Pipeline::TileDirection. We work from left to right (or top to bottom). At each point we know where we are in the output image(s) - given by the end coordinate of the previous tile, or zero for the first tile. This gets pushed all the way to the top of the pipeline using PushStartUp, starting from each of our final output stages. Then we can read from the input stages which pixels they will have to feed downstream. We add the maximum allowed tile size to this, and then push this value all the way back down using PushEndDown. Recall that PushEndDown implicitly calls PushEndUp, and this trims back the right hand edge of the tiles so that we don't read pixels that we can't use.raspberrypi-libpisp-9ba67e6/src/libpisp/backend/tiling/context_stage.cpp000066400000000000000000000067471507172066400266500ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (C) 2021 - 2023, Raspberry Pi Ltd * * context_stage.cpp - Tiling library component for context stages */ #include "context_stage.hpp" #include "common/logging.hpp" #include "pipeline.hpp" using namespace tiling; ContextStage::ContextStage(char const *name, Stage *upstream, Config const &config, int struct_offset) : BasicStage(name, upstream->GetPipeline(), upstream, struct_offset), config_(config) { } void ContextStage::PushStartUp(int output_start, Dir dir) { PISP_LOG(debug, "(" << name_ << ") Enter with output_start " << output_start); int input_start = output_start - config_.context[dir].start; if (input_start < 0) input_start = 0; input_start -= input_start % config_.alignment[dir]; output_interval_.offset = output_start; input_interval_.offset = input_start; PISP_LOG(debug, "(" << name_ << ") Exit - call PushStartUp with " << input_interval_.offset); upstream_->PushStartUp(input_start, dir); } int ContextStage::PushEndDown(int input_end, Dir dir) { // If we're given an end point that doesn't align then what are we supposed to do about it? Well, // we have to rely on the subsequent PushEndUp to correct the upstream stage, but we must ensure // we send a value downstream that, when it comes back (possibly modified) in PushEndUp, won't // cause us to demand a input larger than was given to us here. Simple, no? PISP_LOG(debug, "(" << name_ << ") Enter with input_end " << input_end); int output_end = input_end; if (input_end < GetInputImageSize()[dir]) { output_end -= output_end % config_.alignment[dir]; output_end -= config_.context[dir].end; } input_interval_.SetEnd(input_end); output_interval_.SetEnd(output_end); PISP_LOG(debug, "(" << name_ << ") Exit with output_end " << output_end); PushEndUp(downstream_->PushEndDown(output_end, dir), dir); return input_interval_.End(); } void ContextStage::PushEndUp(int output_end, Dir dir) { PISP_LOG(debug, "(" << name_ << ") Enter with output_end " << output_end); PISP_ASSERT(output_end <= output_interval_.End()); int input_end = output_end; input_end += config_.context[dir].end; int align = config_.alignment[dir]; input_end = ((input_end + align - 1) / align) * align; if (input_end > GetInputImageSize()[dir]) input_end = GetInputImageSize()[dir]; input_interval_.SetEnd(input_end); output_interval_.SetEnd(output_end); PISP_LOG(debug, "(" << name_ << ") Exit with input_end " << input_end); } void ContextStage::PushCropDown(Interval interval, Dir dir) { PISP_LOG(debug, "(" << name_ << ") Enter with interval " << interval); PISP_ASSERT(input_interval_ < interval); int align = config_.alignment[dir]; if (interval.offset % align || (interval.End() % align && interval.End() != GetInputImageSize()[dir])) { // Interval doesn't align properly. Some cropping will be required. Rather than calculating // the necessary crop it's safe just to send out former input tile downstream. This could // genuinely happen if people put weird alignments throughout their pipeline, but in practice // Bayer stages should all be 2-pixel aligned so there's no reason this should pop out. PISP_LOG(warning, "Stage receiving misaligned input - cropping will be required"); output_interval_ = input_interval_; } else output_interval_ = interval; input_interval_ = interval; crop_ = input_interval_ - output_interval_; PISP_LOG(debug, "(" << name_ << ") Exit with interval " << output_interval_); downstream_->PushCropDown(output_interval_, dir); } raspberrypi-libpisp-9ba67e6/src/libpisp/backend/tiling/context_stage.hpp000066400000000000000000000014331507172066400266400ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (C) 2021 - 2023, Raspberry Pi Ltd * * context_stage.hpp - Tiling library component for context stages */ #pragma once #include "stages.hpp" namespace tiling { class ContextStage : public BasicStage { public: struct Config { Config(Crop2 const &_context, Length2 const &_alignment) : context(_context), alignment(_alignment) {} Crop2 context; Length2 alignment; }; ContextStage(char const *name, Stage *upstream, Config const &config, int struct_offset); virtual void PushStartUp(int output_start, Dir dir); virtual int PushEndDown(int input_end, Dir dir); virtual void PushEndUp(int output_end, Dir dir); virtual void PushCropDown(Interval interval, Dir dir); private: Config config_; }; } // namespace tilingraspberrypi-libpisp-9ba67e6/src/libpisp/backend/tiling/crop_stage.cpp000066400000000000000000000076561507172066400261270ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (C) 2021 - 2023, Raspberry Pi Ltd * * crop_stage.cpp - Tiling library component for crop stages */ #include "crop_stage.hpp" #include "common/logging.hpp" #include "pipeline.hpp" using namespace tiling; namespace { inline bool interval_valid(const Interval &interval, const int min_tile_size) { return interval.End() >= min_tile_size && interval.length >= min_tile_size; } } // namespace CropStage::CropStage(char const *name, Stage *upstream, Config const &config, int struct_offset) : BasicStage(name, upstream->GetPipeline(), upstream, struct_offset), config_(config) { } Length2 CropStage::GetOutputImageSize() const { return Length2(config_.crop.x.length, config_.crop.y.length); } void CropStage::PushStartUp(int output_start, Dir dir) { PISP_LOG(debug, "(" << name_ << ") Enter with output_start " << output_start); int input_start = output_start + config_.crop[dir].offset; // input_start can never be negative here, but it is possible to have output_start // negative if, for example, a branch starts producing output on the second // tile in a row (or column) and the resampler requires left (or top) context pixels. if (input_start < 0) throw std::runtime_error("input start is negative: " + std::to_string(input_start)); output_interval_.offset = output_start; input_interval_.offset = input_start; PISP_LOG(debug, "(" << name_ << ") Exit with input_start " << input_start); upstream_->PushStartUp(input_start, dir); } int CropStage::PushEndDown(int input_end, Dir dir) { PISP_LOG(debug, "(" << name_ << ") Enter with input_end " << input_end); int output_end = input_end - config_.crop[dir].offset; if (output_end > config_.crop[dir].length) output_end = config_.crop[dir].length; output_interval_.SetEnd(output_end); // If this is the first tile to generate output, ensure we can make at least // min_tile_size of output pixels. If not, terminate iteration here and don't // go futher downstream. Defer the output for the next tile. // // output_end may also be negative if no output will be generated for this tile. if (!interval_valid(output_interval_, GetPipeline()->GetConfig().min_tile_size[dir])) { PISP_LOG(debug, "(" << name_ << ") Output branch not started or output too small, terminating"); BasicStage::Reset(); return 0; } input_interval_.SetEnd(input_end); PISP_LOG(debug, "(" << name_ << ") Exit with output_end " << output_end); PushEndUp(downstream_->PushEndDown(output_end, dir), dir); return input_interval_.End(); } void CropStage::PushEndUp(int output_end, Dir dir) { PISP_LOG(debug, "(" << name_ << ") Enter with output_end " << output_end); int input_end = output_end + config_.crop[dir].offset; input_interval_.SetEnd(input_end); output_interval_.SetEnd(output_end); // Same check as we do in PushEndDown(). if (!interval_valid(output_interval_, GetPipeline()->GetConfig().min_tile_size[dir])) { PISP_LOG(debug, "(" << name_ << ") Output branch not started or output too small, terminating"); BasicStage::Reset(); return; } PISP_LOG(debug, "(" << name_ << ") Exit with input_end " << input_end); } void CropStage::PushCropDown(Interval interval, Dir dir) { PISP_LOG(debug, "(" << name_ << ") Enter with interval " << interval); // Branch has not started producing output. Terminate the iteration here // and don't go futher downstream. if (!interval_valid(output_interval_, GetPipeline()->GetConfig().min_tile_size[dir])) { PISP_LOG(debug, "(" << name_ << ") Output branch not started or output too small, terminating"); BasicStage::Reset(); return; } PISP_ASSERT(interval > input_interval_); input_interval_ = interval; interval.offset -= config_.crop[dir].offset; crop_ = interval - output_interval_; PISP_LOG(debug, "(" << name_ << ") Exit with interval " << output_interval_); downstream_->PushCropDown(output_interval_, dir); } bool CropStage::GetBranchInactive() const { return !output_interval_.length; } raspberrypi-libpisp-9ba67e6/src/libpisp/backend/tiling/crop_stage.hpp000066400000000000000000000015051507172066400261170ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (C) 2021 - 2023, Raspberry Pi Ltd * * crop_stage.hpp - Tiling library component for crop stages */ #pragma once #include "stages.hpp" namespace tiling { class CropStage : public BasicStage { public: struct Config { Config(Interval2 const &_crop) : crop(_crop) {} Interval2 crop; }; CropStage(char const *name, Stage *upstream, Config const &config, int struct_offset); virtual Length2 GetOutputImageSize() const override; virtual void PushStartUp(int output_start, Dir dir) override; virtual int PushEndDown(int input_end, Dir dir) override; virtual void PushEndUp(int output_end, Dir dir) override; virtual void PushCropDown(Interval interval, Dir dir) override; bool GetBranchInactive() const override; private: Config config_; }; } // namespace tilingraspberrypi-libpisp-9ba67e6/src/libpisp/backend/tiling/input_stage.cpp000066400000000000000000000067001507172066400263100ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (C) 2021 - 2023, Raspberry Pi Ltd * * input_stage.cpp - Tiling library component for input stages */ #include "input_stage.hpp" #include "common/logging.hpp" #include "pipeline.hpp" using namespace tiling; InputStage::InputStage(char const *name, Pipeline *pipeline, Config const &config, int struct_offset) : BasicStage(name, pipeline, nullptr, struct_offset), config_(config) { pipeline->AddInputStage(this); // If the compression requires more alignment than the basic X alignment, just bump the X alignment up. PISP_ASSERT(config_.compression_alignment == 0 || // (assume one alignment is a multiple of the other to make life easy!) config_.alignment.dx % config_.compression_alignment == 0 || config_.compression_alignment % config_.alignment.dx == 0); config_.alignment.dx = std::max(config_.alignment.dx, config_.compression_alignment); } Length2 InputStage::GetInputImageSize() const { return config_.input_image_size; } Interval InputStage::GetInputInterval() const { return input_interval_; } void InputStage::PushStartUp(int output_start, Dir dir) { PISP_LOG(debug, "(" << name_ << ") Enter with output_start " << output_start); // We may have to read a more aligned value than we were given. output_interval_.offset = output_start; input_interval_.offset = output_start - output_start % config_.alignment[dir]; PISP_LOG(debug, "(" << name_ << ") Exit with input_start " << input_interval_.offset); } int InputStage::PushEndDown(int input_end, Dir dir) { PISP_LOG(debug, "(" << name_ << ") Enter with input_end " << input_end); if (input_end >= GetInputImageSize()[dir]) input_end = GetInputImageSize()[dir]; else { // We don't get given pixels which we crop off as we go through the block, there's no // sense in our input containing any unnecessary pixels so we adjust our input_interval // directly. Watch out for the end-of-image case when we must write whatever we have. input_end -= input_end % config_.alignment[dir]; } input_interval_.SetEnd(input_end); output_interval_.SetEnd(input_end); PISP_LOG(debug, "(" << name_ << ") Exit with output_end " << input_end); PushEndUp(downstream_->PushEndDown(input_end, dir), dir); return input_interval_.End(); } void InputStage::PushEndUp(int output_end, Dir dir) { PISP_LOG(debug, "(" << name_ << ") Enter with output_end " << output_end); int align = config_.alignment[dir]; int input_end = ((output_end + align - 1) / align) * align; if (input_end > GetInputImageSize()[dir]) { input_end = GetInputImageSize()[dir]; // When compressed, we must always read a full compressed block, even if this extends beyond the nominal image width. if (dir == Dir::X && config_.compression_alignment) input_end = ((input_end + config_.compression_alignment - 1) / config_.compression_alignment) * config_.compression_alignment; } output_interval_.SetEnd(output_end); input_interval_.SetEnd(input_end); PISP_LOG(debug, "(" << name_ << ") Exit with input_end " << input_interval_.End()); } void InputStage::PushCropDown(Interval interval, Dir dir) { PISP_LOG(debug, "(" << name_ << ") Enter with interval " << interval); // As we're at the head of the pipeline, no one should be trying to give us extra pixels. PISP_ASSERT(interval == input_interval_); crop_ = Crop(0, 0); output_interval_ = interval; PISP_LOG(debug, "(" << name_ << ") Exit with interval " << interval); downstream_->PushCropDown(interval, dir); } raspberrypi-libpisp-9ba67e6/src/libpisp/backend/tiling/input_stage.hpp000066400000000000000000000020071507172066400263110ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (C) 2021 - 2023, Raspberry Pi Ltd * * input_stage.hpp - Tiling library component for input stages */ #pragma once #include "stages.hpp" namespace tiling { class InputStage : public BasicStage { public: struct Config { Config(Length2 const &_input_image_size, Length2 const &_alignment, int _compression_alignment = 0) : input_image_size(_input_image_size), alignment(_alignment), compression_alignment(_compression_alignment) { } Length2 input_image_size; Length2 alignment; int compression_alignment; }; InputStage(char const *name, Pipeline *pipeline, Config const &config, int struct_offset); virtual Length2 GetInputImageSize() const; virtual Interval GetInputInterval() const; virtual void PushStartUp(int output_start, Dir dir); virtual int PushEndDown(int input_end, Dir dir); virtual void PushEndUp(int output_end, Dir dir); virtual void PushCropDown(Interval interval, Dir dir); private: Config config_; }; } // namespace tilingraspberrypi-libpisp-9ba67e6/src/libpisp/backend/tiling/meson.build000066400000000000000000000007521507172066400254250ustar00rootroot00000000000000# SPDX-License-Identifier: CC0-1.0 # Copyright (C) 2023, Raspberry Pi Ltd backend_sources += files([ 'context_stage.cpp', 'crop_stage.cpp', 'input_stage.cpp', 'output_stage.cpp', 'pipeline.cpp', 'pisp_tiling.cpp', 'rescale_stage.cpp', 'split_stage.cpp', 'stages.cpp', ]) tiling_headers = files([ 'pisp_tiling.hpp', 'types.hpp', ]) tiling_include_dir = backend_include_dir / 'tiling' install_headers(tiling_headers, subdir: tiling_include_dir) raspberrypi-libpisp-9ba67e6/src/libpisp/backend/tiling/output_stage.cpp000066400000000000000000000112321507172066400265050ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (C) 2021 - 2023, Raspberry Pi Ltd * * output_stage.cpp - Tiling library component for output stages */ #include "output_stage.hpp" #include "common/logging.hpp" #include "pipeline.hpp" using namespace tiling; // There's a rather crucial convention here that when the output image is flipped, we use // a coordinate system to describe it starting from the RH edge of the image travelling left. // This means that tile coordinates don't change, it's the coordinate system that did. The // upshot is that not very much changes when things are flipped, we just have to be careful // that alignment applies not to the given tile offsets/lengths (which are now working from // the right to left), but only when we subtract them from the image width (effectively now // the offset/length from the LH edge). OutputStage::OutputStage(char const *name, Stage *upstream, Config const &config, int struct_offset) : BasicStage(name, upstream->GetPipeline(), upstream, struct_offset), config_(config), branch_complete_(false) { pipeline_->AddOutputStage(this); } Interval OutputStage::GetOutputInterval() const { return output_interval_; } void OutputStage::PushStartUp(int output_start, Dir dir) { PISP_LOG(debug, "(" << name_ << ") Enter with output_start " << output_start); output_interval_.offset = input_interval_.offset = output_start; PISP_LOG(debug, "(" << name_ << ") Exit with input_start " << input_interval_.offset); upstream_->PushStartUp(input_interval_.offset, dir); } static int align_end(int input_end, int image_size, int align, bool mirrored) { int output_end = input_end, aligned_output_end = output_end; if (mirrored) { // It's the end in the unflipped coordinate space that must align. int unflipped_end = image_size - input_end; unflipped_end = ((unflipped_end + align - 1) / align) * align; aligned_output_end = image_size - unflipped_end; } else { if (input_end < image_size) aligned_output_end = output_end - (input_end % align); } return aligned_output_end; } int OutputStage::PushEndDown(int input_end, Dir dir) { PISP_LOG(debug, "(" << name_ << ") Enter with input_end " << input_end); int output_end = input_end; int image_size = GetInputImageSize()[dir]; bool mirrored = (dir == Dir::X && config_.x_mirrored); int aligned_output_end = align_end(output_end, image_size, config_.max_alignment[dir], mirrored); if (aligned_output_end >= output_interval_.offset + config_.max_alignment[dir]) output_end = aligned_output_end; else { aligned_output_end = align_end(output_end, image_size, config_.min_alignment[dir], mirrored); if (aligned_output_end > output_interval_.offset) { output_end = aligned_output_end; PISP_LOG(debug, "(" << name_ << ") Unable to achieve optimal alignment " << config_.max_alignment[dir]); } else if (input_interval_.offset < image_size) { // test against size in case this branch already finished PISP_LOG(warning, "(" << name_ << ") Unable to achieve mandatory alignment " << config_.min_alignment[dir]); output_end = aligned_output_end; // Just because this output can't make progress, the other branch may - at which point this // branch may then succeed again. So this isn't necessarily fatal. Let the split stage decide // if there was no progress at all. } } input_interval_.SetEnd(input_end); output_interval_.SetEnd(output_end); PISP_LOG(debug, "(" << name_ << ") Exit with output_end " << output_end); PushEndUp(output_end, dir); return input_interval_.End(); } void OutputStage::PushEndUp(int output_end, [[maybe_unused]] Dir dir) { PISP_LOG(debug, "(" << name_ << ") Enter with output_end " << output_end); // We should just get given back our own output value. PISP_ASSERT(output_end == output_interval_.End()); input_interval_.SetEnd(output_end); PISP_LOG(debug, "(" << name_ << ") Exit with input_end " << output_end); } void OutputStage::PushCropDown(Interval interval, [[maybe_unused]] Dir dir) { PISP_LOG(debug, "(" << name_ << ") Enter with interval " << interval); // We can't push crop any further down, it has to be trimmed here. input_interval_ = interval; crop_ = interval - output_interval_; PISP_ASSERT(crop_.start >= 0 && crop_.end >= 0); // Note that we don't flip our output interval when horizontally mirrored; we assume our caller expects to do that. PISP_LOG(debug, "(" << name_ << ") Exit with interval " << output_interval_); } void OutputStage::Reset() { BasicStage::Reset(); branch_complete_ = false; } bool OutputStage::GetBranchComplete() const { return branch_complete_; } void OutputStage::SetBranchComplete() { branch_complete_ = true; PISP_LOG(debug, "(" << name_ << ") Setting branch complete"); } raspberrypi-libpisp-9ba67e6/src/libpisp/backend/tiling/output_stage.hpp000066400000000000000000000020361507172066400265140ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (C) 2021 - 2023, Raspberry Pi Ltd * * output_stage.hpp - Tiling library component for output stages */ #pragma once #include "stages.hpp" namespace tiling { class OutputStage : public BasicStage { public: struct Config { Config(Length2 const &_max_alignment, Length2 const &_min_alignment, bool _x_mirrored) : max_alignment(_max_alignment), min_alignment(_min_alignment), x_mirrored(_x_mirrored) { } Length2 max_alignment; Length2 min_alignment; bool x_mirrored; }; OutputStage(char const *name, Stage *upstream, Config const &config, int struct_offset); virtual Interval GetOutputInterval() const; virtual void PushStartUp(int output_start, Dir dir); virtual int PushEndDown(int input_end, Dir dir); virtual void PushEndUp(int output_end, Dir dir); virtual void PushCropDown(Interval interval, Dir dir); virtual void Reset(); bool GetBranchComplete() const; void SetBranchComplete(); private: Config config_; bool branch_complete_; }; } // namespace tilingraspberrypi-libpisp-9ba67e6/src/libpisp/backend/tiling/pipeline.cpp000066400000000000000000000053221507172066400255720ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (C) 2021 - 2023, Raspberry Pi Ltd * * pipeline.cpp - Tiling library pipeline generator */ #include "pipeline.hpp" #include "common/logging.hpp" #include "input_stage.hpp" #include "output_stage.hpp" #include "stages.hpp" #include using namespace tiling; Pipeline::Pipeline(char const *name, Config const &config) : name_(name), config_(config), first_tile_(false) { } Pipeline::Config const &Pipeline::GetConfig() const { return config_; } void Pipeline::AddStage(Stage *stage) { stages_.push_back(stage); } void Pipeline::AddInputStage(InputStage *input_stage) { inputs_.push_back(input_stage); } void Pipeline::AddOutputStage(OutputStage *output_stage) { outputs_.push_back(output_stage); } void Pipeline::Tile(void *mem, size_t num_items, size_t item_size, Length2 *grid) { // Tiling produces a rectangular X by Y grid. We create X direction tiles along the first row, // then Y direction tiles down the column. Finally we copy the X/Y information to all the other // tiles in the grid. grid->dx = tileDirection(Dir::X, mem, num_items, item_size); grid->dy = tileDirection(Dir::Y, mem, num_items / grid->dx, item_size * grid->dx); int i, j; for (j = 0; j < grid->dy; j++) { void *y_src = (uint8_t *)mem + item_size * grid->dx * j; for (i = 0; i < grid->dx; i++) { void *x_src = (uint8_t *)mem + item_size * i; void *dest = (uint8_t *)y_src + item_size * i; for (auto s : stages_) s->MergeRegions(dest, x_src, y_src); } } } void Pipeline::reset() { for (auto s : stages_) s->Reset(); } int Pipeline::tileDirection(Dir dir, void *mem, size_t num_items, size_t item_size) { PISP_LOG(debug, "Tiling direction " << dir); reset(); bool done = false; unsigned int num_tiles = 0; first_tile_ = true; for (; !done; num_tiles++) { PISP_LOG(debug, "----------------------------------------------------------------"); if (num_tiles == num_items) throw std::runtime_error("Too many tiles!"); for (auto s : outputs_) { if (!s->GetBranchComplete()) s->PushStartUp(s->GetOutputInterval().End(), dir); } for (auto s : inputs_) s->PushEndDown(s->GetInputInterval().offset + config_.max_tile_size[dir], dir); for (auto s : inputs_) s->PushCropDown(s->GetInputInterval(), dir); void *dest = (uint8_t *)mem + num_tiles * item_size; for (auto s : stages_) s->CopyOut(dest, dir); done = true; for (auto s : outputs_) { if (s->GetBranchComplete()) continue; else if (s->GetOutputInterval().End() >= s->GetOutputImageSize()[dir]) s->SetBranchComplete(); else done = false; } first_tile_ = false; } PISP_LOG(debug, "Made " << num_tiles << " tiles in direction " << dir); return num_tiles; } raspberrypi-libpisp-9ba67e6/src/libpisp/backend/tiling/pipeline.hpp000066400000000000000000000022071507172066400255760ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (C) 2021 - 2023, Raspberry Pi Ltd * * pipeline.hpp - Tiling library pipeline generator */ #pragma once #include #include #include "types.hpp" namespace tiling { class Stage; class InputStage; class OutputStage; class Pipeline { public: struct Config { Config(Length2 const &_max_tile_size, Length2 const &_min_tile_size) : max_tile_size(_max_tile_size), min_tile_size(_min_tile_size) { } Length2 max_tile_size; Length2 min_tile_size; }; Pipeline(char const *name, Config const &config); Config const &GetConfig() const; void AddStage(Stage *stage); void AddInputStage(InputStage *input_stage); void AddOutputStage(OutputStage *output_stage); void Tile(void *mem, size_t num_items, size_t item_size, Length2 *grid); bool FirstTile() const { return first_tile_; } private: int tileDirection(Dir dir, void *mem, size_t num_items, size_t item_size); void reset(); std::string name_; Config config_; std::vector stages_; std::vector inputs_; std::vector outputs_; bool first_tile_; }; } // namespace tilingraspberrypi-libpisp-9ba67e6/src/libpisp/backend/tiling/pisp_tiling.cpp000066400000000000000000000107041507172066400263060ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (C) 2021 - 2023, Raspberry Pi Ltd * * pisp_tiling.cpp - Tiling library top level */ #include "pisp_tiling.hpp" #include #include "common/logging.hpp" #include "context_stage.hpp" #include "crop_stage.hpp" #include "input_stage.hpp" #include "output_stage.hpp" #include "pipeline.hpp" #include "rescale_stage.hpp" #include "split_stage.hpp" using namespace tiling; namespace { constexpr unsigned int PipelineContextX = 16; constexpr unsigned int PipelineContextY = 16; constexpr unsigned int PipelineAlignX = 2; constexpr unsigned int PipelineAlignY = 2; constexpr unsigned int CompressionAlign = 8; // Resampling parameters constexpr unsigned int StartContext = 2; constexpr unsigned int EndContext = 3; constexpr unsigned int ScalePrecision = 12; constexpr unsigned int RoundUp = (1 << ScalePrecision) - 1; } // namespace namespace libpisp { void tile_pipeline(TilingConfig const &config, Tile *tiles, int num_tiles, Length2 *grid) { PISP_LOG(info, config); // First set up the pipeline. Pipeline::Config pipeline_config(config.max_tile_size, config.min_tile_size); Pipeline pipeline("PiSP", pipeline_config); InputStage::Config input_config(config.input_image_size, config.input_alignment, config.compressed_input ? CompressionAlign : 0); InputStage input_stage("input", &pipeline, input_config, offsetof(Tile, input)); ContextStage::Config context_config(Crop2(Crop(PipelineContextX), Crop(PipelineContextY)), Length2(PipelineAlignX, PipelineAlignY)); ContextStage context_stage("context", &input_stage, context_config, offsetof(Tile, context)); SplitStage split_stage("split", &context_stage); std::unique_ptr crop_stages[NumOutputBranches]; std::unique_ptr downscale_stages[NumOutputBranches]; std::unique_ptr resample_stages[NumOutputBranches]; std::unique_ptr output_stages[NumOutputBranches]; for (int i = 0; i < NumOutputBranches; i++) { Length2 const &output_image_size = config.output_image_size[i]; // A zero-dimension output disables that branch. if (output_image_size.dx == 0 || output_image_size.dy == 0) continue; char name[32]; Stage *prev_stage = &split_stage; sprintf(name, "crop%d", i); crop_stages[i] = std::unique_ptr( new CropStage(name, prev_stage, config.crop[i], offsetof(Tile, crop) + i * sizeof(Region))); prev_stage = crop_stages[i].get(); // There's a little awkwardness if the resize blocks (downscale and resample) are not enabled. Resize *does* change the output tile // size, even if it's doing a 1-to-1 scaling (it loses context), so we must leave it out of the tiling // calculation. if (config.downscale_enables & (1 << i)) { sprintf(name, "downscale%d", i); Length2 const &downscale_image_size = config.downscale_image_size[i]; // There is only right context here - and it is the scale factor rounded up. Length2 context_right = Length2(((config.downscale_factor[i][Dir::X] + RoundUp) >> ScalePrecision) - 1, ((config.downscale_factor[i][Dir::Y] + RoundUp) >> ScalePrecision) - 1); RescaleStage::Config downscale_config(downscale_image_size, config.downscale_factor[i], Length2(0, 0), context_right, ScalePrecision, RescaleStage::RescalerType::Downscaler); downscale_stages[i] = std::unique_ptr( new RescaleStage(name, prev_stage, downscale_config, offsetof(Tile, downscale) + i * sizeof(Region))); prev_stage = downscale_stages[i].get(); } if (config.resample_enables & (1 << i)) { sprintf(name, "resample%d", i); RescaleStage::Config resample_config(output_image_size, config.resample_factor[i], Length2(StartContext, StartContext), Length2(EndContext, EndContext), ScalePrecision, RescaleStage::RescalerType::Resampler); resample_stages[i] = std::unique_ptr( new RescaleStage(name, prev_stage, resample_config, offsetof(Tile, resample) + i * sizeof(Region))); prev_stage = resample_stages[i].get(); } sprintf(name, "output%d", i); OutputStage::Config output_config(config.output_max_alignment[i], config.output_min_alignment[i], config.output_h_mirror[i]); output_stages[i] = std::unique_ptr( new OutputStage(name, prev_stage, output_config, offsetof(Tile, output) + i * sizeof(Region))); } // Now tile it. pipeline.Tile(tiles, num_tiles, sizeof(Tile), grid); PISP_LOG(info, "Made " << grid->dx << "x" << grid->dy << " tiles"); } } // namespace libpisp raspberrypi-libpisp-9ba67e6/src/libpisp/backend/tiling/pisp_tiling.hpp000066400000000000000000000044171507172066400263170ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (C) 2021 - 2023, Raspberry Pi Ltd * * pisp_tiling.hpp - Tiling library top level */ #pragma once // Some data structures and an API function to make it easier to tile up and image for a PiSP-like pipeline. #include #include "types.hpp" namespace libpisp { constexpr int NumOutputBranches = 2; struct Tile { tiling::Region input; tiling::Region decompress; tiling::Region context; tiling::Region crop[NumOutputBranches]; tiling::Region downscale[NumOutputBranches]; tiling::Region resample[NumOutputBranches]; tiling::Region output[NumOutputBranches]; }; struct TilingConfig { tiling::Length2 input_image_size; tiling::Interval2 crop[NumOutputBranches]; tiling::Length2 downscale_image_size[NumOutputBranches]; tiling::Length2 output_image_size[NumOutputBranches]; tiling::Length2 max_tile_size; tiling::Length2 min_tile_size; tiling::Length2 downscale_factor[NumOutputBranches]; tiling::Length2 resample_factor[NumOutputBranches]; bool output_h_mirror[NumOutputBranches]; int resample_enables; int downscale_enables; bool compressed_input; tiling::Length2 input_alignment; tiling::Length2 output_max_alignment[NumOutputBranches]; // "preferred" alignment tiling::Length2 output_min_alignment[NumOutputBranches]; // "required" minimum alignment }; inline std::ostream &operator<<(std::ostream &os, TilingConfig const &tc) { os << "TilingConfig:" << std::endl; os << "\tinput_image_size " << tc.input_image_size << " align " << tc.input_alignment << std::endl; for (int i = 0; i < NumOutputBranches; i++) { os << "\tcrop[" << i << "] " << tc.crop[i] << std::endl; os << "\toutput_image_size[" << i << "] " << tc.output_image_size[i] << " align max " << tc.output_max_alignment[i] << " min " << tc.output_min_alignment[i] << std::endl; os << "\tdownscale_image_size " << tc.downscale_image_size[i] << " downscale_factor " << tc.downscale_factor[i] << " resample_factor " << tc.resample_factor[i] << std::endl; } os << "\tenables resample " << tc.resample_enables << " downscale " << tc.downscale_enables << std::endl; return os << "\tmax_tile_size " << tc.max_tile_size; } void tile_pipeline(TilingConfig const &config, Tile *tile, int num_tile, tiling::Length2 *grid); } // namespace libpisp raspberrypi-libpisp-9ba67e6/src/libpisp/backend/tiling/rescale_stage.cpp000066400000000000000000000144001507172066400265630ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (C) 2021 - 2023, Raspberry Pi Ltd * * rescale_stage.cpp - Tiling library component for rescaling stages */ #include "rescale_stage.hpp" #include "common/logging.hpp" #include "pipeline.hpp" using namespace tiling; RescaleStage::RescaleStage(char const *name, Stage *upstream, Config const &config, int struct_offset) : BasicStage(name, upstream->GetPipeline(), upstream, struct_offset), config_(config) { round_up = (1 << config.precision) - 1; } Length2 RescaleStage::GetOutputImageSize() const { return config_.output_image_size; } // In what follows, the suffix _P indicates that a variable is a fixed point value,left-shifted by PRECISION. // We also use _w_context and _no_context to denote input coordinates where, respectively, the context pixels // required by the resample filter either are or are not included. void RescaleStage::PushStartUp(int output_start, Dir dir) { PISP_LOG(debug, "(" << name_ << ") Enter with output_start " << output_start); int input_start_P = output_start * config_.scale[dir]; int input_start = input_start_P >> config_.precision; int input_start_w_context = input_start - config_.start_context[dir]; // input_start_w_context is allowed to go negative here if, for example, the branch starts producing output // on the second tile in a row/column and the resampler requires left context pixels. In such cases, the left/top // edge flag will not be set on the tile and the hardware cannot remove the left/top context pixels. So allow // negative values on all but the left/top edge tile. if (GetPipeline()->FirstTile() && input_start_w_context < 0) input_start_w_context = 0; output_interval_.offset = output_start; input_interval_.offset = input_start_w_context; PISP_LOG(debug, "(" << name_ << ") Exit with input_start " << input_start_w_context); upstream_->PushStartUp(input_start_w_context, dir); } // Furthermore, when we're talking about end pixels we need to be careful whether we're talking inclusively // or exclusively. For example, the End() pixel of an interval is an exclusive pixel number, so End()-1 is // actually the last pixel still in the interval. We'll denote these with the suffices _exc or _inc. int RescaleStage::PushEndDown(int input_end, Dir dir) { PISP_LOG(debug, "(" << name_ << ") Enter with input_end " << input_end); int input_image_size = GetInputImageSize()[dir]; input_interval_.SetEnd(input_end); int output_end_exc; if (config_.rescaler_type == RescalerType::Downscaler) { // Trapezoidal downscaler has a variable-sized kernel. Round down its end // position to get the number of complete output samples that can be generated // (provided scale was rounded down, there should be no shortage of input). output_end_exc = (input_end << config_.precision) / config_.scale[dir]; } else { // Resampler: find the last ("inclusive") sample that can be generated. // Take off context plus an extra 2 pixels off to allow for an initial phase // (except that at the bottom of the image, no more context is available). int input_end_inc = input_end - 1; int input_end_inc_no_context = input_end_inc; if (input_end < input_image_size) { input_end_inc_no_context = input_end_inc - config_.end_context[dir] - 2; } int input_end_inc_no_context_P = input_end_inc_no_context << config_.precision; int output_end_inc = (input_end_inc_no_context_P + round_up) / config_.scale[dir]; output_end_exc = output_end_inc + 1; } if (output_end_exc > config_.output_image_size[dir]) output_end_exc = config_.output_image_size[dir]; // Upscaling could generate larger output tiles than we can handle, so avoid doing that! if (output_end_exc > output_interval_.offset + GetPipeline()->GetConfig().max_tile_size[dir]) output_end_exc = GetPipeline()->GetConfig().max_tile_size[dir] + output_interval_.offset; output_interval_.SetEnd(output_end_exc); PISP_LOG(debug, "(" << name_ << ") Exit with output_end " << output_end_exc); PushEndUp(downstream_->PushEndDown(output_end_exc, dir), dir); // If we didn't quite finish the output then we can't get too close to the end of the input because another tile will be // needed, and we can't let that become infeasibly small. So pull our input_end back and simply try again. if (output_interval_.End() < config_.output_image_size[dir] && input_interval_.End() > input_image_size - GetPipeline()->GetConfig().min_tile_size[dir]) { PISP_LOG(debug, "(" << name_ << ") Too close to input image edge - try again"); PushEndDown(input_image_size - GetPipeline()->GetConfig().min_tile_size[dir], dir); } return input_interval_.End(); } void RescaleStage::PushEndUp(int output_end, Dir dir) { PISP_LOG(debug, "(" << name_ << ") Enter with output_end " << output_end); int input_end_w_context_exc; if (config_.rescaler_type == RescalerType::Downscaler) { // Trapezoidal downscaler has a variable-sized kernel. Round up its fractional end position. int input_end_exc_P = output_end * config_.scale[dir]; input_end_w_context_exc = (input_end_exc_P + round_up) >> config_.precision; } else { // Resampler has a fixed-sized context, so calculations are based on the start position // for its final output sample ("inclusive" dimensions). Need 2 more pixels to the end // to allow for an initial phase that pushes us to use up to 2 extra samples on the right. int output_end_inc = output_end - 1; int input_end_P = output_end_inc * config_.scale[dir]; int input_end = input_end_P >> config_.precision; int input_end_w_context = input_end + config_.end_context[dir] + 2; input_end_w_context_exc = input_end_w_context + 1; } Length2 input_image_size(GetInputImageSize()); if (input_end_w_context_exc > input_image_size[dir]) input_end_w_context_exc = input_image_size[dir]; output_interval_.SetEnd(output_end); input_interval_.SetEnd(input_end_w_context_exc); PISP_LOG(debug, "(" << name_ << ") Exit with input_end " << input_end_w_context_exc); } void RescaleStage::PushCropDown(Interval interval, Dir dir) { PISP_LOG(debug, "(" << name_ << ") Enter with interval " << interval); PISP_ASSERT(interval > input_interval_); crop_ = interval - input_interval_; input_interval_ = interval; PISP_LOG(debug, "(" << name_ << ") Exit with interval " << output_interval_); downstream_->PushCropDown(output_interval_, dir); } raspberrypi-libpisp-9ba67e6/src/libpisp/backend/tiling/rescale_stage.hpp000066400000000000000000000024251507172066400265740ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (C) 2021 - 2023, Raspberry Pi Ltd * * rescale_stage.hpp - Tiling library component for rescaling stages */ #pragma once #include "stages.hpp" #include namespace tiling { class RescaleStage : public BasicStage { public: enum class RescalerType { Downscaler, Resampler }; struct Config { Config(Length2 const &_output_image_size, Length2 const &_scale, Length2 const &_start_context, Length2 const &_end_context, uint8_t _precision, RescalerType rescaler_type_) : output_image_size(_output_image_size), scale(_scale), start_context(_start_context), end_context(_end_context), precision(_precision), rescaler_type(rescaler_type_) { } Length2 output_image_size; Length2 scale; Length2 start_context; Length2 end_context; uint8_t precision; RescalerType rescaler_type; }; RescaleStage(char const *name, Stage *upstream, Config const &config, int struct_offset); virtual Length2 GetOutputImageSize() const; virtual void PushStartUp(int output_start, Dir dir); virtual int PushEndDown(int input_end, Dir dir); virtual void PushEndUp(int output_end, Dir dir); virtual void PushCropDown(Interval interval, Dir dir); private: Config config_; int round_up; }; } // namespace tiling raspberrypi-libpisp-9ba67e6/src/libpisp/backend/tiling/split_stage.cpp000066400000000000000000000076171507172066400263140ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (C) 2021 - 2023, Raspberry Pi Ltd * * split_stage.cpp - Tiling library component for splitter stages */ #include "split_stage.hpp" #include "common/logging.hpp" #include "pipeline.hpp" using namespace tiling; SplitStage::SplitStage(char const *name, Stage *upstream) : Stage(name, upstream->GetPipeline(), -1), upstream_(upstream) { upstream->SetDownstream(this); } Length2 SplitStage::GetInputImageSize() const { return upstream_->GetOutputImageSize(); } Length2 SplitStage::GetOutputImageSize() const { return GetInputImageSize(); } void SplitStage::SetDownstream(Stage *stage) { downstream_.push_back(stage); } void SplitStage::Reset() { input_interval_ = Interval(0, 0); count_ = 0; } void SplitStage::PushStartUp(int output_start, Dir dir) { PISP_LOG(debug, "(" << name_ << ") Enter with output_start " << output_start); // We must wait till all the downstream branches have given us their number, then we send the leftmost // one up the pipeline. if (count_++ == 0) input_interval_ = Interval(output_start); else input_interval_ |= output_start; unsigned int branch_incomplete_count = 0; for (auto const &d : downstream_) if (!d->GetBranchComplete()) branch_incomplete_count++; if (count_ == branch_incomplete_count) { count_ = 0; PISP_LOG(debug, "(" << name_ << ") Exit - call PushStartUp with " << input_interval_.offset); upstream_->PushStartUp(input_interval_.offset, dir); } } int SplitStage::PushEndDown(int input_end, Dir dir) { PISP_LOG(debug, "(" << name_ << ") Enter with input_end " << input_end); // First tell all the branches what the maximum number of pixels is that they can have so that we find // out what they can do with it. Then remember the least far-on end position that they need. This avoid // potential over-read if one branch can only accept way fewer pixels than another. input_interval_.SetEnd(0); for (auto d : downstream_) { if (d->GetBranchComplete()) continue; int branch_end = d->PushEndDown(input_end, dir); // (It is OK for a branch to make no progress at all - so long as another branch does!) if (branch_end > input_interval_.End()) input_interval_.SetEnd(branch_end); } // Finally tell all the branches now what they will really get, which is that end point. PISP_LOG(debug, "(" << name_ << ") Split using input_end " << input_interval_.End()); if (input_interval_.length == 0) { PISP_LOG(fatal, "(" << name_ << ") Neither branch can make progress"); throw TilingException(); } for (auto d : downstream_) if (!d->GetBranchComplete()) d->PushEndDown(input_interval_.End(), dir); PushEndUp(input_interval_.End(), dir); return input_interval_.End(); } void SplitStage::PushEndUp([[maybe_unused]] int output_end, [[maybe_unused]] Dir dir) { PISP_LOG(debug, "(" << name_ << ") Enter with output_end " << output_end); // Genuinely nothing to do here, but we just like to log the usual trace information for consistency. PISP_LOG(debug, "(" << name_ << ") Exit with input_end " << output_end); } void SplitStage::PushCropDown(Interval interval, Dir dir) { PISP_LOG(debug, "(" << name_ << ") Enter with interval " << interval); // Whatever we get goes down all the branches. If there are any that don't like it then they'll have to // start by cropping off what they can't handle. PISP_ASSERT(interval > input_interval_); input_interval_ = interval; for (auto d : downstream_) { if (d->GetBranchComplete()) continue; PISP_LOG(debug, "(" << name_ << ") Exit with interval " << interval); d->PushCropDown(interval, dir); } } void SplitStage::CopyOut([[maybe_unused]] void *dest, [[maybe_unused]] Dir dir) { } bool SplitStage::GetBranchComplete() const { bool done = true; for (auto d : downstream_) done &= d->GetBranchComplete(); return done; } bool SplitStage::GetBranchInactive() const { if (!upstream_) return false; return upstream_->GetBranchInactive(); } raspberrypi-libpisp-9ba67e6/src/libpisp/backend/tiling/split_stage.hpp000066400000000000000000000017071507172066400263130ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (C) 2021 - 2023, Raspberry Pi Ltd * * split_stage.hpp - Tiling library component for splitter stages */ #pragma once #include #include "stages.hpp" namespace tiling { class SplitStage : public Stage { public: SplitStage(char const *name, Stage *upstream); virtual Length2 GetInputImageSize() const; virtual Length2 GetOutputImageSize() const; virtual void SetDownstream(Stage *downstream); virtual void Reset(); virtual void PushStartUp(int output_start, Dir dir); virtual int PushEndDown(int input_end, Dir dir); virtual void PushEndUp(int output_end, Dir dir); virtual void PushCropDown(Interval interval, Dir dir); virtual void CopyOut(void *dest, Dir dir); virtual bool GetBranchComplete() const; virtual bool GetBranchInactive() const; private: Stage *upstream_; std::vector downstream_; Interval input_interval_; unsigned int count_; }; } // namespace tilingraspberrypi-libpisp-9ba67e6/src/libpisp/backend/tiling/stages.cpp000066400000000000000000000047611507172066400252610ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (C) 2021 - 2023, Raspberry Pi Ltd * * stages.cpp - Tiling library Stage class implementation */ #include "stages.hpp" #include "common/logging.hpp" #include "pipeline.hpp" #include using namespace tiling; Stage::Stage(char const *name, Pipeline *pipeline, int struct_offset) : name_(name), pipeline_(pipeline), struct_offset_(struct_offset) { if (pipeline) pipeline->AddStage(this); } Pipeline *Stage::GetPipeline() const { return pipeline_; } void Stage::MergeRegions(void *dest, void *x_src, void *y_src) const { if (struct_offset_ >= 0) // Hacky test to exclude stages that don't store a Region in each Tile { Region *dest_region = (Region *)((int8_t *)dest + struct_offset_); Region *x_src_region = (Region *)((int8_t *)x_src + struct_offset_); Region *y_src_region = (Region *)((int8_t *)y_src + struct_offset_); dest_region->input[Dir::X] = x_src_region->input[Dir::X]; dest_region->crop[Dir::X] = x_src_region->crop[Dir::X]; dest_region->output[Dir::X] = x_src_region->output[Dir::X]; dest_region->input[Dir::Y] = y_src_region->input[Dir::Y]; dest_region->crop[Dir::Y] = y_src_region->crop[Dir::Y]; dest_region->output[Dir::Y] = y_src_region->output[Dir::Y]; } } BasicStage::BasicStage(char const *name, Pipeline *pipeline, Stage *upstream, int struct_offset) : Stage(name, pipeline, struct_offset), upstream_(upstream), downstream_(nullptr) { if (upstream) upstream->SetDownstream(this); } Length2 BasicStage::GetInputImageSize() const { return upstream_->GetOutputImageSize(); } Length2 BasicStage::GetOutputImageSize() const { return GetInputImageSize(); } void BasicStage::SetDownstream(Stage *downstream) { downstream_ = downstream; } void BasicStage::Reset() { input_interval_ = Interval(0, 0); crop_ = Crop(0, 0); output_interval_ = Interval(0, 0); } void BasicStage::CopyOut(void *dest, Dir dir) { if (struct_offset_ >= 0) { Region *region = (Region *)((uint8_t *)dest + struct_offset_); PISP_LOG(debug, "(" << name_ << ") complete: " << GetBranchComplete() << " inactive: " << GetBranchInactive()); if (GetBranchComplete() || GetBranchInactive()) BasicStage::Reset(); region->input[dir] = input_interval_; region->crop[dir] = crop_; region->output[dir] = output_interval_; } } bool BasicStage::GetBranchComplete() const { return downstream_->GetBranchComplete(); } bool BasicStage::GetBranchInactive() const { if (!upstream_) return false; return upstream_->GetBranchInactive(); } raspberrypi-libpisp-9ba67e6/src/libpisp/backend/tiling/stages.hpp000066400000000000000000000035001507172066400252540ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (C) 2021 - 2023, Raspberry Pi Ltd * * stage.cpp - Tiling library stage definition */ #pragma once #include #include "types.hpp" namespace tiling { class Pipeline; class TilingException : public std::exception { public: char const *what() const noexcept { return "Tiling Failed"; } }; class Stage { public: Stage(char const *name, Pipeline *pipeline, int struct_offset); virtual ~Stage() {} Pipeline *GetPipeline() const; virtual Length2 GetInputImageSize() const = 0; virtual Length2 GetOutputImageSize() const = 0; virtual void SetDownstream(Stage *downstream) = 0; virtual void Reset() = 0; virtual void PushStartUp(int output_start, Dir dir) = 0; virtual int PushEndDown(int input_end, Dir dir) = 0; virtual void PushEndUp(int output_end, Dir dir) = 0; virtual void PushCropDown(Interval interval, Dir dir) = 0; virtual void CopyOut(void *dest, Dir dir) = 0; virtual bool GetBranchComplete() const = 0; virtual bool GetBranchInactive() const = 0; void MergeRegions(void *dest, void *x_src, void *y_src) const; protected: std::string name_; Pipeline *pipeline_; int struct_offset_; }; // BasicStage is a Stage that has a single upstream and a single downstream stage (though they can be NULL). class BasicStage : public Stage { public: BasicStage(char const *name, Pipeline *pipeline, Stage *upstream, int struct_offset); virtual Length2 GetInputImageSize() const; virtual Length2 GetOutputImageSize() const; virtual void SetDownstream(Stage *downstream); virtual void Reset(); virtual void CopyOut(void *dest, Dir dir); virtual bool GetBranchComplete() const; virtual bool GetBranchInactive() const; protected: Stage *upstream_; Stage *downstream_; Interval input_interval_; Crop crop_; Interval output_interval_; }; } // namespace tilingraspberrypi-libpisp-9ba67e6/src/libpisp/backend/tiling/tiling.hpp000066400000000000000000000005541507172066400252620ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (C) 2021 - 2023, Raspberry Pi Ltd * * tiling.hpp - Tiling library headers */ #pragma once #include #include "context_stage.hpp" #include "crop_stage.hpp" #include "input_stage.hpp" #include "output_stage.hpp" #include "pipeline.hpp" #include "rescale_stage.hpp" #include "split_stage.hpp" raspberrypi-libpisp-9ba67e6/src/libpisp/backend/tiling/types.hpp000066400000000000000000000073761507172066400251510ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (C) 2021 - 2023, Raspberry Pi Ltd * * types.hpp - Tiling library type definitions */ #pragma once #include namespace tiling { enum class Dir { X = 0, Y = 1 }; inline std::ostream &operator<<(std::ostream &os, Dir dir) { return os << (dir == Dir::X ? "X" : "Y"); } struct Length2 { Length2() : Length2(0, 0) {} Length2(int _dx, int _dy) : dx(_dx), dy(_dy) {} explicit Length2(int len) : Length2(len, len) {} int dx, dy; int operator[](Dir dir) const { return dir == Dir::Y ? dy : dx; } Length2 operator-(Length2 const &other) { return Length2(dx - other.dx, dy - other.dy); } Length2 operator/(int num) { return Length2(dx / num, dy / num); } }; inline std::ostream &operator<<(std::ostream &os, Length2 const &l) { return os << "(" << l.dx << ", " << l.dy << ")"; } struct Crop { Crop() : Crop(0, 0) {} Crop(int _start, int _end) : start(_start), end(_end) {} explicit Crop(int crop) : Crop(crop, crop) {} int start, end; Crop operator+(Crop const &other) { return Crop(start + other.start, end + other.end); } }; inline std::ostream &operator<<(std::ostream &os, Crop const &c) { return os << ""; } struct Interval { Interval() : Interval(0, 0) {} Interval(int _offset, int _length) : offset(_offset), length(_length) {} explicit Interval(int _offset) : offset(_offset), length(0) {} int offset, length; int End() const { return offset + length; } void SetStart(int start) { length += (offset - start), offset = start; } // leave End unchanged void SetEnd(int end) { length = end - offset; } bool operator==(Interval const &other) { return offset == other.offset && length == other.length; } Interval operator-(Crop const &crop) const { return Interval(offset - crop.start, length - crop.start - crop.end); } Crop operator-(Interval const &other) { return Crop(other.offset - offset, End() - other.End()); } bool operator>(Interval const &other) { return offset <= other.offset && End() >= other.End(); } // contains bool operator<(Interval const &other) { return offset >= other.offset && End() <= other.End(); } // is contained Interval &operator|=(int off) { if (off < offset) SetStart(off); else if (off > End()) SetEnd(off); return *this; } }; inline std::ostream &operator<<(std::ostream &os, Interval const &i) { return os << "[off " << i.offset << " len " << i.length << "]"; } struct Crop2 { Crop2() {} Crop2(Crop const &_x, Crop const &_y) : x(_x), y(_y) {} Crop x, y; Crop operator[](Dir dir) const { return dir == Dir::Y ? y : x; } Crop &operator[](Dir dir) { return dir == Dir::Y ? y : x; } Crop2 operator+(Crop2 const &other) { return Crop2(x + other.x, y + other.y); } }; inline std::ostream &operator<<(std::ostream &os, Crop2 const &c) { return os << "< X " << c.x << " Y " << c.y << " >"; } struct Interval2 { Interval2() {} Interval2(Interval const &_x, Interval const &_y) : x(_x), y(_y) {} Interval2(Length2 const &offset, Length2 const &size) : x(offset.dx, size.dx), y(offset.dy, size.dy) {} Interval x, y; Interval operator[](Dir dir) const { return dir == Dir::Y ? y : x; } Interval &operator[](Dir dir) { return dir == Dir::Y ? y : x; } bool operator==(Interval2 const &other) { return x == other.x && y == other.y; } bool operator!=(Interval2 const &other) { return !(*this == other); } }; inline std::ostream &operator<<(std::ostream &os, Interval2 const &i) { return os << "[ X " << i.x << " Y " << i.y << " ]"; } struct Region { Interval2 input; Crop2 crop; Interval2 output; }; inline std::ostream &operator<<(std::ostream &os, Region const &r) { os << "\t{ input " << r.input << std::endl; os << "\t crop " << r.crop << std::endl; os << "\t output " << r.output << " }"; return os; } } // namespace tilingraspberrypi-libpisp-9ba67e6/src/libpisp/common/000077500000000000000000000000001507172066400216725ustar00rootroot00000000000000raspberrypi-libpisp-9ba67e6/src/libpisp/common/logging.hpp000066400000000000000000000012071507172066400240310ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (C) 2021 - 2023, Raspberry Pi Ltd * * logging.hpp - PiSP logging library */ #pragma once #include #ifndef PISP_LOGGING_ENABLE #define PISP_LOGGING_ENABLE 0 #endif #if PISP_LOGGING_ENABLE #include #define PISP_LOG(sev, stuff) \ do { \ if (PISP_LOGGING_ENABLE) \ BOOST_LOG_TRIVIAL(sev) << __FUNCTION__ << ": " << stuff; \ } while (0) #else #define PISP_LOG(sev, stuff) do { } while(0) #endif #define PISP_ASSERT(x) assert(x) namespace libpisp { // Call this before you try and use any logging. void logging_init(); } // namespace libpispraspberrypi-libpisp-9ba67e6/src/libpisp/common/meson.build000066400000000000000000000006241507172066400240360ustar00rootroot00000000000000# SPDX-License-Identifier: CC0-1.0 # Copyright (C) 2023, Raspberry Pi Ltd pisp_sources += files([ 'pisp_utils.cpp', 'pisp_logging.cpp', 'pisp_pwl.cpp', ]) common_headers = files([ 'pisp_common.h', 'logging.hpp', 'shm_mutex.hpp', 'utils.hpp', 'version.hpp', ]) common_include_dir = pisp_include_dir / 'common' install_headers(common_headers, subdir: common_include_dir) raspberrypi-libpisp-9ba67e6/src/libpisp/common/pisp_common.h000066400000000000000000000173301507172066400243720ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */ /* * RP1 PiSP common definitions. * * Copyright (C) 2021 - Raspberry Pi Ltd. * */ #ifndef _PISP_COMMON_H_ #define _PISP_COMMON_H_ #include struct pisp_image_format_config { /* size in pixels */ __u16 width; __u16 height; /* must match struct pisp_image_format below */ __u32 format; __s32 stride; /* some planar image formats will need a second stride */ __s32 stride2; } __attribute__((packed)); enum pisp_bayer_order { /* * Note how bayer_order&1 tells you if G is on the even pixels of the * checkerboard or not, and bayer_order&2 tells you if R is on the even * rows or is swapped with B. Note that if the top (of the 8) bits is * set, this denotes a monochrome or greyscale image, and the lower bits * should all be ignored. */ PISP_BAYER_ORDER_RGGB = 0, PISP_BAYER_ORDER_GBRG = 1, PISP_BAYER_ORDER_BGGR = 2, PISP_BAYER_ORDER_GRBG = 3, PISP_BAYER_ORDER_GREYSCALE = 128 }; enum pisp_image_format { /* * Precise values are mostly tbd. Generally these will be portmanteau * values comprising bit fields and flags. This format must be shared * throughout the PiSP. */ PISP_IMAGE_FORMAT_BPS_8 = 0x00000000, PISP_IMAGE_FORMAT_BPS_10 = 0x00000001, PISP_IMAGE_FORMAT_BPS_12 = 0x00000002, PISP_IMAGE_FORMAT_BPS_16 = 0x00000003, PISP_IMAGE_FORMAT_BPS_MASK = 0x00000003, PISP_IMAGE_FORMAT_PLANARITY_INTERLEAVED = 0x00000000, PISP_IMAGE_FORMAT_PLANARITY_SEMI_PLANAR = 0x00000010, PISP_IMAGE_FORMAT_PLANARITY_PLANAR = 0x00000020, PISP_IMAGE_FORMAT_PLANARITY_MASK = 0x00000030, PISP_IMAGE_FORMAT_SAMPLING_444 = 0x00000000, PISP_IMAGE_FORMAT_SAMPLING_422 = 0x00000100, PISP_IMAGE_FORMAT_SAMPLING_420 = 0x00000200, PISP_IMAGE_FORMAT_SAMPLING_MASK = 0x00000300, PISP_IMAGE_FORMAT_ORDER_NORMAL = 0x00000000, PISP_IMAGE_FORMAT_ORDER_SWAPPED = 0x00001000, PISP_IMAGE_FORMAT_SHIFT_0 = 0x00000000, PISP_IMAGE_FORMAT_SHIFT_1 = 0x00010000, PISP_IMAGE_FORMAT_SHIFT_2 = 0x00020000, PISP_IMAGE_FORMAT_SHIFT_3 = 0x00030000, PISP_IMAGE_FORMAT_SHIFT_4 = 0x00040000, PISP_IMAGE_FORMAT_SHIFT_5 = 0x00050000, PISP_IMAGE_FORMAT_SHIFT_6 = 0x00060000, PISP_IMAGE_FORMAT_SHIFT_7 = 0x00070000, PISP_IMAGE_FORMAT_SHIFT_8 = 0x00080000, PISP_IMAGE_FORMAT_SHIFT_MASK = 0x000f0000, PISP_IMAGE_FORMAT_BPP_32 = 0x00100000, PISP_IMAGE_FORMAT_X_VALUE = 0x00200000, PISP_IMAGE_FORMAT_UNCOMPRESSED = 0x00000000, PISP_IMAGE_FORMAT_COMPRESSION_MODE_1 = 0x01000000, PISP_IMAGE_FORMAT_COMPRESSION_MODE_2 = 0x02000000, PISP_IMAGE_FORMAT_COMPRESSION_MODE_3 = 0x03000000, PISP_IMAGE_FORMAT_COMPRESSION_MASK = 0x03000000, PISP_IMAGE_FORMAT_HOG_SIGNED = 0x04000000, PISP_IMAGE_FORMAT_HOG_UNSIGNED = 0x08000000, PISP_IMAGE_FORMAT_INTEGRAL_IMAGE = 0x10000000, PISP_IMAGE_FORMAT_WALLPAPER_ROLL = 0x20000000, PISP_IMAGE_FORMAT_THREE_CHANNEL = 0x40000000, /* Lastly a few specific instantiations of the above. */ PISP_IMAGE_FORMAT_SINGLE_16 = PISP_IMAGE_FORMAT_BPS_16, PISP_IMAGE_FORMAT_THREE_16 = PISP_IMAGE_FORMAT_BPS_16 | PISP_IMAGE_FORMAT_THREE_CHANNEL }; #define PISP_IMAGE_FORMAT_BPS_8(fmt) \ (((fmt) & PISP_IMAGE_FORMAT_BPS_MASK) == PISP_IMAGE_FORMAT_BPS_8) #define PISP_IMAGE_FORMAT_BPS_10(fmt) \ (((fmt) & PISP_IMAGE_FORMAT_BPS_MASK) == PISP_IMAGE_FORMAT_BPS_10) #define PISP_IMAGE_FORMAT_BPS_12(fmt) \ (((fmt) & PISP_IMAGE_FORMAT_BPS_MASK) == PISP_IMAGE_FORMAT_BPS_12) #define PISP_IMAGE_FORMAT_BPS_16(fmt) \ (((fmt) & PISP_IMAGE_FORMAT_BPS_MASK) == PISP_IMAGE_FORMAT_BPS_16) #define PISP_IMAGE_FORMAT_BPS(fmt) \ (((fmt) & PISP_IMAGE_FORMAT_BPS_MASK) ? \ 8 + (2 << (((fmt) & PISP_IMAGE_FORMAT_BPS_MASK) - 1)) : 8) #define PISP_IMAGE_FORMAT_SHIFT(fmt) \ (((fmt) & PISP_IMAGE_FORMAT_SHIFT_MASK) / PISP_IMAGE_FORMAT_SHIFT_1) #define PISP_IMAGE_FORMAT_THREE_CHANNEL(fmt) \ ((fmt) & PISP_IMAGE_FORMAT_THREE_CHANNEL) #define PISP_IMAGE_FORMAT_SINGLE_CHANNEL(fmt) \ (!((fmt) & PISP_IMAGE_FORMAT_THREE_CHANNEL)) #define PISP_IMAGE_FORMAT_COMPRESSED(fmt) \ (((fmt) & PISP_IMAGE_FORMAT_COMPRESSION_MASK) != \ PISP_IMAGE_FORMAT_UNCOMPRESSED) #define PISP_IMAGE_FORMAT_SAMPLING_444(fmt) \ (((fmt) & PISP_IMAGE_FORMAT_SAMPLING_MASK) == \ PISP_IMAGE_FORMAT_SAMPLING_444) #define PISP_IMAGE_FORMAT_SAMPLING_422(fmt) \ (((fmt) & PISP_IMAGE_FORMAT_SAMPLING_MASK) == \ PISP_IMAGE_FORMAT_SAMPLING_422) #define PISP_IMAGE_FORMAT_SAMPLING_420(fmt) \ (((fmt) & PISP_IMAGE_FORMAT_SAMPLING_MASK) == \ PISP_IMAGE_FORMAT_SAMPLING_420) #define PISP_IMAGE_FORMAT_ORDER_NORMAL(fmt) \ (!((fmt) & PISP_IMAGE_FORMAT_ORDER_SWAPPED)) #define PISP_IMAGE_FORMAT_ORDER_SWAPPED(fmt) \ ((fmt) & PISP_IMAGE_FORMAT_ORDER_SWAPPED) #define PISP_IMAGE_FORMAT_INTERLEAVED(fmt) \ (((fmt) & PISP_IMAGE_FORMAT_PLANARITY_MASK) == \ PISP_IMAGE_FORMAT_PLANARITY_INTERLEAVED) #define PISP_IMAGE_FORMAT_SEMIPLANAR(fmt) \ (((fmt) & PISP_IMAGE_FORMAT_PLANARITY_MASK) == \ PISP_IMAGE_FORMAT_PLANARITY_SEMI_PLANAR) #define PISP_IMAGE_FORMAT_PLANAR(fmt) \ (((fmt) & PISP_IMAGE_FORMAT_PLANARITY_MASK) == \ PISP_IMAGE_FORMAT_PLANARITY_PLANAR) #define PISP_IMAGE_FORMAT_WALLPAPER(fmt) \ ((fmt) & PISP_IMAGE_FORMAT_WALLPAPER_ROLL) #define PISP_IMAGE_FORMAT_BPP_32(fmt) ((fmt) & PISP_IMAGE_FORMAT_BPP_32) #define PISP_IMAGE_FORMAT_HOG(fmt) \ ((fmt) & \ (PISP_IMAGE_FORMAT_HOG_SIGNED | PISP_IMAGE_FORMAT_HOG_UNSIGNED)) #define PISP_IMAGE_FORMAT_X_VALUE(fmt) \ ((fmt) & PISP_IMAGE_FORMAT_X_VALUE) #define PISP_WALLPAPER_WIDTH 128 /* in bytes */ struct pisp_bla_config { __u16 black_level_r; __u16 black_level_gr; __u16 black_level_gb; __u16 black_level_b; __u16 output_black_level; __u8 pad[2]; } __attribute__((packed)); struct pisp_wbg_config { __u16 gain_r; __u16 gain_g; __u16 gain_b; __u8 pad[2]; } __attribute__((packed)); struct pisp_compress_config { /* value subtracted from incoming data */ __u16 offset; __u8 pad; /* 1 => Companding; 2 => Delta (recommended); 3 => Combined (for HDR) */ __u8 mode; } __attribute__((packed)); struct pisp_decompress_config { /* value added to reconstructed data */ __u16 offset; __u8 pad; /* 1 => Companding; 2 => Delta (recommended); 3 => Combined (for HDR) */ __u8 mode; } __attribute__((packed)); enum pisp_axi_flags { /* * round down bursts to end at a 32-byte boundary, to align following * bursts */ PISP_AXI_FLAG_ALIGN = 128, /* for FE writer: force WSTRB high, to pad output to 16-byte boundary */ PISP_AXI_FLAG_PAD = 64, /* for FE writer: Use Output FIFO level to trigger "panic" */ PISP_AXI_FLAG_PANIC = 32, }; struct pisp_axi_config { /* * burst length minus one, which must be in the range 0:15; OR'd with * flags */ __u8 maxlen_flags; /* { prot[2:0], cache[3:0] } fields, echoed on AXI bus */ __u8 cache_prot; /* QoS field(s) (4x4 bits for FE writer; 4 bits for other masters) */ __u16 qos; } __attribute__((packed)); #endif /* _PISP_COMMON_H_ */ raspberrypi-libpisp-9ba67e6/src/libpisp/common/pisp_logging.cpp000066400000000000000000000045071507172066400250650ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (C) 2021 - 2023, Raspberry Pi Ltd * * pisp_logging.cpp - PiSP logging library */ #include "logging.hpp" #if PISP_LOGGING_ENABLE #include #include #include #include #include #include #include #include #include namespace logging = boost::log; namespace sinks = boost::log::sinks; namespace expr = boost::log::expressions; namespace keywords = boost::log::keywords; namespace trivial = boost::log::trivial; namespace libpisp { namespace { std::mutex mutex; boost::shared_ptr> console; boost::shared_ptr> file; void logging_file_init(const char *filename, unsigned int level) { logging::formatter format = expr::format("[libpisp %1%] %2%") % expr::attr("Severity") % expr::smessage; // file sink file = logging::add_file_log( keywords::file_name = filename, keywords::rotation_size = 1 * 1024 * 1024, keywords::open_mode = std::ios_base::trunc, keywords::filter = trivial::severity >= level); file->set_formatter(format); file->locked_backend()->auto_flush(true); } } // namespace void logging_init() { std::scoped_lock l(mutex); // Default to "warning" level. unsigned int level = 3; // Can only initialise the console logging once. if (console) return; logging::add_common_attributes(); logging::formatter format = expr::format("[libpisp %1%] %2%") % expr::attr("Severity") % expr::smessage; char *lev = std::getenv("LIBPISP_LOG_LEVEL"); if (lev) level = std::atoi(lev); // Console sink. console = logging::add_console_log(std::clog); console->set_formatter(format); console->set_filter(trivial::severity >= level); // File sink if needed. const char *log_file = std::getenv("LIBPISP_LOG_FILE"); if (log_file) { lev = std::getenv("LIBPISP_LOG_FILE_LEVEL"); if (lev) level = std::atoi(lev); logging_file_init(log_file, level); } } } // namespace libpisp #else void libpisp::logging_init() { } #endif // PISP_LOGGING_ENABLE raspberrypi-libpisp-9ba67e6/src/libpisp/common/pisp_pwl.cpp000066400000000000000000000134701507172066400242400ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (C) 2021 - 2023, Raspberry Pi Ltd * * pisp_pwl.cpp - PiSP PWL library */ #include #include #include "pisp_pwl.hpp" using namespace libpisp; using json = nlohmann::json; void Pwl::Read(json const ¶ms) { for (auto it = params.begin(); it != params.end(); it++) { double x = it->get(); assert(it == params.begin() || x > points_.back().x); it++; double y = it->get(); points_.push_back(Point(x, y)); } assert(points_.size() >= 2); } void Pwl::Append(double x, double y, const double eps) { if (points_.empty() || points_.back().x + eps < x) points_.push_back(Point(x, y)); } void Pwl::Prepend(double x, double y, const double eps) { if (points_.empty() || points_.front().x - eps > x) points_.insert(points_.begin(), Point(x, y)); } Pwl::Interval Pwl::Domain() const { return Interval(points_[0].x, points_[points_.size() - 1].x); } Pwl::Interval Pwl::Range() const { double lo = points_[0].y, hi = lo; for (auto &p : points_) lo = std::min(lo, p.y), hi = std::max(hi, p.y); return Interval(lo, hi); } bool Pwl::Empty() const { return points_.empty(); } double Pwl::Eval(double x, int *span_ptr, bool update_span) const { int span = findSpan(x, span_ptr && *span_ptr != -1 ? *span_ptr : points_.size() / 2 - 1); if (span_ptr && update_span) *span_ptr = span; return points_[span].y + (x - points_[span].x) * (points_[span + 1].y - points_[span].y) / (points_[span + 1].x - points_[span].x); } int Pwl::findSpan(double x, int span) const { // Pwls are generally small, so linear search may well be faster than binary, though could review this // if large PWls start turning up. int last_span = points_.size() - 2; span = std::max( 0, std::min(last_span, span)); // some algorithms may call us with span pointing directly at the last control point while (span < last_span && x >= points_[span + 1].x) span++; while (span && x < points_[span].x) span--; return span; } Pwl::PerpType Pwl::Invert(Point const &xy, Point &perp, int &span, const double eps) const { assert(span >= -1); bool prev_off_end = false; for (span = span + 1; span < (int)points_.size() - 1; span++) { Point span_vec = points_[span + 1] - points_[span]; double t = ((xy - points_[span]) % span_vec) / span_vec.Len2(); if (t < -eps) // off the start of this span { if (span == 0) { perp = points_[span]; return PerpType::Start; } else if (prev_off_end) { perp = points_[span]; return PerpType::Vertex; } } else if (t > 1 + eps) // off the end of this span { if (span == (int)points_.size() - 2) { perp = points_[span + 1]; return PerpType::End; } prev_off_end = true; } else // a true perpendicular { perp = points_[span] + span_vec * t; return PerpType::Perpendicular; } } return PerpType::None; } Pwl Pwl::Compose(Pwl const &other, const double eps) const { double this_x = points_[0].x, this_y = points_[0].y; int this_span = 0, other_span = other.findSpan(this_y, 0); Pwl result({ { this_x, other.Eval(this_y, &other_span, false) } }); while (this_span != (int)points_.size() - 1) { double dx = points_[this_span + 1].x - points_[this_span].x, dy = points_[this_span + 1].y - points_[this_span].y; if (std::abs(dy) > eps && other_span + 1 < (int)other.points_.size() && points_[this_span + 1].y >= other.points_[other_span + 1].x + eps) { // next control point in result will be where this function's y reaches the next span in other this_x = points_[this_span].x + (other.points_[other_span + 1].x - points_[this_span].y) * dx / dy; this_y = other.points_[++other_span].x; } else if (std::abs(dy) > eps && other_span > 0 && points_[this_span + 1].y <= other.points_[other_span - 1].x - eps) { // next control point in result will be where this function's y reaches the previous span in other this_x = points_[this_span].x + (other.points_[other_span + 1].x - points_[this_span].y) * dx / dy; this_y = other.points_[--other_span].x; } else { // we stay in the same span in other this_span++; this_x = points_[this_span].x, this_y = points_[this_span].y; } result.Append(this_x, other.Eval(this_y, &other_span, false), eps); } return result; } void Pwl::Map(std::function f) const { for (auto &pt : points_) f(pt.x, pt.y); } void Pwl::Map2(Pwl const &pwl0, Pwl const &pwl1, std::function f) { int span0 = 0, span1 = 0; double x = std::min(pwl0.points_[0].x, pwl1.points_[0].x); f(x, pwl0.Eval(x, &span0, false), pwl1.Eval(x, &span1, false)); while (span0 < (int)pwl0.points_.size() - 1 || span1 < (int)pwl1.points_.size() - 1) { if (span0 == (int)pwl0.points_.size() - 1) x = pwl1.points_[++span1].x; else if (span1 == (int)pwl1.points_.size() - 1) x = pwl0.points_[++span0].x; else if (pwl0.points_[span0 + 1].x > pwl1.points_[span1 + 1].x) x = pwl1.points_[++span1].x; else x = pwl0.points_[++span0].x; f(x, pwl0.Eval(x, &span0, false), pwl1.Eval(x, &span1, false)); } } Pwl Pwl::Combine(Pwl const &pwl0, Pwl const &pwl1, std::function f, const double eps) { Pwl result; Map2(pwl0, pwl1, [&](double x, double y0, double y1) { result.Append(x, f(x, y0, y1), eps); }); return result; } void Pwl::MatchDomain(Interval const &domain, bool clip, const double eps) { int span = 0; Prepend(domain.start, Eval(clip ? points_[0].x : domain.start, &span), eps); span = points_.size() - 2; Append(domain.end, Eval(clip ? points_.back().x : domain.end, &span), eps); } Pwl &Pwl::operator*=(double d) { for (auto &pt : points_) pt.y *= d; return *this; } void Pwl::Debug(FILE *fp) const { fprintf(fp, "Pwl {\n"); for (auto &p : points_) fprintf(fp, "\t(%g, %g)\n", p.x, p.y); fprintf(fp, "}\n"); } raspberrypi-libpisp-9ba67e6/src/libpisp/common/pisp_pwl.hpp000066400000000000000000000064071507172066400242470ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (C) 2021 - 2023, Raspberry Pi Ltd * * pisp_pwl.hpp - PiSP PWL library */ #pragma once #include #include #include namespace libpisp { class Pwl { public: struct Interval { Interval(double _start, double _end) : start(_start), end(_end) {} double start, end; bool Contains(double value) { return value >= start && value <= end; } double Clip(double value) { return value < start ? start : (value > end ? end : value); } double Len() const { return end - start; } }; struct Point { Point() : x(0), y(0) {} Point(double _x, double _y) : x(_x), y(_y) {} double x, y; Point operator-(Point const &p) const { return Point(x - p.x, y - p.y); } Point operator+(Point const &p) const { return Point(x + p.x, y + p.y); } double operator%(Point const &p) const { return x * p.x + y * p.y; } Point operator*(double f) const { return Point(x * f, y * f); } Point operator/(double f) const { return Point(x / f, y / f); } double Len2() const { return x * x + y * y; } double Len() const { return std::sqrt(Len2()); } }; Pwl() {} Pwl(std::vector const &points) : points_(points) {} void Read(nlohmann::json const ¶ms); void Append(double x, double y, const double eps = 1e-6); void Prepend(double x, double y, const double eps = 1e-6); Interval Domain() const; Interval Range() const; bool Empty() const; // Evaluate Pwl, optionally supplying an initial guess for the "span". The "span" may be optionally be updated. // If you want to know the "span" value but don't have an initial guess you can set it to -1. double Eval(double x, int *span_ptr = nullptr, bool update_span = true) const; // Find perpendicular closest to xy, starting from span+1 so you can call it repeatedly to check for multiple // closest points (set span to -1 on the first call). Also returns "pseudo" perpendiculars; see PerpType enum. enum class PerpType { None, // no perpendicular found Start, // start of Pwl is closest point End, // end of Pwl is closest point Vertex, // vertex of Pwl is closest point Perpendicular // true perpendicular found }; PerpType Invert(Point const &xy, Point &perp, int &span, const double eps = 1e-6) const; // Compose two Pwls together, doing "this" first and "other" after. Pwl Compose(Pwl const &other, const double eps = 1e-6) const; // Apply function to (x,y) values at every control point. void Map(std::function f) const; // Apply function to (x, y0, y1) values wherever either Pwl has a control point. static void Map2(Pwl const &pwl0, Pwl const &pwl1, std::function f); // Combine two Pwls, meaning we create a new Pwl where the y values are given by running f wherever either has a knot. static Pwl Combine(Pwl const &pwl0, Pwl const &pwl1, std::function f, const double eps = 1e-6); // Make "this" match (at least) the given domain. Any extension my be clipped or linear. void MatchDomain(Interval const &domain, bool clip = true, const double eps = 1e-6); Pwl &operator*=(double d); void Debug(FILE *fp = stdout) const; private: int findSpan(double x, int span) const; std::vector points_; }; } // namespace libpisp raspberrypi-libpisp-9ba67e6/src/libpisp/common/pisp_utils.cpp000066400000000000000000000223371507172066400246000ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (C) 2021 - 2023, Raspberry Pi Ltd * * pisp_utils.cpp - PiSP buffer helper utilities */ #include #include #include #include #include #include "backend/pisp_be_config.h" #include "pisp_common.h" #include "logging.hpp" namespace libpisp { uint32_t compute_x_offset(uint32_t /* pisp_image_format */ format, int x) { PISP_ASSERT(x >= 0 && x < 65536); uint32_t x_offset = 0; uint32_t bps = format & PISP_IMAGE_FORMAT_BPS_MASK; // HoG features are slightly different from the rest. if (PISP_IMAGE_FORMAT_HOG(format)) { // x here is in units of cells. // Output 16-bit word samples per bin. This is then packed to: // 32-bytes for an unsigned histogram cell. // 48-bytes for a signed histogram cell. x_offset = x * ((format & PISP_IMAGE_FORMAT_HOG_UNSIGNED) ? 32 : 48); } else if (format & (PISP_IMAGE_FORMAT_INTEGRAL_IMAGE | PISP_IMAGE_FORMAT_BPP_32)) { // 32-bit words per sample. x_offset = x * 4; } else { if (bps == PISP_IMAGE_FORMAT_BPS_16) x_offset = x * 2; else if (bps == PISP_IMAGE_FORMAT_BPS_12) x_offset = (x * 3 + 1) / 2; else if (bps == PISP_IMAGE_FORMAT_BPS_10) x_offset = (x / 3) * 4; else x_offset = x; if ((format & PISP_IMAGE_FORMAT_THREE_CHANNEL) && PISP_IMAGE_FORMAT_INTERLEAVED(format)) { if (PISP_IMAGE_FORMAT_SAMPLING_422(format)) x_offset *= 2; else x_offset *= 3; } } return x_offset; } void compute_stride_align(pisp_image_format_config &config, int align, bool preserve_subsample_ratio) { if (PISP_IMAGE_FORMAT_WALLPAPER(config.format)) { config.stride2 = config.stride = config.height * PISP_WALLPAPER_WIDTH; if (PISP_IMAGE_FORMAT_SAMPLING_420(config.format)) config.stride2 /= 2; return; } uint16_t width = config.width; if (PISP_IMAGE_FORMAT_COMPRESSED(config.format)) width = (width + 7) & ~7; // compression uses blocks of 8 samples int32_t computed_stride = compute_x_offset(config.format, width); if (!config.stride || config.stride < computed_stride) config.stride = computed_stride; config.stride2 = 0; if (!PISP_IMAGE_FORMAT_HOG(config.format)) { switch (config.format & PISP_IMAGE_FORMAT_PLANARITY_MASK) { case PISP_IMAGE_FORMAT_PLANARITY_PLANAR: if (PISP_IMAGE_FORMAT_SAMPLING_422(config.format) || PISP_IMAGE_FORMAT_SAMPLING_420(config.format)) config.stride2 = config.stride >> 1; else if (PISP_IMAGE_FORMAT_THREE_CHANNEL(config.format)) config.stride2 = config.stride; break; case PISP_IMAGE_FORMAT_PLANARITY_SEMI_PLANAR: PISP_ASSERT(PISP_IMAGE_FORMAT_SAMPLING_422(config.format) || PISP_IMAGE_FORMAT_SAMPLING_420(config.format)); config.stride2 = config.stride; break; } // image in memory must be sufficiently aligned config.stride = (config.stride + align - 1) & ~(align - 1); config.stride2 = (config.stride2 + align - 1) & ~(align - 1); // For YUV420/422 formats, ensure the stride ratio matches the subample ratio for the planes. if (preserve_subsample_ratio && PISP_IMAGE_FORMAT_PLANAR(config.format) && (PISP_IMAGE_FORMAT_SAMPLING_422(config.format) || PISP_IMAGE_FORMAT_SAMPLING_420(config.format))) config.stride = config.stride2 << 1; } } void compute_stride(pisp_image_format_config &config, bool preserve_subsample_ratio) { // Our preferred alignment is really 64 bytes, though 16 should work too. Use 16 for now, as it gives better test coverage. compute_stride_align(config, PISP_BACK_END_OUTPUT_MIN_ALIGN, preserve_subsample_ratio); } void compute_optimal_stride(pisp_image_format_config &config, bool preserve_subsample_ratio) { // Use our preferred alignment of 64 bytes. compute_stride_align(config, PISP_BACK_END_OUTPUT_MAX_ALIGN, preserve_subsample_ratio); } void compute_optimal_stride(pisp_image_format_config &config) { compute_optimal_stride(config, false); } void compute_addr_offset(const pisp_image_format_config &config, int x, int y, uint32_t *addr_offset, uint32_t *addr_offset2) { if (PISP_IMAGE_FORMAT_WALLPAPER(config.format)) { int pixels_in_roll = PISP_IMAGE_FORMAT_BPS_8(config.format) ? PISP_WALLPAPER_WIDTH : (PISP_IMAGE_FORMAT_BPS_16(config.format) ? PISP_WALLPAPER_WIDTH / 2 : PISP_WALLPAPER_WIDTH / 4 * 3); int pixel_offset_in_roll = x % pixels_in_roll; int pixel_offset_in_bytes; if (PISP_IMAGE_FORMAT_BPS_8(config.format)) pixel_offset_in_bytes = pixel_offset_in_roll; else if (PISP_IMAGE_FORMAT_BPS_16(config.format)) pixel_offset_in_bytes = pixel_offset_in_roll * 2; else { // 10-bit format. Whinge if not a multiple of 3 into the roll. PISP_ASSERT(pixel_offset_in_roll % 3 == 0); pixel_offset_in_bytes = pixel_offset_in_roll / 3 * 4; } int num_rolls = x / pixels_in_roll; *addr_offset = num_rolls * config.stride + y * PISP_WALLPAPER_WIDTH + pixel_offset_in_bytes; if (PISP_IMAGE_FORMAT_SAMPLING_420(config.format)) *addr_offset2 = num_rolls * config.stride2 + y / 2 * PISP_WALLPAPER_WIDTH + pixel_offset_in_bytes; else *addr_offset2 = *addr_offset; return; } uint32_t x_byte_offset = compute_x_offset(config.format, x); *addr_offset = y * config.stride + x_byte_offset; if (addr_offset2 && !PISP_IMAGE_FORMAT_INTERLEAVED(config.format)) { if (PISP_IMAGE_FORMAT_SAMPLING_420(config.format)) y /= 2; if (PISP_IMAGE_FORMAT_PLANAR(config.format) && !PISP_IMAGE_FORMAT_SAMPLING_444(config.format)) x_byte_offset /= 2; *addr_offset2 = y * config.stride2 + x_byte_offset; } } int num_planes(pisp_image_format format) { int planes = 1; if (PISP_IMAGE_FORMAT_THREE_CHANNEL(format)) { switch (format & PISP_IMAGE_FORMAT_PLANARITY_MASK) { case PISP_IMAGE_FORMAT_PLANARITY_INTERLEAVED: planes = 1; break; case PISP_IMAGE_FORMAT_PLANARITY_SEMI_PLANAR: planes = 2; break; case PISP_IMAGE_FORMAT_PLANARITY_PLANAR: planes = 3; break; } } return planes; } std::size_t get_plane_size(const pisp_image_format_config &config, int plane) { uint64_t stride = std::abs(plane ? config.stride2 : config.stride); // in case vflipped? uint64_t plane_size = 0; if (PISP_IMAGE_FORMAT_WALLPAPER(config.format)) { int pixels_in_roll = PISP_IMAGE_FORMAT_BPS_8(config.format) ? PISP_WALLPAPER_WIDTH : (PISP_IMAGE_FORMAT_BPS_16(config.format) ? PISP_WALLPAPER_WIDTH / 2 : PISP_WALLPAPER_WIDTH / 4 * 3); std::size_t num_rolls = (config.width + pixels_in_roll - 1) / pixels_in_roll; plane_size = num_rolls * stride; } else { std::size_t height = plane && PISP_IMAGE_FORMAT_SAMPLING_420(config.format) ? config.height >> 1 : config.height; plane_size = height * stride; } return plane_size >= (1ULL << 32) ? 0 : plane_size; } static const std::map &formats_table() { // Note that alternate names and plane orderings are not defined to keep a 1:1 mapping. static const std::map formats = { { "YUV444P", PISP_IMAGE_FORMAT_THREE_CHANNEL + PISP_IMAGE_FORMAT_BPS_8 + PISP_IMAGE_FORMAT_SAMPLING_444 + PISP_IMAGE_FORMAT_PLANARITY_PLANAR }, { "YUV422P", PISP_IMAGE_FORMAT_THREE_CHANNEL + PISP_IMAGE_FORMAT_BPS_8 + PISP_IMAGE_FORMAT_SAMPLING_422 + PISP_IMAGE_FORMAT_PLANARITY_PLANAR }, { "YUV420P", PISP_IMAGE_FORMAT_THREE_CHANNEL + PISP_IMAGE_FORMAT_BPS_8 + PISP_IMAGE_FORMAT_SAMPLING_420 + PISP_IMAGE_FORMAT_PLANARITY_PLANAR }, { "NV12", PISP_IMAGE_FORMAT_THREE_CHANNEL + PISP_IMAGE_FORMAT_BPS_8 + PISP_IMAGE_FORMAT_SAMPLING_420 + PISP_IMAGE_FORMAT_PLANARITY_SEMI_PLANAR }, { "NV21", PISP_IMAGE_FORMAT_THREE_CHANNEL + PISP_IMAGE_FORMAT_BPS_8 + PISP_IMAGE_FORMAT_SAMPLING_420 + PISP_IMAGE_FORMAT_PLANARITY_SEMI_PLANAR + PISP_IMAGE_FORMAT_ORDER_SWAPPED }, { "YUYV", PISP_IMAGE_FORMAT_THREE_CHANNEL + PISP_IMAGE_FORMAT_BPS_8 + PISP_IMAGE_FORMAT_SAMPLING_422 + PISP_IMAGE_FORMAT_PLANARITY_INTERLEAVED }, { "UYVY", PISP_IMAGE_FORMAT_THREE_CHANNEL + PISP_IMAGE_FORMAT_BPS_8 + PISP_IMAGE_FORMAT_SAMPLING_422 + PISP_IMAGE_FORMAT_PLANARITY_INTERLEAVED + PISP_IMAGE_FORMAT_ORDER_SWAPPED }, { "NV16", PISP_IMAGE_FORMAT_THREE_CHANNEL + PISP_IMAGE_FORMAT_BPS_8 + PISP_IMAGE_FORMAT_SAMPLING_422 + PISP_IMAGE_FORMAT_PLANARITY_SEMI_PLANAR }, { "NV61", PISP_IMAGE_FORMAT_THREE_CHANNEL + PISP_IMAGE_FORMAT_BPS_8 + PISP_IMAGE_FORMAT_SAMPLING_422 + PISP_IMAGE_FORMAT_PLANARITY_SEMI_PLANAR + PISP_IMAGE_FORMAT_ORDER_SWAPPED }, { "RGB888", PISP_IMAGE_FORMAT_THREE_CHANNEL }, { "RGBX8888", PISP_IMAGE_FORMAT_THREE_CHANNEL + PISP_IMAGE_FORMAT_BPP_32 }, { "RGB161616", PISP_IMAGE_FORMAT_THREE_CHANNEL + PISP_IMAGE_FORMAT_BPS_16 }, { "BAYER16", PISP_IMAGE_FORMAT_BPS_16 + PISP_IMAGE_FORMAT_UNCOMPRESSED }, { "PISP_COMP1", PISP_IMAGE_FORMAT_COMPRESSION_MODE_1 }, { "PISP_COMP2", PISP_IMAGE_FORMAT_COMPRESSION_MODE_2 }, }; return formats; } unsigned int get_pisp_image_format(const std::string &format) { auto it = formats_table().find(format); if (it == formats_table().end()) return 0; return it->second; } std::string get_pisp_image_format(uint32_t format) { // Remove shift from the format assignment, its value does not change format. format = format & ~PISP_IMAGE_FORMAT_SHIFT_MASK; const auto &fmts = formats_table(); auto it = std::find_if(fmts.begin(), fmts.end(), [format](const auto &f) { return f.second == format; }); if (it == fmts.end()) return {}; return it->first; } } // namespace libpisp raspberrypi-libpisp-9ba67e6/src/libpisp/common/shm_mutex.hpp000066400000000000000000000014511507172066400244150ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (C) 2021 - 2023, Raspberry Pi Ltd * * shm_mutex.hpp - PiSP interprocess mutex implementation */ #pragma once #include namespace libpisp { class ShmMutex { public: ShmMutex() { pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST); pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); pthread_mutex_init(&mutex_, &attr); pthread_mutexattr_destroy(&attr); } ~ShmMutex() { pthread_mutex_destroy(&mutex_); } void lock() { pthread_mutex_lock(&mutex_); } void unlock() { pthread_mutex_unlock(&mutex_); } bool try_lock() { return pthread_mutex_trylock(&mutex_) == 0; } private: pthread_mutex_t mutex_; }; } // namespace libpisp raspberrypi-libpisp-9ba67e6/src/libpisp/common/utils.hpp000066400000000000000000000017441507172066400235510ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (C) 2021 - 2023, Raspberry Pi Ltd * * utils.hpp - PiSP buffer helper utilities */ #pragma once #include #include #include "pisp_common.h" namespace libpisp { void compute_stride(pisp_image_format_config &config, bool preserve_subsample_ratio = false); void compute_optimal_stride(pisp_image_format_config &config); void compute_optimal_stride(pisp_image_format_config &config, bool preserve_subsample_ratio); void compute_stride_align(pisp_image_format_config &config, int align, bool preserve_subsample_ratio = false); void compute_addr_offset(const pisp_image_format_config &config, int x, int y, uint32_t *addr_offset, uint32_t *addr_offset2); int num_planes(pisp_image_format format); std::size_t get_plane_size(const pisp_image_format_config &config, int plane); uint32_t get_pisp_image_format(const std::string &format); std::string get_pisp_image_format(uint32_t format); } // namespace libpisp raspberrypi-libpisp-9ba67e6/src/libpisp/common/version.hpp000066400000000000000000000004041507172066400240660ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (C) 2021 - 2023, Raspberry Pi Ltd * * version.hpp - libpisp auto-generated versioning */ #pragma once #include namespace libpisp { const std::string& version(); } // namespace libpisp raspberrypi-libpisp-9ba67e6/src/libpisp/frontend/000077500000000000000000000000001507172066400222215ustar00rootroot00000000000000raspberrypi-libpisp-9ba67e6/src/libpisp/frontend/frontend.cpp000066400000000000000000000367151507172066400245600ustar00rootroot00000000000000 /* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (C) 2021 - 2023, Raspberry Pi Ltd * * frontend.cpp - PiSP Front End implementation */ #include "frontend.hpp" #include #include #include "common/logging.hpp" #include "common/utils.hpp" using namespace libpisp; namespace { inline uint32_t block_enable(uint32_t block, unsigned int branch) { return block << (4 * branch); } void finalise_lsc(pisp_fe_lsc_config &lsc, uint16_t width, uint16_t height) { if (lsc.centre_x == 0) lsc.centre_x = width / 2; if (lsc.centre_y == 0) lsc.centre_y = height / 2; if (lsc.scale == 0) { uint16_t max_dx = std::max(width - lsc.centre_x, lsc.centre_x); uint16_t max_dy = std::max(height - lsc.centre_y, lsc.centre_y); uint32_t max_r2 = max_dx * (uint32_t)max_dx + max_dy * (uint32_t)max_dy; // spec requires r^2 to fit 31 bits PISP_ASSERT(max_r2 < (1u << 31)); lsc.shift = 0; while (max_r2 >= 2 * ((PISP_FE_LSC_LUT_SIZE - 1) << FrontEnd::InterpPrecision)) { max_r2 >>= 1; lsc.shift++; } lsc.scale = ((1 << FrontEnd::ScalePrecision) * ((PISP_FE_LSC_LUT_SIZE - 1) << FrontEnd::InterpPrecision) - 1) / max_r2; if (lsc.scale >= (1 << FrontEnd::ScalePrecision)) lsc.scale = (1 << FrontEnd::ScalePrecision) - 1; } } void finalise_agc(pisp_fe_agc_stats_config &agc, uint16_t width, uint16_t height) { if (agc.size_x == 0) agc.size_x = std::max(2, ((width - 2 * agc.offset_x) / PISP_AGC_STATS_SIZE) & ~1); if (agc.size_y == 0) agc.size_y = std::max(2, ((height - 2 * agc.offset_y) / PISP_AGC_STATS_SIZE) & ~1); if (agc.row_size_x == 0) agc.row_size_x = std::max(2, (width - 2 * agc.row_offset_x) & ~1); if (agc.row_size_y == 0) agc.row_size_y = std::max(2, ((height - 2 * agc.row_offset_y) / PISP_AGC_STATS_NUM_ROW_SUMS) & ~1); } void finalise_awb(pisp_fe_awb_stats_config &awb, uint16_t width, uint16_t height) { // Just a warning that ACLS algorithms might want the size calculations // here to match the Back End LSC. Here we round the cell width and height // to the nearest even number. if (awb.size_x == 0) awb.size_x = 2 * std::max(1, ((width - 2 * awb.offset_x + PISP_AWB_STATS_SIZE) / (2 * PISP_AWB_STATS_SIZE))); if (awb.size_y == 0) awb.size_y = 2 * std::max(1, ((height - 2 * awb.offset_y + PISP_AWB_STATS_SIZE) / (2 * PISP_AWB_STATS_SIZE))); } void finalise_cdaf(pisp_fe_cdaf_stats_config &cdaf, uint16_t width, uint16_t height) { if (cdaf.size_x == 0) cdaf.size_x = std::max(2, ((width - 2 * cdaf.offset_x) / PISP_CDAF_STATS_SIZE) & ~1); if (cdaf.size_y == 0) cdaf.size_y = std::max(2, ((height - 2 * cdaf.offset_y) / PISP_CDAF_STATS_SIZE) & ~1); } void finalise_downscale(pisp_fe_downscale_config &downscale, uint16_t width, uint16_t height) { downscale.output_width = (((width >> 1) * downscale.xout) / downscale.xin) * 2; downscale.output_height = (((height >> 1) * downscale.yout) / downscale.yin) * 2; } void finalise_compression(pisp_fe_config const &fe_config, int i) { uint32_t fmt = fe_config.ch[i].output.format.format; uint32_t enables = fe_config.global.enables; if (PISP_IMAGE_FORMAT_COMPRESSED(fmt) && !(enables & block_enable(PISP_FE_ENABLE_COMPRESS0, i))) PISP_LOG(fatal, "FrontEnd::finalise: output compressed but compression not enabled"); if (!PISP_IMAGE_FORMAT_COMPRESSED(fmt) && (enables & block_enable(PISP_FE_ENABLE_COMPRESS0, i))) PISP_LOG(fatal, "FrontEnd::finalise: output uncompressed but compression enabled"); if ((enables & block_enable(PISP_FE_ENABLE_COMPRESS0, i)) && !PISP_IMAGE_FORMAT_BPS_8(fmt)) PISP_LOG(fatal, "FrontEnd::finalise: compressed output is not 8 bit"); } template std::enable_if_t> inline div2_round_e(T &val) { // Divide by 2 and round to the next even number. val = ((val + 2) & ~3) >> 1; } void decimate_config(pisp_fe_config &fe_config) { if (fe_config.global.enables & PISP_FE_ENABLE_LSC) { div2_round_e(fe_config.lsc.centre_x); div2_round_e(fe_config.lsc.centre_y); } if (fe_config.global.enables & PISP_FE_ENABLE_CDAF_STATS) { div2_round_e(fe_config.cdaf_stats.offset_x); div2_round_e(fe_config.cdaf_stats.offset_y); div2_round_e(fe_config.cdaf_stats.size_x); div2_round_e(fe_config.cdaf_stats.size_y); div2_round_e(fe_config.cdaf_stats.skip_x); div2_round_e(fe_config.cdaf_stats.skip_y); } if (fe_config.global.enables & PISP_FE_ENABLE_AWB_STATS) { div2_round_e(fe_config.awb_stats.offset_x); div2_round_e(fe_config.awb_stats.offset_y); div2_round_e(fe_config.awb_stats.size_x); div2_round_e(fe_config.awb_stats.size_y); } if (fe_config.global.enables & PISP_FE_ENABLE_AGC_STATS) { div2_round_e(fe_config.agc_stats.offset_x); div2_round_e(fe_config.agc_stats.offset_y); div2_round_e(fe_config.agc_stats.size_x); div2_round_e(fe_config.agc_stats.size_y); div2_round_e(fe_config.agc_stats.row_offset_x); div2_round_e(fe_config.agc_stats.row_offset_y); div2_round_e(fe_config.agc_stats.row_size_x); div2_round_e(fe_config.agc_stats.row_size_y); } for (unsigned int i = 0; i < PISP_FLOATING_STATS_NUM_ZONES; i++) { pisp_fe_floating_stats_region ®ion = fe_config.floating_stats.regions[i]; div2_round_e(region.offset_x); div2_round_e(region.offset_y); div2_round_e(region.size_x); div2_round_e(region.size_y); } } } // namespace FrontEnd::FrontEnd(bool streaming, PiSPVariant const &variant, int align) : variant_(variant), align_(align) { pisp_fe_input_config input; memset(&fe_config_, 0, sizeof(fe_config_)); memset(&input, 0, sizeof(input)); input.streaming = !!streaming; // Configure some plausible default AXI reader settings. if (!input.streaming) { input.axi.maxlen_flags = PISP_AXI_FLAG_ALIGN | 7; input.axi.cache_prot = 0x33; input.axi.qos = 0; input.holdoff = 0; } else { fe_config_.output_axi.maxlen_flags = 0xaf; fe_config_.output_axi.cache_prot = 0x32; fe_config_.output_axi.qos = 0x8410; fe_config_.output_axi.thresh = 0x0140; fe_config_.output_axi.throttle = 0x4100; fe_config_.dirty_flags_extra |= PISP_FE_DIRTY_OUTPUT_AXI; } pisp_fe_global_config global; GetGlobal(global); global.enables |= PISP_FE_ENABLE_INPUT; SetGlobal(global); SetInput(input); } FrontEnd::~FrontEnd() { } void FrontEnd::SetGlobal(pisp_fe_global_config const &global) { // label anything that has become enabled as dirty fe_config_.dirty_flags |= (global.enables & ~fe_config_.global.enables); fe_config_.global = global; fe_config_.dirty_flags_extra |= PISP_FE_DIRTY_GLOBAL; } void FrontEnd::GetGlobal(pisp_fe_global_config &global) const { global = fe_config_.global; } void FrontEnd::SetInput(pisp_fe_input_config const &input) { fe_config_.input = input; fe_config_.dirty_flags |= PISP_FE_ENABLE_INPUT; } void FrontEnd::SetDecompress(pisp_decompress_config const &decompress) { fe_config_.decompress = decompress; fe_config_.dirty_flags |= PISP_FE_ENABLE_DECOMPRESS; } void FrontEnd::SetDecompand(pisp_fe_decompand_config const &decompand) { fe_config_.decompand = decompand; fe_config_.decompand.pad = 0; fe_config_.dirty_flags |= PISP_FE_ENABLE_DECOMPAND; } void FrontEnd::SetDpc(pisp_fe_dpc_config const &dpc) { fe_config_.dpc = dpc; fe_config_.dirty_flags |= PISP_FE_ENABLE_DPC; } void FrontEnd::SetBla(pisp_bla_config const &bla) { fe_config_.bla = bla; fe_config_.dirty_flags |= PISP_FE_ENABLE_BLA; } void FrontEnd::SetStatsCrop(pisp_fe_crop_config const &stats_crop) { fe_config_.stats_crop = stats_crop; fe_config_.dirty_flags |= PISP_FE_ENABLE_STATS_CROP; } void FrontEnd::SetBlc(pisp_bla_config const &blc) { fe_config_.blc = blc; fe_config_.dirty_flags |= PISP_FE_ENABLE_BLC; } void FrontEnd::SetLsc(pisp_fe_lsc_config const &lsc) { fe_config_.lsc = lsc; fe_config_.dirty_flags |= PISP_FE_ENABLE_LSC; } void FrontEnd::SetRGBY(pisp_fe_rgby_config const &rgby) { fe_config_.rgby = rgby; fe_config_.dirty_flags |= PISP_FE_ENABLE_RGBY; } void FrontEnd::SetAgcStats(pisp_fe_agc_stats_config const &agc_stats) { fe_config_.agc_stats = agc_stats; fe_config_.dirty_flags |= PISP_FE_ENABLE_AGC_STATS; } void FrontEnd::GetAgcStats(pisp_fe_agc_stats_config &agc_stats) const { agc_stats = fe_config_.agc_stats; } void FrontEnd::SetAwbStats(pisp_fe_awb_stats_config const &awb_stats) { fe_config_.awb_stats = awb_stats; fe_config_.dirty_flags |= PISP_FE_ENABLE_AWB_STATS; } void FrontEnd::GetAwbStats(pisp_fe_awb_stats_config &awb_stats) const { awb_stats = fe_config_.awb_stats; } void FrontEnd::SetFloatingStats(pisp_fe_floating_stats_config const &floating_stats) { fe_config_.floating_stats = floating_stats; fe_config_.dirty_flags_extra |= PISP_FE_DIRTY_FLOATING; } void FrontEnd::SetCdafStats(pisp_fe_cdaf_stats_config const &cdaf_stats) { fe_config_.cdaf_stats = cdaf_stats; fe_config_.dirty_flags |= PISP_FE_ENABLE_CDAF_STATS; } void FrontEnd::GetCdafStats(pisp_fe_cdaf_stats_config &cdaf_stats) const { cdaf_stats = fe_config_.cdaf_stats; } void FrontEnd::SetCrop(unsigned int output_num, pisp_fe_crop_config const &crop) { PISP_ASSERT(output_num < variant_.FrontEndNumBranches(0)); fe_config_.ch[output_num].crop = crop; fe_config_.dirty_flags |= block_enable(PISP_FE_ENABLE_CROP0, output_num); } void FrontEnd::SetDownscale(unsigned int output_num, pisp_fe_downscale_config const &downscale) { PISP_ASSERT(output_num < variant_.FrontEndNumBranches(0)); PISP_ASSERT(variant_.FrontEndDownscalerAvailable(0, output_num)); fe_config_.ch[output_num].downscale = downscale; fe_config_.dirty_flags |= block_enable(PISP_FE_ENABLE_DOWNSCALE0, output_num); } void FrontEnd::SetCompress(unsigned int output_num, pisp_compress_config const &compress) { PISP_ASSERT(output_num < variant_.FrontEndNumBranches(0)); fe_config_.ch[output_num].compress = compress; fe_config_.dirty_flags |= block_enable(PISP_FE_ENABLE_COMPRESS0, output_num); } void FrontEnd::SetOutputFormat(unsigned int output_num, pisp_image_format_config const &output_format) { PISP_ASSERT(output_num < variant_.FrontEndNumBranches(0)); fe_config_.ch[output_num].output.format = output_format; fe_config_.dirty_flags |= block_enable(PISP_FE_ENABLE_OUTPUT0, output_num); } void FrontEnd::SetOutputIntrLines(unsigned int output_num, int ilines) { PISP_ASSERT(output_num < variant_.FrontEndNumBranches(0)); fe_config_.ch[output_num].output.ilines = ilines; fe_config_.dirty_flags |= block_enable(PISP_FE_ENABLE_OUTPUT0, output_num); } void FrontEnd::SetOutputBuffer(unsigned int output_num, pisp_fe_output_buffer_config const &output_buffer) { PISP_ASSERT(output_num < variant_.FrontEndNumBranches(0)); fe_config_.output_buffer[output_num] = output_buffer; // Assume these always get written. } void FrontEnd::GetInput(pisp_fe_input_config &input) const { input = fe_config_.input; } void FrontEnd::GetInputBuffer(pisp_fe_input_buffer_config &input_buffer) const { input_buffer = fe_config_.input_buffer; } void FrontEnd::GetDecompress(pisp_decompress_config &decompress) const { decompress = fe_config_.decompress; } void FrontEnd::GetDecompand(pisp_fe_decompand_config &decompand) const { decompand = fe_config_.decompand; } void FrontEnd::GetDpc(pisp_fe_dpc_config &dpc) const { dpc = fe_config_.dpc; } void FrontEnd::GetBla(pisp_bla_config &bla) const { bla = fe_config_.bla; } void FrontEnd::GetStatsCrop(pisp_fe_crop_config &stats_crop) const { stats_crop = fe_config_.stats_crop; } void FrontEnd::GetBlc(pisp_bla_config &blc) const { blc = fe_config_.blc; } void FrontEnd::GetRGBY(pisp_fe_rgby_config &rgby) const { rgby = fe_config_.rgby; } void FrontEnd::GetLsc(pisp_fe_lsc_config &lsc) const { lsc = fe_config_.lsc; } void FrontEnd::GetFloatingStats(pisp_fe_floating_stats_config &floating_stats) const { floating_stats = fe_config_.floating_stats; } void FrontEnd::GetCrop(unsigned int output_num, pisp_fe_crop_config &crop) const { PISP_ASSERT(output_num < variant_.FrontEndNumBranches(0)); crop = fe_config_.ch[output_num].crop; } void FrontEnd::GetDownscale(unsigned int output_num, pisp_fe_downscale_config &downscale) const { PISP_ASSERT(output_num < variant_.FrontEndNumBranches(0)); downscale = fe_config_.ch[output_num].downscale; } void FrontEnd::GetCompress(unsigned int output_num, pisp_compress_config &compress) const { PISP_ASSERT(output_num < variant_.FrontEndNumBranches(0)); compress = fe_config_.ch[output_num].compress; } void FrontEnd::GetOutputFormat(unsigned int output_num, pisp_image_format_config &output_format) const { PISP_ASSERT(output_num < variant_.FrontEndNumBranches(0)); output_format = fe_config_.ch[output_num].output.format; } void FrontEnd::GetOutputBuffer(unsigned int output_num, pisp_fe_output_buffer_config &output_buffer) const { PISP_ASSERT(output_num < variant_.FrontEndNumBranches(0)); output_buffer = fe_config_.output_buffer[output_num]; } int FrontEnd::GetOutputIntrLines(unsigned int output_num) const { PISP_ASSERT(output_num < variant_.FrontEndNumBranches(0)); return fe_config_.ch[output_num].output.ilines; } void FrontEnd::Prepare(pisp_fe_config *config) { // Only finalise blocks that are dirty *and* enabled. uint32_t dirty_flags = fe_config_.dirty_flags & fe_config_.global.enables; uint16_t width = fe_config_.input.format.width, height = fe_config_.input.format.height; if (fe_config_.global.enables & PISP_FE_ENABLE_STATS_CROP) { width = fe_config_.stats_crop.width; height = fe_config_.stats_crop.height; } if (dirty_flags & PISP_FE_ENABLE_LSC) finalise_lsc(fe_config_.lsc, width, height); if (dirty_flags & PISP_FE_ENABLE_AGC_STATS) finalise_agc(fe_config_.agc_stats, width, height); if (dirty_flags & PISP_FE_ENABLE_AWB_STATS) finalise_awb(fe_config_.awb_stats, width, height); if (dirty_flags & PISP_FE_ENABLE_CDAF_STATS) finalise_cdaf(fe_config_.cdaf_stats, width, height); width = fe_config_.input.format.width, height = fe_config_.input.format.height; for (int i = 0; i < PISP_FE_NUM_OUTPUTS; i++) { if (dirty_flags & block_enable(PISP_FE_ENABLE_DOWNSCALE0, i)) { int cwidth = width, cheight = height; if (fe_config_.global.enables & block_enable(PISP_FE_ENABLE_CROP0, i)) cwidth = fe_config_.ch[i].crop.width, cheight = fe_config_.ch[i].crop.height; finalise_downscale(fe_config_.ch[i].downscale, cwidth, cheight); } if (dirty_flags & (block_enable(PISP_FE_ENABLE_OUTPUT0, i) | block_enable(PISP_FE_ENABLE_COMPRESS0, i))) finalise_compression(fe_config_, i); if (dirty_flags & block_enable(PISP_FE_ENABLE_OUTPUT0, i)) { pisp_image_format_config &image_config = fe_config_.ch[i].output.format; fixOutputSize(i); if (!image_config.stride) compute_stride_align(image_config, align_); } } *config = fe_config_; // Fixup any grid offsets/sizes if stats decimation is enabled. if (config->global.enables & PISP_FE_ENABLE_DECIMATE) decimate_config(*config); fe_config_.dirty_flags = fe_config_.dirty_flags_extra = 0; } void FrontEnd::fixOutputSize(unsigned int output_num) { PISP_ASSERT(output_num < variant_.FrontEndNumBranches(0)); pisp_image_format_config &image_config = fe_config_.ch[output_num].output.format; image_config.width = image_config.height = 0; if (fe_config_.global.enables & block_enable(PISP_FE_ENABLE_OUTPUT0, output_num)) { image_config.width = fe_config_.input.format.width; image_config.height = fe_config_.input.format.height; if (fe_config_.global.enables & block_enable(PISP_FE_ENABLE_CROP0, output_num)) { image_config.width = fe_config_.ch[output_num].crop.width; image_config.width = fe_config_.ch[output_num].crop.height; } if (fe_config_.global.enables & block_enable(PISP_FE_ENABLE_DOWNSCALE0, output_num)) { image_config.width = fe_config_.ch[output_num].downscale.output_width; image_config.height = fe_config_.ch[output_num].downscale.output_height; } } } raspberrypi-libpisp-9ba67e6/src/libpisp/frontend/frontend.hpp000066400000000000000000000073701507172066400245600ustar00rootroot00000000000000 /* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (C) 2021 - 2023, Raspberry Pi Ltd * * frontend.hpp - PiSP Front End implementation */ #pragma once #include #include #include "common/pisp_common.h" #include "common/shm_mutex.hpp" #include "variants/variant.hpp" #include "pisp_fe_config.h" #include "pisp_statistics.h" namespace libpisp { class FrontEnd final { public: static constexpr uint32_t ScalePrecision = 10; static constexpr uint32_t InterpPrecision = 6; FrontEnd(bool streaming, PiSPVariant const &variant, int align = 64); ~FrontEnd(); void SetGlobal(pisp_fe_global_config const &global); void GetGlobal(pisp_fe_global_config &global) const; void SetInput(pisp_fe_input_config const &input); void GetInput(pisp_fe_input_config &input) const; void SetInputBuffer(pisp_fe_input_buffer_config const &input_buffer); void GetInputBuffer(pisp_fe_input_buffer_config &input_buffer) const; void SetDecompress(pisp_decompress_config const &decompress); void GetDecompress(pisp_decompress_config &decompress) const; void SetDecompand(pisp_fe_decompand_config const &decompand); void GetDecompand(pisp_fe_decompand_config &decompand) const; void SetDpc(pisp_fe_dpc_config const &dpc); void GetDpc(pisp_fe_dpc_config &dpc) const; void SetBla(pisp_bla_config const &bla); void GetBla(pisp_bla_config &bla) const; void SetStatsCrop(pisp_fe_crop_config const &stats_crop); void GetStatsCrop(pisp_fe_crop_config &stats_crop) const; void SetBlc(pisp_bla_config const &blc); void GetBlc(pisp_bla_config &blc) const; void SetRGBY(pisp_fe_rgby_config const &rgby); void GetRGBY(pisp_fe_rgby_config &rgby) const; void SetLsc(pisp_fe_lsc_config const &lsc); void GetLsc(pisp_fe_lsc_config &lsc) const; void SetAgcStats(pisp_fe_agc_stats_config const &agc_stats); void GetAgcStats(pisp_fe_agc_stats_config &agc_stats) const; void SetAwbStats(pisp_fe_awb_stats_config const &awb_stats); void GetAwbStats(pisp_fe_awb_stats_config &awb_stats) const; void SetFloatingStats(pisp_fe_floating_stats_config const &floating_stats); void GetFloatingStats(pisp_fe_floating_stats_config &floating_stats) const; void SetCdafStats(pisp_fe_cdaf_stats_config const &cdaf_stats); void GetCdafStats(pisp_fe_cdaf_stats_config &cdaf_stats) const; void SetCrop(unsigned int output_num, pisp_fe_crop_config const &crop); void GetCrop(unsigned int output_num, pisp_fe_crop_config &crop) const; void SetDownscale(unsigned int output_num, pisp_fe_downscale_config const &downscale); void GetDownscale(unsigned int output_num, pisp_fe_downscale_config &downscale) const; void SetCompress(unsigned int output_num, pisp_compress_config const &compress); void GetCompress(unsigned int output_num, pisp_compress_config &compress) const; void SetOutputFormat(unsigned int output_num, pisp_image_format_config const &output_format); void GetOutputFormat(unsigned int output_num, pisp_image_format_config &output_format) const; void SetOutputBuffer(unsigned int output_num, pisp_fe_output_buffer_config const &output_buffer); void GetOutputBuffer(unsigned int output_num, pisp_fe_output_buffer_config &output_buffer) const; void SetOutputIntrLines(unsigned int output_num, int lines); int GetOutputIntrLines(unsigned int output_num) const; void Prepare(pisp_fe_config *config); void lock() { mutex_.lock(); } void unlock() { mutex_.unlock(); } bool try_lock() { return mutex_.try_lock(); } private: void fixOutputSize(unsigned int output_num); const PiSPVariant variant_; pisp_fe_config fe_config_; int align_; mutable ShmMutex mutex_; }; // This is required to ensure we can safely share a FrontEnd object across multiple processes. static_assert(std::is_standard_layout::value, "FrontEnd must be a standard layout type"); } // namespace libpisp raspberrypi-libpisp-9ba67e6/src/libpisp/frontend/meson.build000066400000000000000000000005251507172066400243650ustar00rootroot00000000000000# SPDX-License-Identifier: CC0-1.0 # Copyright (C) 2023, Raspberry Pi Ltd frontend_sources = files([ 'frontend.cpp' ]) frontend_headers = files([ 'frontend.hpp', 'pisp_fe_config.h', 'pisp_statistics.h' ]) frontend_include_dir = pisp_include_dir / 'frontend' install_headers(frontend_headers, subdir: frontend_include_dir) raspberrypi-libpisp-9ba67e6/src/libpisp/frontend/pisp_fe_config.h000066400000000000000000000150521507172066400253470ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */ /* * Copyright (C) 2021 - 2023, Raspberry Pi Ltd * * pisp_fe_config.h - PiSP Front End Driver Configuration structures */ #ifndef _PISP_FE_CONFIG_ #define _PISP_FE_CONFIG_ #include "common/pisp_common.h" #include "pisp_statistics.h" #define PISP_FE_NUM_OUTPUTS 2 #define PISP_FE_ENABLE_CROP(i) (PISP_FE_ENABLE_CROP0<<(4*i)) #define PISP_FE_ENABLE_DOWNSCALE(i) (PISP_FE_ENABLE_DOWNSCALE0<<(4*i)) #define PISP_FE_ENABLE_COMPRESS(i) (PISP_FE_ENABLE_COMPRESS0<<(4*i)) #define PISP_FE_ENABLE_OUTPUT(i) (PISP_FE_ENABLE_OUTPUT0<<(4*i)) typedef enum { PISP_FE_ENABLE_INPUT = 0x000001, PISP_FE_ENABLE_DECOMPRESS = 0x000002, PISP_FE_ENABLE_DECOMPAND = 0x000004, PISP_FE_ENABLE_BLA = 0x000008, PISP_FE_ENABLE_DPC = 0x000010, PISP_FE_ENABLE_STATS_CROP = 0x000020, PISP_FE_ENABLE_DECIMATE = 0x000040, PISP_FE_ENABLE_BLC = 0x000080, PISP_FE_ENABLE_CDAF_STATS = 0x000100, PISP_FE_ENABLE_AWB_STATS = 0x000200, PISP_FE_ENABLE_RGBY = 0x000400, PISP_FE_ENABLE_LSC = 0x000800, PISP_FE_ENABLE_AGC_STATS = 0x001000, PISP_FE_ENABLE_CROP0 = 0x010000, PISP_FE_ENABLE_DOWNSCALE0 = 0x020000, PISP_FE_ENABLE_COMPRESS0 = 0x040000, PISP_FE_ENABLE_OUTPUT0 = 0x080000, PISP_FE_ENABLE_CROP1 = 0x100000, PISP_FE_ENABLE_DOWNSCALE1 = 0x200000, PISP_FE_ENABLE_COMPRESS1 = 0x400000, PISP_FE_ENABLE_OUTPUT1 = 0x800000 } pisp_fe_enable; /* * We use the enable flags to show when blocks are "dirty", but we need some * extra ones too. */ typedef enum { PISP_FE_DIRTY_GLOBAL = 0x0001, PISP_FE_DIRTY_FLOATING = 0x0002, PISP_FE_DIRTY_OUTPUT_AXI = 0x0004 } pisp_fe_dirty; typedef struct { uint32_t enables; uint8_t bayer_order; uint8_t pad[3]; } pisp_fe_global_config; typedef struct { /* burst length minus one, in the range 0..15; OR'd with flags */ uint8_t maxlen_flags; /* { prot[2:0], cache[3:0] } fields */ uint8_t cache_prot; /* QoS (only 4 LS bits are used) */ uint16_t qos; } pisp_fe_input_axi_config; typedef struct { /* burst length minus one, in the range 0..15; OR'd with flags */ uint8_t maxlen_flags; /* { prot[2:0], cache[3:0] } fields */ uint8_t cache_prot; /* QoS (4 bitfields of 4 bits each for different panic levels) */ uint16_t qos; /* For Panic mode: Output FIFO panic threshold */ uint16_t thresh; /* For Panic mode: Output FIFO statistics throttle threshold */ uint16_t throttle; } pisp_fe_output_axi_config; typedef struct { uint8_t streaming; uint8_t pad[3]; pisp_image_format_config format; pisp_fe_input_axi_config axi; /* Extra cycles delay before issuing each burst request */ uint8_t holdoff; uint8_t pad2[3]; } pisp_fe_input_config; typedef struct { pisp_image_format_config format; uint16_t ilines; uint8_t pad[2]; } pisp_fe_output_config; typedef struct { uint32_t addr_lo; uint32_t addr_hi; uint16_t frame_id; uint16_t pad; } pisp_fe_input_buffer_config; #define PISP_FE_DECOMPAND_LUT_SIZE 65 typedef struct { uint16_t lut[PISP_FE_DECOMPAND_LUT_SIZE]; uint16_t pad; } pisp_fe_decompand_config; typedef struct { uint8_t coeff_level; uint8_t coeff_range; uint8_t coeff_range2; #define PISP_FE_DPC_FLAG_FOLDBACK 1 #define PISP_FE_DPC_FLAG_VFLAG 2 uint8_t flags; } pisp_fe_dpc_config; #define PISP_FE_LSC_LUT_SIZE 16 typedef struct { uint8_t shift; uint8_t pad0; uint16_t scale; uint16_t centre_x; uint16_t centre_y; uint16_t lut[PISP_FE_LSC_LUT_SIZE]; } pisp_fe_lsc_config; typedef struct { uint16_t gain_r; uint16_t gain_g; uint16_t gain_b; uint8_t maxflag; uint8_t pad; } pisp_fe_rgby_config; typedef struct { uint16_t offset_x; uint16_t offset_y; uint16_t size_x; uint16_t size_y; /* each weight only 4 bits */ uint8_t weights[PISP_AGC_STATS_NUM_ZONES / 2]; uint16_t row_offset_x; uint16_t row_offset_y; uint16_t row_size_x; uint16_t row_size_y; uint8_t row_shift; uint8_t float_shift; uint8_t pad1[2]; } pisp_fe_agc_stats_config; typedef struct { uint16_t offset_x; uint16_t offset_y; uint16_t size_x; uint16_t size_y; uint8_t shift; uint8_t pad[3]; uint16_t r_lo; uint16_t r_hi; uint16_t g_lo; uint16_t g_hi; uint16_t b_lo; uint16_t b_hi; } pisp_fe_awb_stats_config; typedef struct { uint16_t offset_x; uint16_t offset_y; uint16_t size_x; uint16_t size_y; } pisp_fe_floating_stats_region; typedef struct { pisp_fe_floating_stats_region regions[PISP_FLOATING_STATS_NUM_ZONES]; } pisp_fe_floating_stats_config; #define PISP_FE_CDAF_NUM_WEIGHTS 8 typedef struct { uint16_t noise_constant; uint16_t noise_slope; uint16_t offset_x; uint16_t offset_y; uint16_t size_x; uint16_t size_y; uint16_t skip_x; uint16_t skip_y; uint32_t mode; } pisp_fe_cdaf_stats_config; typedef struct { uint32_t addr_lo; uint32_t addr_hi; } pisp_fe_stats_buffer_config; typedef struct { uint16_t offset_x; uint16_t offset_y; uint16_t width; uint16_t height; } pisp_fe_crop_config; typedef enum { DOWNSCALE_BAYER = 1, /* downscale the four Bayer components independently... */ DOWNSCALE_BIN = 2 /* ...without trying to preserve their spatial relationship */ } pisp_fe_downscale_flags; typedef struct { uint8_t xin; uint8_t xout; uint8_t yin; uint8_t yout; uint8_t flags; /* enum pisp_fe_downscale_flags */ uint8_t pad[3]; uint16_t output_width; uint16_t output_height; } pisp_fe_downscale_config; typedef struct { uint32_t addr_lo; uint32_t addr_hi; } pisp_fe_output_buffer_config; /* Each of the two output channels/branches: */ typedef struct { pisp_fe_crop_config crop; pisp_fe_downscale_config downscale; pisp_compress_config compress; pisp_fe_output_config output; uint32_t pad; } pisp_fe_output_branch_config; /* And finally one to rule them all: */ typedef struct { /* I/O configuration: */ pisp_fe_stats_buffer_config stats_buffer; pisp_fe_output_buffer_config output_buffer[PISP_FE_NUM_OUTPUTS]; pisp_fe_input_buffer_config input_buffer; /* processing configuration: */ pisp_fe_global_config global; pisp_fe_input_config input; pisp_decompress_config decompress; pisp_fe_decompand_config decompand; pisp_bla_config bla; pisp_fe_dpc_config dpc; pisp_fe_crop_config stats_crop; uint32_t spare1; /* placeholder for future decimate configuration */ pisp_bla_config blc; pisp_fe_rgby_config rgby; pisp_fe_lsc_config lsc; pisp_fe_agc_stats_config agc_stats; pisp_fe_awb_stats_config awb_stats; pisp_fe_cdaf_stats_config cdaf_stats; pisp_fe_floating_stats_config floating_stats; pisp_fe_output_axi_config output_axi; pisp_fe_output_branch_config ch[PISP_FE_NUM_OUTPUTS]; /* non-register fields: */ uint32_t dirty_flags; /* these use pisp_fe_enable */ uint32_t dirty_flags_extra; /* these use pisp_fe_dirty */ } pisp_fe_config; #endif /* _PISP_FE_CONFIG_ */ raspberrypi-libpisp-9ba67e6/src/libpisp/frontend/pisp_statistics.h000066400000000000000000000032461507172066400256240ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */ /* * PY PiSP Front End statistics definitions * * Copyright (C) 2021 - 2023, Raspberry Pi Ltd * */ #ifndef _PISP_FE_STATISTICS_H_ #define _PISP_FE_STATISTICS_H_ #include #define PISP_FLOATING_STATS_NUM_ZONES 4 #define PISP_AGC_STATS_NUM_BINS 1024 #define PISP_AGC_STATS_SIZE 16 #define PISP_AGC_STATS_NUM_ZONES (PISP_AGC_STATS_SIZE * PISP_AGC_STATS_SIZE) #define PISP_AGC_STATS_NUM_ROW_SUMS 512 typedef struct { uint64_t Y_sum; uint32_t counted; uint32_t pad; } pisp_agc_statistics_zone; typedef struct { uint32_t row_sums[PISP_AGC_STATS_NUM_ROW_SUMS]; /* * 32-bits per bin means an image (just less than) 16384x16384 pixels * in size can weight every pixel from 0 to 15. */ uint32_t histogram[PISP_AGC_STATS_NUM_BINS]; pisp_agc_statistics_zone floating[PISP_FLOATING_STATS_NUM_ZONES]; } pisp_agc_statistics; #define PISP_AWB_STATS_SIZE 32 #define PISP_AWB_STATS_NUM_ZONES (PISP_AWB_STATS_SIZE * PISP_AWB_STATS_SIZE) typedef struct { uint32_t R_sum; uint32_t G_sum; uint32_t B_sum; uint32_t counted; } pisp_awb_statistics_zone; typedef struct { pisp_awb_statistics_zone zones[PISP_AWB_STATS_NUM_ZONES]; pisp_awb_statistics_zone floating[PISP_FLOATING_STATS_NUM_ZONES]; } pisp_awb_statistics; #define PISP_CDAF_STATS_SIZE 8 #define PISP_CDAF_STATS_NUM_FOMS (PISP_CDAF_STATS_SIZE * PISP_CDAF_STATS_SIZE) typedef struct { uint64_t foms[PISP_CDAF_STATS_NUM_FOMS]; uint64_t floating[PISP_FLOATING_STATS_NUM_ZONES]; } pisp_cdaf_statistics; typedef struct { pisp_awb_statistics awb; pisp_agc_statistics agc; pisp_cdaf_statistics cdaf; } pisp_statistics; #endif /* _PISP_FE_STATISTICS_H_ */ raspberrypi-libpisp-9ba67e6/src/libpisp/meson.build000066400000000000000000000002241507172066400225420ustar00rootroot00000000000000# SPDX-License-Identifier: CC0-1.0 # Copyright (C) 2023, Raspberry Pi Ltd subdir('common') subdir('variants') subdir('frontend') subdir('backend') raspberrypi-libpisp-9ba67e6/src/libpisp/variants/000077500000000000000000000000001507172066400222315ustar00rootroot00000000000000raspberrypi-libpisp-9ba67e6/src/libpisp/variants/meson.build000066400000000000000000000004401507172066400243710ustar00rootroot00000000000000# SPDX-License-Identifier: CC0-1.0 # Copyright (C) 2023, Raspberry Pi Ltd pisp_sources += files([ 'variant.cpp', ]) variants_headers = files([ 'variant.hpp' ]) variants_include_dir = pisp_include_dir / 'variants' install_headers(variants_headers, subdir: variants_include_dir) raspberrypi-libpisp-9ba67e6/src/libpisp/variants/variant.cpp000066400000000000000000000047741507172066400244150ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (C) 2021 - 2023, Raspberry Pi Ltd * * pisp_variantPiSP variant configuration definitions */ #include "variant.hpp" #include namespace libpisp { namespace { const PiSPVariant variant_error {}; } // namespace const PiSPVariant BCM2712_C0 { "BCM2712_C0", /* Name */ 0x00114666, /* FrontEnd version */ 0x02252700, /* BackEnd version */ 2, /* Number of FrontEnds */ 1, /* Number of BackEnds */ { 2, 2 }, /* Number of branches per FrontEnd */ { 6144, 6144 }, /* Maximum statistics width per FrontEnd branch */ {{ { true, true }, { true, true } }}, /* Availability of downscalers per FrontEnd branch */ {{ { 6144, 4096 }, { 6144, 4096 } }}, /* Maximum width of downscalers per FrontEnd branch */ 640, /* Maximum tile size of the BackEnd */ { 2 }, /* Number of branches per BackEnd */ {{ { false, false } }}, /* Availability of integral image output per BackEnd branch */ {{ { false, true } }}, /* Availability of downscalers per BackEnd branch */ false, /* BackEnd RGB32 output format support */ }; const PiSPVariant BCM2712_D0 { "BCM2712_D0", /* Name */ 0x00114666, /* FrontEnd version */ 0x02252701, /* BackEnd version */ 2, /* Number of FrontEnds */ 1, /* Number of BackEnds */ { 2, 2 }, /* Number of branches per FrontEnd */ { 6144, 6144 }, /* Maximum statistics width per FrontEnd branch */ {{ { true, true }, { true, true } }}, /* Availability of downscalers per FrontEnd branch */ {{ { 6144, 4096 }, { 6144, 4096 } }}, /* Maximum width of downscalers per FrontEnd branch */ 640, /* Maximum tile size of the BackEnd */ { 2 }, /* Number of branches per BackEnd */ {{ { false, false } }}, /* Availability of integral image output per BackEnd branch */ {{ { false, true } }}, /* Availability of downscalers per BackEnd branch */ true, /* BackEnd RGB32 output format support */ }; const std::vector &get_variants() { static const std::vector variants { BCM2712_C0, BCM2712_D0 }; return variants; } const PiSPVariant &get_variant(unsigned int fe_version, unsigned int be_version) { const std::vector &variants = get_variants(); auto it = std::find_if(variants.begin(), variants.end(), [fe_version, be_version](const auto &hw) { return hw.FrontEndVersion() == fe_version && hw.BackEndVersion() == be_version; }); if (it == variants.end()) return variant_error; return *it; } } // namespace libpisp raspberrypi-libpisp-9ba67e6/src/libpisp/variants/variant.hpp000066400000000000000000000111531507172066400244070ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (C) 2021 - 2023, Raspberry Pi Ltd * * pisp_variant.hpp - PiSP variant configuration definitions */ #pragma once #include #include #include // This header files defines functions that can be called to discover the details about the specific // implementation of the PiSP that is being used. Whatever the build system being used is, it needs // to select the correct C file in this directory that implements these functions. namespace libpisp { class PiSPVariant { private: static constexpr unsigned int MaxFrontEnds = 4; static constexpr unsigned int MaxBackEnds = 4; static constexpr unsigned int MaxFrontEndBranches = 4; static constexpr unsigned int MaxBackEndBranches = 4; std::string name_; unsigned int fe_version_; unsigned int be_version_; unsigned int num_fe_; unsigned int num_be_; std::array num_fe_branches_; std::array fe_stats_max_width_; std::array, MaxFrontEnds> fe_downscaler_; std::array, MaxFrontEnds> fe_downscaler_max_width_; unsigned int be_max_tile_width_; std::array num_be_branches_; std::array, MaxBackEnds> be_integral_images_; std::array, MaxBackEnds> be_downscaler_; bool be_rgb32_support_; public: PiSPVariant(const std::string &name, unsigned int fe_version, unsigned int be_version, unsigned int num_fe, unsigned int num_be, const std::array &num_fe_branches, const std::array &fe_stats_max_width, const std::array, MaxFrontEnds> &fe_downscaler, const std::array, MaxFrontEnds> fe_downscaler_max_width, unsigned int be_max_tile_width, const std::array &num_be_branches, const std::array, MaxBackEnds> &be_integral_images, const std::array, MaxBackEnds> &be_downscaler, bool be_rgb32_support) : name_(name), fe_version_(fe_version), be_version_(be_version), num_fe_(num_fe), num_be_(num_be), num_fe_branches_(num_fe_branches), fe_stats_max_width_(fe_stats_max_width), fe_downscaler_(fe_downscaler), fe_downscaler_max_width_(fe_downscaler_max_width), be_max_tile_width_(be_max_tile_width), num_be_branches_(num_be_branches), be_integral_images_(be_integral_images), be_downscaler_(be_downscaler), be_rgb32_support_(be_rgb32_support) { } // For error handling PiSPVariant() : name_("INVALID"), num_fe_(0), num_be_(0), num_fe_branches_({ 0 }), num_be_branches_({ 0 }) { } const std::string &Name() const { return name_; } unsigned int BackEndVersion() const { return be_version_; } unsigned int FrontEndVersion() const { return fe_version_; } unsigned int NumFrontEnds() const { return num_fe_; } unsigned int NumBackEnds() const { return num_be_; } unsigned int FrontEndNumBranches(unsigned int id) const { return id < num_fe_ ? num_fe_branches_[id] : 0; } unsigned int FrontEndStatsMaxWidth(unsigned int id) const { return (id < num_fe_) ? fe_stats_max_width_[id] : 0; } unsigned int FrontEndDownscalerMaxWidth(unsigned int id, unsigned int branch) const { return (id < num_fe_ && branch < num_fe_branches_[id]) ? fe_downscaler_max_width_[id][branch] : 0; } bool FrontEndDownscalerAvailable(unsigned int id, unsigned int branch) const { return (id < num_fe_ && branch < num_fe_branches_[id]) ? fe_downscaler_[id][branch] : 0; } unsigned int BackEndNumBranches(unsigned int id) const { return id < num_be_ ? num_be_branches_[id] : 0; } unsigned int BackEndMaxTileWidth(unsigned int id) const { return (id < num_be_) ? be_max_tile_width_ : 0; } bool BackEndIntegralImage(unsigned int id, unsigned int branch) const { return (id < num_be_ && branch < num_be_branches_[id]) ? be_integral_images_[id][branch] : 0; } bool BackEndDownscalerAvailable(unsigned int id, unsigned int branch) const { return (id < num_be_ && branch < num_be_branches_[id]) ? be_downscaler_[id][branch] : 0; } bool BackendRGB32Supported(unsigned int id) const { return id < num_be_ ? be_rgb32_support_ : false; } }; const std::vector &get_variants(); const PiSPVariant &get_variant(unsigned int fe_version, unsigned int be_version); extern const PiSPVariant BCM2712_C0; extern const PiSPVariant BCM2712_D0; } // namespace libpisp raspberrypi-libpisp-9ba67e6/src/meson.build000066400000000000000000000042621507172066400211060ustar00rootroot00000000000000# SPDX-License-Identifier: CC0-1.0 # Copyright (C) 2023, Raspberry Pi Ltd pisp_sources = [] inc_dirs = ['.', 'libpisp'] pisp_include_dir = 'libpisp' subdir('libpisp') pisp_sources += frontend_sources pisp_sources += backend_sources pisp_deps = [ dependency('nlohmann_json', fallback : ['nlohmann_json', 'nlohmann_json_dep']), dependency('threads') ] # Meson version >= 0.64 can simply use dependency('dl') for this, but we don't want to bump up the min version just yet. dl_dep = meson.get_compiler('c').find_library('dl', required : true) pisp_deps += dl_dep logging_dep = dependency('boost', modules : ['log', 'log_setup', 'thread', 'system'], required : get_option('logging')) if logging_dep.found() logging_args = ['-DPISP_LOGGING_ENABLE=1', '-DBOOST_BIND_GLOBAL_PLACEHOLDERS', '-DBOOST_LOG_DYN_LINK=1'] pisp_deps += logging_dep else logging_args = '-DPISP_LOGGING_ENABLE=0' endif add_project_arguments(logging_args, language : 'cpp') # Needed to avoid (erroneous) warnings on the use of addresses of fields in __attribute__((packed)) structs. add_project_arguments('-Wno-address-of-packed-member', language : 'cpp') # Generate a version string: version_cmd = [meson.project_source_root() / 'utils' / 'version.py', meson.project_version()] version_template = meson.project_source_root() / 'utils' / 'version.cpp.in' version_cpp = vcs_tag(command : version_cmd, input : version_template, output : 'version.cpp', fallback : meson.project_version()) pisp_sources += version_cpp subdir('helpers') libpisp = library( meson.project_name(), pisp_sources, version : meson.project_version(), include_directories : include_directories(inc_dirs), name_prefix : '', install : true, build_rpath : meson.project_source_root(), dependencies : pisp_deps, ) libpisp_dep = declare_dependency( include_directories : include_directories(inc_dirs), link_with : libpisp, sources : [pisp_build_config] ) pkg_mod = import('pkgconfig') pkg_mod.generate(libpisp, description : 'PiSP Library', subdirs : 'libpisp') if get_option('examples') subdir('examples') endifraspberrypi-libpisp-9ba67e6/subprojects/000077500000000000000000000000001507172066400205145ustar00rootroot00000000000000raspberrypi-libpisp-9ba67e6/subprojects/cxxopts.wrap000066400000000000000000000011201507172066400231110ustar00rootroot00000000000000[wrap-file] directory = cxxopts-3.2.0 source_url = https://github.com/jarro2783/cxxopts/archive/v3.2.0.tar.gz source_filename = cxxopts-3.2.0.tar.gz source_hash = 9f43fa972532e5df6c5fd5ad0f5bac606cdec541ccaf1732463d8070bbb7f03b patch_filename = cxxopts_3.2.0-1_patch.zip patch_url = https://wrapdb.mesonbuild.com/v2/cxxopts_3.2.0-1/get_patch patch_hash = 7d8c5d49dc7f825fd153d2653a44e5cdb1a33e1591318e94007cf43acdaffc58 source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/cxxopts_3.2.0-1/cxxopts-3.2.0.tar.gz wrapdb_version = 3.2.0-1 [provide] cxxopts = cxxopts_dep raspberrypi-libpisp-9ba67e6/subprojects/nlohmann_json.wrap000066400000000000000000000005401507172066400242510ustar00rootroot00000000000000[wrap-file] directory = nlohmann_json-3.11.2 lead_directory_missing = true source_url = https://github.com/nlohmann/json/releases/download/v3.11.2/include.zip source_filename = nlohmann_json-3.11.2.zip source_hash = e5c7a9f49a16814be27e4ed0ee900ecd0092bfb7dbfca65b5a421b774dccaaed wrapdb_version = 3.11.2-1 [provide] nlohmann_json = nlohmann_json_dep raspberrypi-libpisp-9ba67e6/utils/000077500000000000000000000000001507172066400173115ustar00rootroot00000000000000raspberrypi-libpisp-9ba67e6/utils/checkstyle.py000077500000000000000000000523751507172066400220400ustar00rootroot00000000000000#!/usr/bin/python3 # SPDX-License-Identifier: GPL-2.0-or-later # # checkstyle.py - A patch style checker script based on clang-format # # Copied from libcamera's implementation Authored by # Laurent Pinchart # Copyright (C) 2018, Google Inc. # # https://git.libcamera.org/libcamera/libcamera.git/tree/utils/checkstyle.py import argparse import difflib import fnmatch import os.path import re import shutil import subprocess import sys dependencies = { 'clang-format': True, 'git': True, } # ------------------------------------------------------------------------------ # Colour terminal handling # class Colours: Default = 0 Black = 0 Red = 31 Green = 32 Yellow = 33 Blue = 34 Magenta = 35 Cyan = 36 LightGrey = 37 DarkGrey = 90 LightRed = 91 LightGreen = 92 Lightyellow = 93 LightBlue = 94 LightMagenta = 95 LightCyan = 96 White = 97 @staticmethod def fg(colour): if sys.stdout.isatty(): return '\033[%um' % colour else: return '' @staticmethod def bg(colour): if sys.stdout.isatty(): return '\033[%um' % (colour + 10) else: return '' @staticmethod def reset(): if sys.stdout.isatty(): return '\033[0m' else: return '' # ------------------------------------------------------------------------------ # Diff parsing, handling and printing # class DiffHunkSide(object): """A side of a diff hunk, recording line numbers""" def __init__(self, start): self.start = start self.touched = [] self.untouched = [] def __len__(self): return len(self.touched) + len(self.untouched) class DiffHunk(object): diff_header_regex = re.compile( r'@@ -([0-9]+),?([0-9]+)? \+([0-9]+),?([0-9]+)? @@') def __init__(self, line): match = DiffHunk.diff_header_regex.match(line) if not match: raise RuntimeError("Malformed diff hunk header '%s'" % line) self.__from_line = int(match.group(1)) self.__to_line = int(match.group(3)) self.__from = DiffHunkSide(self.__from_line) self.__to = DiffHunkSide(self.__to_line) self.lines = [] def __repr__(self): s = '%s@@ -%u,%u +%u,%u @@\n' % \ (Colours.fg(Colours.Cyan), self.__from.start, len(self.__from), self.__to.start, len(self.__to)) for line in self.lines: if line[0] == '-': s += Colours.fg(Colours.Red) elif line[0] == '+': s += Colours.fg(Colours.Green) if line[0] == '-': spaces = 0 for i in range(len(line)): if line[-i - 1].isspace(): spaces += 1 else: break spaces = len(line) - spaces line = line[0:spaces] + Colours.bg(Colours.Red) + line[spaces:] s += line s += Colours.reset() s += '\n' return s[:-1] def append(self, line): if line[0] == ' ': self.__from.untouched.append(self.__from_line) self.__from_line += 1 self.__to.untouched.append(self.__to_line) self.__to_line += 1 elif line[0] == '-': self.__from.touched.append(self.__from_line) self.__from_line += 1 elif line[0] == '+': self.__to.touched.append(self.__to_line) self.__to_line += 1 self.lines.append(line.rstrip('\n')) def intersects(self, lines): for line in lines: if line in self.__from.touched: return True return False def side(self, side): if side == 'from': return self.__from else: return self.__to def parse_diff(diff): hunks = [] hunk = None for line in diff: if line.startswith('@@'): if hunk: hunks.append(hunk) hunk = DiffHunk(line) elif hunk is not None: hunk.append(line) if hunk: hunks.append(hunk) return hunks # ------------------------------------------------------------------------------ # Commit, Staged Changes & Amendments # class CommitFile: def __init__(self, name): info = name.split() self.__status = info[0][0] # For renamed files, store the new name if self.__status == 'R': self.__filename = info[2] else: self.__filename = info[1] @property def filename(self): return self.__filename @property def status(self): return self.__status class Commit: def __init__(self, commit): self.commit = commit self._parse() def _parse(self): # Get the commit title and list of files. ret = subprocess.run(['git', 'show', '--pretty=oneline', '--name-status', self.commit], stdout=subprocess.PIPE).stdout.decode('utf-8') files = ret.splitlines() self._files = [CommitFile(f) for f in files[1:]] self._title = files[0] def files(self, filter='AMR'): return [f.filename for f in self._files if f.status in filter] @property def title(self): return self._title def get_diff(self, top_level, filename): diff = subprocess.run(['git', 'diff', '%s~..%s' % (self.commit, self.commit), '--', '%s/%s' % (top_level, filename)], stdout=subprocess.PIPE).stdout.decode('utf-8') return parse_diff(diff.splitlines(True)) def get_file(self, filename): return subprocess.run(['git', 'show', '%s:%s' % (self.commit, filename)], stdout=subprocess.PIPE).stdout.decode('utf-8') class StagedChanges(Commit): def __init__(self): Commit.__init__(self, '') def _parse(self): ret = subprocess.run(['git', 'diff', '--staged', '--name-status'], stdout=subprocess.PIPE).stdout.decode('utf-8') self._title = "Staged changes" self._files = [CommitFile(f) for f in ret.splitlines()] def get_diff(self, top_level, filename): diff = subprocess.run(['git', 'diff', '--staged', '--', '%s/%s' % (top_level, filename)], stdout=subprocess.PIPE).stdout.decode('utf-8') return parse_diff(diff.splitlines(True)) class Amendment(StagedChanges): def __init__(self): StagedChanges.__init__(self) def _parse(self): # Create a title using HEAD commit ret = subprocess.run(['git', 'show', '--pretty=oneline', '--no-patch'], stdout=subprocess.PIPE).stdout.decode('utf-8') self._title = 'Amendment of ' + ret.strip() # Extract the list of modified files ret = subprocess.run(['git', 'diff', '--staged', '--name-status', 'HEAD~'], stdout=subprocess.PIPE).stdout.decode('utf-8') self._files = [CommitFile(f) for f in ret.splitlines()] def get_diff(self, top_level, filename): diff = subprocess.run(['git', 'diff', '--staged', 'HEAD~', '--', '%s/%s' % (top_level, filename)], stdout=subprocess.PIPE).stdout.decode('utf-8') return parse_diff(diff.splitlines(True)) # ------------------------------------------------------------------------------ # Helpers # class ClassRegistry(type): def __new__(cls, clsname, bases, attrs): newclass = super().__new__(cls, clsname, bases, attrs) if bases: bases[0].subclasses.append(newclass) return newclass # ------------------------------------------------------------------------------ # Commit Checkers # class CommitChecker(metaclass=ClassRegistry): subclasses = [] def __init__(self): pass # # Class methods # @classmethod def checkers(cls): for checker in cls.subclasses: yield checker class CommitIssue(object): def __init__(self, msg): self.msg = msg # ------------------------------------------------------------------------------ # Style Checkers # class StyleChecker(metaclass=ClassRegistry): subclasses = [] def __init__(self): pass # # Class methods # @classmethod def checkers(cls, filename): for checker in cls.subclasses: if checker.supports(filename): yield checker @classmethod def supports(cls, filename): for pattern in cls.patterns: if fnmatch.fnmatch(os.path.basename(filename), pattern): return True return False @classmethod def all_patterns(cls): patterns = set() for checker in cls.subclasses: patterns.update(checker.patterns) return patterns class StyleIssue(object): def __init__(self, line_number, line, msg): self.line_number = line_number self.line = line self.msg = msg class IncludeChecker(StyleChecker): patterns = ('*.cpp', '*.h', '*.hpp') headers = ('assert', 'ctype', 'errno', 'fenv', 'float', 'inttypes', 'limits', 'locale', 'setjmp', 'signal', 'stdarg', 'stddef', 'stdint', 'stdio', 'stdlib', 'string', 'time', 'uchar', 'wchar', 'wctype') include_regex = re.compile('^#include ') def __init__(self, content): super().__init__() self.__content = content def check(self, line_numbers): issues = [] for line_number in line_numbers: line = self.__content[line_number - 1] match = IncludeChecker.include_regex.match(line) if not match: continue header = match.group(1) if header not in IncludeChecker.headers: continue issues.append(StyleIssue(line_number, line, 'C compatibility header <%s.h> is preferred' % header)) return issues class Pep8Checker(StyleChecker): patterns = ('*.py',) results_regex = re.compile('stdin:([0-9]+):([0-9]+)(.*)') def __init__(self, content): super().__init__() self.__content = content def check(self, line_numbers): issues = [] data = ''.join(self.__content).encode('utf-8') try: ret = subprocess.run(['pycodestyle', '--ignore=E501', '-'], input=data, stdout=subprocess.PIPE) except FileNotFoundError: issues.append(StyleIssue( 0, None, "Please install pycodestyle to validate python additions")) return issues results = ret.stdout.decode('utf-8').splitlines() for item in results: search = re.search(Pep8Checker.results_regex, item) line_number = int(search.group(1)) position = int(search.group(2)) msg = search.group(3) if line_number in line_numbers: line = self.__content[line_number - 1] issues.append(StyleIssue(line_number, line, msg)) return issues class ShellChecker(StyleChecker): patterns = ('*.sh',) results_line_regex = re.compile('In - line ([0-9]+):') def __init__(self, content): super().__init__() self.__content = content def check(self, line_numbers): issues = [] data = ''.join(self.__content).encode('utf-8') try: ret = subprocess.run(['shellcheck', '-Cnever', '-'], input=data, stdout=subprocess.PIPE) except FileNotFoundError: issues.append(StyleIssue( 0, None, "Please install shellcheck to validate shell script additions")) return issues results = ret.stdout.decode('utf-8').splitlines() for nr, item in enumerate(results): search = re.search(ShellChecker.results_line_regex, item) if search is None: continue line_number = int(search.group(1)) line = results[nr + 1] msg = results[nr + 2] # Determined, but not yet used position = msg.find('^') + 1 if line_number in line_numbers: issues.append(StyleIssue(line_number, line, msg)) return issues # ------------------------------------------------------------------------------ # Formatters # class Formatter(metaclass=ClassRegistry): subclasses = [] def __init__(self): pass # # Class methods # @classmethod def formatters(cls, filename): for formatter in cls.subclasses: if formatter.supports(filename): yield formatter @classmethod def supports(cls, filename): for pattern in cls.patterns: if fnmatch.fnmatch(os.path.basename(filename), pattern): return True return False @classmethod def all_patterns(cls): patterns = set() for formatter in cls.subclasses: patterns.update(formatter.patterns) return patterns class CLangFormatter(Formatter): patterns = ('*.c', '*.cpp', '*.h', '*.hpp') @classmethod def format(cls, filename, data): ret = subprocess.run(['clang-format', '-style=file', '-assume-filename=' + filename], input=data.encode('utf-8'), stdout=subprocess.PIPE) return ret.stdout.decode('utf-8') class IncludeOrderFormatter(Formatter): patterns = ('*.cpp', '*.h', '*.hpp') include_regex = re.compile('^#include ["<]([^">]*)[">]') @classmethod def format(cls, filename, data): lines = [] includes = [] # Parse blocks of #include statements, and output them as a sorted list # when we reach a non #include statement. for line in data.split('\n'): match = IncludeOrderFormatter.include_regex.match(line) if match: # If the current line is an #include statement, add it to the # includes group and continue to the next line. includes.append((line, match.group(1))) continue # The current line is not an #include statement, output the sorted # stashed includes first, and then the current line. if len(includes): includes.sort(key=lambda i: i[1]) for include in includes: lines.append(include[0]) includes = [] lines.append(line) # In the unlikely case the file ends with an #include statement, make # sure we output the stashed includes. if len(includes): includes.sort(key=lambda i: i[1]) for include in includes: lines.append(include[0]) includes = [] return '\n'.join(lines) class StripTrailingSpaceFormatter(Formatter): patterns = ('*.c', '*.cpp', '*.h', '*.hpp', '*.py', 'CMakelists.txt') @classmethod def format(cls, filename, data): lines = data.split('\n') for i in range(len(lines)): lines[i] = lines[i].rstrip() + '\n' return ''.join(lines) # ------------------------------------------------------------------------------ # Style checking # def check_file(top_level, commit, filename): # Extract the line numbers touched by the commit. commit_diff = commit.get_diff(top_level, filename) lines = [] for hunk in commit_diff: lines.extend(hunk.side('to').touched) # Skip commits that don't add any line. if len(lines) == 0: return 0 # Format the file after the commit with all formatters and compute the diff # between the unformatted and formatted contents. after = commit.get_file(filename) formatted = after for formatter in Formatter.formatters(filename): formatted = formatter.format(filename, formatted) after = after.splitlines(True) formatted = formatted.splitlines(True) diff = difflib.unified_diff(after, formatted) # Split the diff in hunks, recording line number ranges for each hunk, and # filter out hunks that are not touched by the commit. formatted_diff = parse_diff(diff) formatted_diff = [ hunk for hunk in formatted_diff if hunk.intersects(lines)] # Check for code issues not related to formatting. issues = [] for checker in StyleChecker.checkers(filename): checker = checker(after) for hunk in commit_diff: issues += checker.check(hunk.side('to').touched) # Print the detected issues. if len(issues) == 0 and len(formatted_diff) == 0: return 0 print('%s---' % Colours.fg(Colours.Red), filename) print('%s+++' % Colours.fg(Colours.Green), filename) if len(formatted_diff): for hunk in formatted_diff: print(hunk) if len(issues): issues = sorted(issues, key=lambda i: i.line_number) for issue in issues: print('%s#%u: %s' % (Colours.fg(Colours.Yellow), issue.line_number, issue.msg)) if issue.line is not None: print('+%s%s' % (issue.line.rstrip(), Colours.reset())) return len(formatted_diff) + len(issues) def check_style(top_level, commit): separator = '-' * len(commit.title) print(separator) print(commit.title) print(separator) issues = 0 # Apply the commit checkers first. for checker in CommitChecker.checkers(): for issue in checker.check(commit, top_level): print('%s%s%s' % (Colours.fg(Colours.Yellow), issue.msg, Colours.reset())) issues += 1 # Filter out files we have no checker for. patterns = set() patterns.update(StyleChecker.all_patterns()) patterns.update(Formatter.all_patterns()) files = [f for f in commit.files() if len( [p for p in patterns if fnmatch.fnmatch(os.path.basename(f), p)])] for f in files: issues += check_file(top_level, commit, f) if issues == 0: print("No issue detected") else: print('---') print("%u potential %s detected, please review" % (issues, 'issue' if issues == 1 else 'issues')) return issues def extract_commits(revs): """Extract a list of commits on which to operate from a revision or revision range. """ ret = subprocess.run(['git', 'rev-parse', revs], stdout=subprocess.PIPE, stderr=subprocess.PIPE) if ret.returncode != 0: print(ret.stderr.decode('utf-8').splitlines()[0]) return [] revlist = ret.stdout.decode('utf-8').splitlines() # If the revlist contains more than one item, pass it to git rev-list to list # each commit individually. if len(revlist) > 1: ret = subprocess.run(['git', 'rev-list', *revlist], stdout=subprocess.PIPE) revlist = ret.stdout.decode('utf-8').splitlines() revlist.reverse() return [Commit(x) for x in revlist] def git_top_level(): """Get the absolute path of the git top-level directory.""" ret = subprocess.run(['git', 'rev-parse', '--show-toplevel'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) if ret.returncode != 0: print(ret.stderr.decode('utf-8').splitlines()[0]) return None return ret.stdout.decode('utf-8').strip() def main(argv): # Parse command line arguments parser = argparse.ArgumentParser() parser.add_argument('--staged', '-s', action='store_true', help='Include the changes in the index. Defaults to False') parser.add_argument('--amend', '-a', action='store_true', help='Include changes in the index and the previous patch combined. Defaults to False') parser.add_argument('revision_range', type=str, default=None, nargs='?', help='Revision range (as defined by git rev-parse). Defaults to HEAD if not specified.') args = parser.parse_args(argv[1:]) # Check for required dependencies. for command, mandatory in dependencies.items(): found = shutil.which(command) if mandatory and not found: print("Executable %s not found" % command) return 1 dependencies[command] = found # Get the top level directory to pass absolute file names to git diff # commands, in order to support execution from subdirectories of the git # tree. top_level = git_top_level() if top_level is None: return 1 commits = [] if args.staged: commits.append(StagedChanges()) if args.amend: commits.append(Amendment()) # If none of --staged or --amend was passed if len(commits) == 0: # And no revisions were passed, then default to HEAD if not args.revision_range: args.revision_range = 'HEAD' if args.revision_range: commits += extract_commits(args.revision_range) issues = 0 for commit in commits: issues += check_style(top_level, commit) print('') if issues: return 1 else: return 0 if __name__ == '__main__': sys.exit(main(sys.argv)) raspberrypi-libpisp-9ba67e6/utils/colourspace_calcs.py000066400000000000000000000045241507172066400233540ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # # Copyright (C) 2023, Raspberry Pi Ltd # # colourspace_calcs.py - Colourspace matrix generation # # This short utility generates colour space matrices and offsets for # inclusion in the backend_default_config.json file. import numpy as np BT601 = np.array([[0.299, 0.5870, 0.1140], [-0.168736, -0.331264, 0.5], [0.5, -0.418688, -0.081312]]) REC709 = np.array([[0.2126, 0.7152, 0.0722], [-0.1146, -0.3854, 0.5], [0.5, -0.4542, -0.0458]]) REC2020 = np.array([[0.2627, 0.6780, 0.0593], [-0.13963006, -0.36036994, 0.5], [0.5, -0.4597857, -0.0402143]]) colour_spaces = {"select": "default"} def flatten(array): return [round(num) for num in list(array.flatten())] def add_entry(name, M, limited): offsets = np.array([0, 128, 128]) scaling = np.array([[(235 - 16) / 255, 0, 0], [0, (240 - 16) / 255, 0], [0, 0, (240 - 16) / 255]]) if limited: offsets = np.array([16, 128, 128]) M = np.matmul(scaling, M) Mi = np.linalg.inv(M) colour_spaces[name] = {} colour_spaces[name]["ycbcr"] = {} colour_spaces[name]["ycbcr"]["coeffs"] = flatten(M * 1024) colour_spaces[name]["ycbcr"]["offsets"] = flatten(offsets * (2 ** 18)) colour_spaces[name]["ycbcr_inverse"] = {} colour_spaces[name]["ycbcr_inverse"]["coeffs"] = flatten(Mi * 1024) inv_offsets = np.rint(np.dot(Mi, -offsets) * (2 ** 18)) colour_spaces[name]["ycbcr_inverse"]["offsets"] = flatten(inv_offsets) if inv_offsets.min() < -2 ** 26 or inv_offsets.max() >= 2 ** 26: print("WARNING:", name, "will overflow!") add_entry("default", BT601, limited=False) add_entry("jpeg", BT601, limited=False) add_entry("smpte170m", BT601, limited=True) add_entry("rec709", REC709, limited=True) add_entry("rec709_full", REC709, limited=False) add_entry("bt2020", REC2020, limited=True) add_entry("bt2020_full", REC2020, limited=False) def print_dict(d, indent=0): print("{") indent += 4 for i, (k, v) in enumerate(d.items()): if type(v) is dict: print(" " * indent, f'"{k}"', ": ", end='', sep='') print_dict(v, indent) else: print(" " * indent, f'"{k}"', ": ", v, end='', sep='') print("," if i < len(d) - 1 else "") indent -= 4 print(" " * indent, "}", end ='', sep='') final_dict = {"colour_encoding": colour_spaces} print_dict(final_dict) print() raspberrypi-libpisp-9ba67e6/utils/generate_filter.py000066400000000000000000000076011507172066400230260ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # # Copyright (C) 2023, Raspberry Pi Ltd # # generate_filter.py - Resample filter kernel generation # # Refer to the PiSP specification, for the register details import numpy as np import argparse # Lanczos (order, length) # Order is typically 2 or 3 def lanczos(order, N): x = np.linspace(-order, +order, N + 6, dtype=np.float64) h = np.where((x > -order) & (x < order), np.sinc(x) * np.sinc(x / order), 0) return h[3:-3] # Mitchell - Netravali filters (B, C, length) # B,C = (1,0) is the cubic B-spline # B,C = (1/3,1/3) is a good compromise # B,C = (0, 0.5) for Catmull-Rom def mitchell(B, C, N): x = np.linspace(-2, +2, N, dtype=np.float64) h = np.zeros(N) for i in range(N): ax = abs(x[i]) if ax < 1: h[i] = ((12 - 9 * B - 6 * C) * ax**3 + (-18 + 12 * B + 6 * C) * ax**2 + (6 - 2 * B)) / 6 elif (ax >= 1) and (ax < 2): h[i] = ((-B - 6 * C) * ax**3 + (6 * B + 30 * C) * ax**2 + (-12 * B - 48 * C) * ax + (8 * B + 24 * C)) / 6 return h # bicubic_spline(alpha, length); # alpha is typically set to -0.5 (Hermite spline) or -0.75 def bicubic_spline(a, N): x = np.linspace(-2, +2, N, dtype=np.float64) h = np.zeros(N) for i in range(N): ax = abs(x[i]) if ax <= 1: h[i] = (a + 2) * ax**3 - (a + 3) * ax**2 + 1 elif (ax > 1) and (ax < 2): h[i] = (a) * ax**3 - (5 * a) * ax**2 + (8 * a) * ax - (4 * a) return h def main(): parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter) parser.add_argument('--phases', metavar='P', type=int, help='Number of phases.', default=16) parser.add_argument('--taps', metavar='T', type=int, help='Number of filter taps per phase.', default=6) parser.add_argument('--precision', metavar='PR', type=int, help='Filter precision required.', default=10) parser.add_argument('--filter', type=str, metavar='F', help='Filter type and parameters, e.g.: \n' '"Mitchell, b = 0.333, c = 0.333"\n' '"Lanczos, order = 3"\n' '"bicubic_spline, a=-0.5"', required=True) args = parser.parse_args() phases = args.phases taps = args.taps precision = args.precision # Parse the filter string and pick out the needed parameters. filt = args.filter.split(',') params = {'a': 0., 'b': 0., 'c': 0., 'order': 0} for param in filt[1:]: p = param.replace(' ', '').split('=') params[p[0]] = type(params[p[0]])(p[1]) # Generate the filter. if (filt[0].lower() == 'mitchell'): filter = f'"Michell - Netravali (B = {params["b"]:.3f}, C = {params["c"]:.3f})": [\n' h = mitchell(params['b'], params['c'], phases * taps) elif (filt[0].lower() == 'lanczos'): filter = f'"Lanczos order {params["order"]}": [\n' h = lanczos(params['order'], phases * taps) elif (filt[0].lower() == 'bicubic_spline'): filter = f'"Bicubic-spline (a = {params["a"]:.3f})": [\n' h = bicubic_spline(params['a'], phases * taps) else: print(f'Invalid filter ({filt[0]}) selected!') exit() # Normalise and convert to fixed-point. h = h * phases / np.sum(h) h = np.rint(h * (1 << precision)).astype(np.int32) ppf = np.zeros((phases, taps), dtype=np.int32) for i in range(phases): # Pick out phases and flip array. ppf[i] = (h[i::phases])[::-1] # Make sure there is no DC change by adjusting the largest coefficients. max_index = np.nonzero(ppf[i] == ppf[i].max())[0] ppf[i, max_index] += (1 << precision) - np.int32(ppf[i].sum() / max_index.size) nl = '\n' for i in range(phases): phase = ', '.join([f'{c:>4}' for c in ppf[i, :]]) filter += f' {phase}{nl+"]" if i==phases-1 else ","+nl}' print(filter) if __name__ == '__main__': main() raspberrypi-libpisp-9ba67e6/utils/libpisp.wrap000066400000000000000000000002501507172066400216430ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (C) 2023, Raspberry Pi Ltd [wrap-git] url = https://github.com/raspberrypi/libpisp.git revision = v1.0.6 depth = 1 raspberrypi-libpisp-9ba67e6/utils/test_convert.py000066400000000000000000000205251507172066400224060ustar00rootroot00000000000000#!/usr/bin/python3 # SPDX-License-Identifier: BSD-2-Clause # # Copyright (C) 2025 Raspberry Pi Ltd # # test_convert.py - Test script for libpisp convert utility # import argparse import os import subprocess import sys import tempfile import shutil from pathlib import Path import hashlib class ConvertTester: def __init__(self, convert_binary, output_dir=None, input_dir=None, reference_dir=None): """Initialize the tester with the path to the convert binary.""" self.convert_binary = convert_binary self.output_dir = output_dir self.input_dir = input_dir self.reference_dir = reference_dir if not os.path.exists(convert_binary): raise FileNotFoundError(f"Convert binary not found: {convert_binary}") # Test cases: (input_file, output_file, input_format, output_format, reference_file) self.test_cases = [ { "input_file": "conv_yuv420_4056x3040_4056s.yuv", "output_file": "out_4056x3050_12168s_rgb888.rgb", "input_format": "4056:3040:4056:YUV420P", "output_format": "4056:3040:12168:RGB888", "reference_file": "ref_4056x3050_12168s_rgb888.rgb" }, { "input_file": "conv_800x600_1200s_422_yuyv.yuv", "output_file": "out_1600x1200_422p.yuv", "input_format": "800:600:1600:YUYV", "output_format": "1600:1200:1600:YUV422P", "reference_file": "ref_1600x1200_422p.yuv" }, { "input_file": "conv_rgb888_800x600_2432s.rgb", "output_file": "out_4000x3000_4032s.yuv", "input_format": "800:600:2432:RGB888", "output_format": "4000:3000:0:YUV444P", "reference_file": "ref_4000x3000_4032s.yuv" }, # Add more test cases here as needed ] def run_convert(self, input_file, output_file, input_format, output_format): """Run the convert utility with the specified parameters.""" # Use input directory if specified if self.input_dir: input_file = os.path.join(self.input_dir, input_file) # Use output directory if specified if self.output_dir: output_file = os.path.join(self.output_dir, output_file) cmd = [ self.convert_binary, input_file, output_file, "--input-format", input_format, "--output-format", output_format ] print(f"Running: {' '.join(cmd)}") try: result = subprocess.run(cmd, capture_output=True, text=True, check=True) print("Convert completed successfully") return True except subprocess.CalledProcessError as e: print(f"Convert failed with exit code {e.returncode}") print(f"stdout: {e.stdout}") print(f"stderr: {e.stderr}") return False def compare_files(self, file1, file2): """Compare two files and return True if they are identical.""" if not os.path.exists(file1): print(f"Error: File {file1} does not exist") return False if not os.path.exists(file2): print(f"Error: File {file2} does not exist") return False # Compare file sizes first size1 = os.path.getsize(file1) size2 = os.path.getsize(file2) if size1 != size2: print(f"Files have different sizes: {size1} vs {size2}") return False # Compare file contents using hash hash1 = self._file_hash(file1) hash2 = self._file_hash(file2) if hash1 == hash2: print("Files are identical") return True else: print("Files are different") return False def _file_hash(self, filepath): """Calculate SHA256 hash of a file.""" hash_sha256 = hashlib.sha256() with open(filepath, "rb") as f: for chunk in iter(lambda: f.read(4096), b""): hash_sha256.update(chunk) return hash_sha256.hexdigest() def run_test_case(self, test_case): """Run a single test case.""" print(f"\n=== Running test case ===") print(f"Input file: {test_case['input_file']}") print(f"Output file: {test_case['output_file']}") print(f"Input format: {test_case['input_format']}") print(f"Output format: {test_case['output_format']}") print(f"Reference file: {test_case['reference_file']}") # Check if input file exists input_file = test_case['input_file'] if self.input_dir: input_file = os.path.join(self.input_dir, test_case['input_file']) if not os.path.exists(input_file): print(f"Error: Input file {input_file} does not exist") return False # Run the convert utility success = self.run_convert( test_case['input_file'], test_case['output_file'], test_case['input_format'], test_case['output_format'] ) if not success: return False # Compare with reference file if it exists reference_file = test_case['reference_file'] if self.reference_dir: reference_file = os.path.join(self.reference_dir, test_case['reference_file']) if os.path.exists(reference_file): print(f"Comparing output with reference file...") # Use output directory for the generated output file output_file = test_case['output_file'] if self.output_dir: output_file = os.path.join(self.output_dir, test_case['output_file']) return self.compare_files(output_file, reference_file) else: print(f"Reference file {reference_file} not found, skipping comparison") return True def run_all_tests(self): """Run all test cases.""" print(f"Testing convert utility: {self.convert_binary}") if self.input_dir: print(f"Input directory: {self.input_dir}") if self.output_dir: print(f"Output directory: {self.output_dir}") if self.reference_dir: print(f"Reference directory: {self.reference_dir}") print(f"Number of test cases: {len(self.test_cases)}") passed = 0 failed = 0 for i, test_case in enumerate(self.test_cases, 1): print(f"\n--- Test case {i}/{len(self.test_cases)} ---") if self.run_test_case(test_case): passed += 1 print("✓ Test PASSED") else: failed += 1 print("✗ Test FAILED") print(f"\n=== Test Summary ===") print(f"Passed: {passed}") print(f"Failed: {failed}") print(f"Total: {len(self.test_cases)}") return failed == 0 def main(): parser = argparse.ArgumentParser(description="Test script for libpisp convert utility") parser.add_argument("convert_binary", help="Path to the convert binary") parser.add_argument("--test-dir", help="Directory containing test files") parser.add_argument("--in", dest="input_dir", help="Directory containing input files") parser.add_argument("--out", help="Directory where output files will be written") parser.add_argument("--ref", help="Directory containing reference files") args = parser.parse_args() try: tester = ConvertTester(args.convert_binary, args.out, args.input_dir, args.ref) # Change to test directory if specified if args.test_dir: if not os.path.exists(args.test_dir): print(f"Error: Test directory {args.test_dir} does not exist") return 1 os.chdir(args.test_dir) print(f"Changed to test directory: {args.test_dir}") # Create output directory if specified and it doesn't exist if args.out: if not os.path.exists(args.out): os.makedirs(args.out) print(f"Created output directory: {args.out}") # Run all tests success = tester.run_all_tests() return 0 if success else 1 except FileNotFoundError as e: print(f"Error: {e}") return 1 except Exception as e: print(f"Unexpected error: {e}") return 1 if __name__ == "__main__": sys.exit(main()) raspberrypi-libpisp-9ba67e6/utils/version.cpp.in000066400000000000000000000005171507172066400221120ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (C) 2021 - 2023, Raspberry Pi Ltd * * version.cpp - libpisp auto-generated versioning */ #include "common/version.hpp" namespace libpisp { const std::string versionString {"@VCS_TAG@"}; const std::string& version() { return versionString; } } // namespace libpisp raspberrypi-libpisp-9ba67e6/utils/version.py000077500000000000000000000033461507172066400213610ustar00rootroot00000000000000#!/usr/bin/python3 # SPDX-License-Identifier: BSD-2-Clause # Copyright (C) 2021-2025, Raspberry Pi Ltd. # # Generate version information for libpisp import os import subprocess import sys import time from datetime import datetime from string import hexdigits digits = 12 def generate_version(): try: if len(sys.argv) == 2: # Check if this is a git directory r = subprocess.run(['git', 'rev-parse', '--git-dir'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, universal_newlines=True) if r.returncode: raise RuntimeError('Invalid git directory!') # Get commit id r = subprocess.run(['git', 'rev-parse', '--verify', 'HEAD'], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, universal_newlines=True) if r.returncode: raise RuntimeError('Invalid git commit!') commit = r.stdout.strip('\n')[0:digits] # Check dirty status r = subprocess.run(['git', 'diff-index', '--quiet', 'HEAD'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, universal_newlines=True) if r.returncode: commit = commit + '-dirty' else: raise RuntimeError('Invalid number of command line arguments') commit = f'v{sys.argv[1]} {commit}' except RuntimeError as e: commit = f'v{sys.argv[1]}' finally: date_str = time.strftime( "%d-%m-%Y (%H:%M:%S)", time.gmtime(int(os.environ.get('SOURCE_DATE_EPOCH', time.time()))) ) print(f'{commit} {date_str}', end="") if __name__ == "__main__": generate_version()